index.vue 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692
  1. <template>
  2. <div v-loading="pageLoading" class="app-container" :element-loading-text="loadingText">
  3. <el-tabs type="card" style="min-height: 400px; height: auto" class="employee-tabs">
  4. <el-tab-pane label="员工信息">
  5. <PageSearch ref="searchRef" :search-config="searchConfig" @query-click="handleQueryClick"
  6. @reset-click="handleResetClick" />
  7. <PageContent ref="contentRef" :content-config="contentConfig">
  8. <template #toolbar="{ toolbarRight, onToolbar, removeIds, cols }">
  9. <CrudToolbarLeft :remove-ids="removeIds" :perm-create="['module_payment:employee:create']">
  10. <el-button v-hasPerm="['module_payment:employee:create']" type="primary" icon="Plus"
  11. @click="handleOpenDialog('create')">
  12. 添加员工
  13. </el-button>
  14. </CrudToolbarLeft>
  15. <div class="data-table__toolbar--right">
  16. <CrudToolbarRight :buttons="toolbarRight" :cols="cols" :on-toolbar="onToolbar" />
  17. </div>
  18. </template>
  19. <template #table="{ data, loading, tableRef, onSelectionChange }">
  20. <div class="data-table__content">
  21. <el-table :ref="tableRef as any" v-loading="loading" :data="data" height="100%" border @selection-change="onSelectionChange">
  22. <template #empty>
  23. <el-empty :image-size="80" description="暂无数据" />
  24. </template>
  25. <el-table-column v-if="contentCols.find((col) => col.prop === 'selection')?.show" type="selection"
  26. min-width="55" align="center" />
  27. <!-- <el-table-column v-if="contentCols.find((col) => col.prop === 'employee_id')?.show" key="employee_id"
  28. label="员工ID" prop="employee_id" min-width="150" show-overflow-tooltip /> -->
  29. <el-table-column v-if="contentCols.find((col) => col.prop === 'employee_name')?.show"
  30. key="employee_name" label="员工姓名" prop="employee_name" min-width="120" show-overflow-tooltip />
  31. <el-table-column v-if="contentCols.find((col) => col.prop === 'employee_no')?.show" key="employee_no"
  32. label="员工工号" prop="employee_no" min-width="120" show-overflow-tooltip />
  33. <el-table-column v-if="contentCols.find((col) => col.prop === 'employee_mobile')?.show"
  34. key="employee_mobile" label="手机号" prop="employee_mobile" min-width="120" />
  35. <el-table-column v-if="contentCols.find((col) => col.prop === 'employee_email')?.show"
  36. key="employee_email" label="邮箱" prop="employee_email" min-width="150" show-overflow-tooltip />
  37. <el-table-column v-if="contentCols.find((col) => col.prop === 'status')?.show" key="status" label="激活状态"
  38. prop="status" min-width="100">
  39. <template #default="scope">
  40. <el-tag :type="STATUS_TAG_TYPE[scope.row.status]">
  41. {{ STATUS_LABEL[scope.row.status] || scope.row.status }}
  42. </el-tag>
  43. </template>
  44. </el-table-column>
  45. <el-table-column v-if="contentCols.find((col) => col.prop === 'created_time')?.show" key="created_time"
  46. label="创建时间" prop="created_time" min-width="160" sortable />
  47. <el-table-column v-if="contentCols.find((col) => col.prop === 'operation')?.show" fixed="right"
  48. label="操作" align="center" min-width="160">
  49. <template #default="scope">
  50. <el-button v-hasPerm="['module_payment:employee:detail']" type="info" size="small" link :icon="View"
  51. @click="handleOpenDialog('detail', scope.row.employee_id, scope.row.enterprise_id)">
  52. 详情
  53. </el-button>
  54. <el-button v-hasPerm="['module_payment:employee:update']" type="primary" size="small" link
  55. @click="handleOpenDialog('update', scope.row.employee_id, scope.row.enterprise_id)">
  56. 编辑
  57. </el-button>
  58. <el-button v-hasPerm="['module_payment:employee:invite']" type="success" size="small" link
  59. @click="handleGetInviteLink(scope.row)">
  60. 签约链接
  61. </el-button>
  62. </template>
  63. </el-table-column>
  64. </el-table>
  65. </div>
  66. </template>
  67. </PageContent>
  68. </el-tab-pane>
  69. <el-tab-pane label="部门信息">
  70. <PageSearch ref="deptSearchRef" :search-config="deptSearchConfig" @query-click="handleDeptQueryClick"
  71. @reset-click="handleDeptResetClick" />
  72. <PageContent ref="deptContentRef" :content-config="deptContentConfig">
  73. <template #toolbar="{ toolbarRight, onToolbar, removeIds, cols }">
  74. <CrudToolbarLeft :remove-ids="removeIds" :perm-create="['module_payment:department:create']">
  75. <el-button v-hasPerm="['module_payment:department:create']" type="primary" icon="Plus"
  76. @click="handleOpenDeptDialog('create')">
  77. 添加部门
  78. </el-button>
  79. </CrudToolbarLeft>
  80. <div class="data-table__toolbar--right">
  81. <CrudToolbarRight :buttons="toolbarRight" :cols="cols" :on-toolbar="onToolbar" />
  82. </div>
  83. </template>
  84. <template #table="{ data, loading, tableRef, onSelectionChange }">
  85. <div class="data-table__content">
  86. <el-table :ref="tableRef as any" v-loading="loading" :data="data" height="100%" border
  87. @selection-change="onSelectionChange">
  88. <template #empty>
  89. <el-empty :image-size="80" description="暂无数据" />
  90. </template>
  91. <el-table-column v-if="deptContentCols.find((col) => col.prop === 'selection')?.show" type="selection"
  92. min-width="55" align="center" />
  93. <el-table-column v-if="deptContentCols.find((col) => col.prop === 'name')?.show" key="name" label="部门名称"
  94. prop="name" min-width="150" show-overflow-tooltip />
  95. <el-table-column v-if="deptContentCols.find((col) => col.prop === 'code')?.show" key="code" label="部门编码"
  96. prop="code" min-width="120" show-overflow-tooltip />
  97. <el-table-column v-if="deptContentCols.find((col) => col.prop === 'created_time')?.show"
  98. key="created_time" label="创建时间" prop="created_time" min-width="160" sortable />
  99. <el-table-column v-if="deptContentCols.find((col) => col.prop === 'operation')?.show" fixed="right"
  100. label="操作" align="center" min-width="160">
  101. <template #default="scope">
  102. <el-button v-hasPerm="['module_payment:department:detail']" type="info" size="small" link
  103. :icon="View" @click="handleOpenDeptDialog('detail', scope.row.department_id)">
  104. 详情
  105. </el-button>
  106. <el-button v-hasPerm="['module_payment:department:update']" type="primary" size="small" link
  107. @click="handleOpenDeptDialog('update', scope.row.department_id)">
  108. 编辑
  109. </el-button>
  110. <el-button v-hasPerm="['module_payment:department:delete']" type="danger" size="small" link
  111. @click="handleDeleteDepartment(scope.row.department_id, scope.row.name)">
  112. 删除
  113. </el-button>
  114. </template>
  115. </el-table-column>
  116. </el-table>
  117. </div>
  118. </template>
  119. </PageContent>
  120. </el-tab-pane>
  121. </el-tabs>
  122. <EnhancedDialog v-model="dialogVisible.visible" :title="dialogVisible.title" @close="handleCloseDialog">
  123. <template v-if="dialogVisible.type === 'detail'">
  124. <template v-if="dialogVisible.entity === 'employee'">
  125. <EmployeeDetail :employee-id="currentEmployeeId" :enterprise-id="currentRowEnterpriseId || currentEnterpriseId" />
  126. </template>
  127. <template v-else-if="dialogVisible.entity === 'department'">
  128. <DepartmentDetail :department-id="currentDepartmentId" :enterprise-id="currentRowEnterpriseId || currentEnterpriseId" />
  129. </template>
  130. </template>
  131. <template v-else>
  132. <template v-if="dialogVisible.entity === 'employee'">
  133. <EmployeeForm ref="formRef" :type="dialogVisible.type" :employee-id="currentEmployeeId"
  134. :enterprise-id="currentRowEnterpriseId || currentEnterpriseId" @success="handleFormSuccess" />
  135. </template>
  136. <template v-else-if="dialogVisible.entity === 'department'">
  137. <DepartmentForm ref="deptFormRef" :type="dialogVisible.type" :department-id="currentDepartmentId"
  138. :enterprise-id="currentRowEnterpriseId || currentEnterpriseId" @success="handleDeptFormSuccess" />
  139. </template>
  140. </template>
  141. <template #footer>
  142. <div class="dialog-footer">
  143. <template v-if="dialogVisible.entity === 'employee'">
  144. <el-button v-if="dialogVisible.type !== 'detail'" @click="handleResetForm">
  145. 重置
  146. </el-button>
  147. <el-button v-if="dialogVisible.type !== 'detail' && dialogVisible.type === 'create'" type="primary"
  148. @click="handleSaveAndAddNext">
  149. 保存并添加下一个
  150. </el-button>
  151. <el-button v-if="dialogVisible.type !== 'detail'" type="primary" @click="handleSubmit">
  152. 保存
  153. </el-button>
  154. <el-button v-else type="primary" @click="handleCloseDialog">确定</el-button>
  155. </template>
  156. <template v-else-if="dialogVisible.entity === 'department'">
  157. <el-button v-if="dialogVisible.type !== 'detail'" type="primary" @click="handleDeptSubmit">
  158. 保存
  159. </el-button>
  160. <el-button v-if="dialogVisible.type === 'create'" type="primary" @click="handleDeptSubmit(true)">
  161. 保存并添加下一个
  162. </el-button>
  163. <el-button v-else type="primary" @click="handleCloseDialog">确定</el-button>
  164. </template>
  165. <!-- <el-button @click="handleCloseDialog">取消</el-button> -->
  166. </div>
  167. </template>
  168. </EnhancedDialog>
  169. <!-- 签约链接对话框 -->
  170. <el-dialog v-model="inviteDialogVisible.visible" :title="inviteDialogVisible.title" width="700px"
  171. @close="inviteDialogVisible.data = null">
  172. <div v-if="inviteDialogVisible.data" class="invite-link-container">
  173. <el-descriptions :column="1" border>
  174. <el-descriptions-item label="邀请链接">
  175. <div class="link-item">
  176. <el-link type="primary" :href="inviteDialogVisible.data.sign_url" target="_blank">
  177. {{ inviteDialogVisible.data.sign_url }}
  178. </el-link>
  179. <el-button type="text" size="small" @click="copyInviteLink(inviteDialogVisible.data.sign_url)">
  180. 复制
  181. </el-button>
  182. </div>
  183. </el-descriptions-item>
  184. <!-- <el-descriptions-item label="小程序签约二维码">
  185. <div class="qrcode-item">
  186. <div v-if="miniAppQrCode" class="qrcode-container">
  187. <img :src="miniAppQrCode" alt="小程序签约二维码" class="qrcode" />
  188. <p class="qrcode-tip">请使用支付宝扫码</p>
  189. </div>
  190. <div v-else class="qrcode-error">
  191. 二维码生成失败
  192. </div>
  193. <el-button type="text" size="small" @click="copyInviteLink(inviteDialogVisible.data.mini_app_sign_url)">
  194. 复制链接
  195. </el-button>
  196. </div>
  197. </el-descriptions-item> -->
  198. <el-descriptions-item label="签约二维码">
  199. <div class="qrcode-wrapper">
  200. <img v-if="qrcodeDataUrl" :src="qrcodeDataUrl" alt="签约二维码" class="qrcode-img" />
  201. <span v-else class="qrcode-loading">生成中...</span>
  202. <p class="qrcode-tip">请使用支付宝扫码签约</p>
  203. </div>
  204. </el-descriptions-item>
  205. <el-descriptions-item v-if="inviteDialogVisible.data.share_code" label="签约吱口令">
  206. <div class="link-item">
  207. <span>{{ inviteDialogVisible.data.share_code }}</span>
  208. <el-button type="text" size="small" @click="copyInviteLink(inviteDialogVisible.data.share_code)">
  209. 复制
  210. </el-button>
  211. </div>
  212. </el-descriptions-item>
  213. </el-descriptions>
  214. </div>
  215. </el-dialog>
  216. </div>
  217. </template>
  218. <script setup lang="ts">
  219. defineOptions({
  220. name: "Employee",
  221. inheritAttrs: false,
  222. });
  223. import { View } from "@element-plus/icons-vue";
  224. import EmployeeAPI, {
  225. EmployeePageQuery,
  226. STATUS_TAG_TYPE,
  227. STATUS_LABEL,
  228. } from "@/api/module_payment/employee";
  229. import DepartmentAPI, {
  230. DepartmentPageQuery,
  231. DEPARTMENT_STATUS_TAG_TYPE,
  232. DEPARTMENT_STATUS_LABEL,
  233. } from "@/api/module_payment/department";
  234. import CrudToolbarLeft from "@/components/CURD/CrudToolbarLeft.vue";
  235. import CrudToolbarRight from "@/components/CURD/CrudToolbarRight.vue";
  236. import PageSearch from "@/components/CURD/PageSearch.vue";
  237. import PageContent from "@/components/CURD/PageContent.vue";
  238. import EnhancedDialog from "@/components/CURD/EnhancedDialog.vue";
  239. import EmployeeDetail from "./components/EmployeeDetail.vue";
  240. import EmployeeForm from "./components/EmployeeForm.vue";
  241. import DepartmentDetail from "./components/DepartmentDetail.vue";
  242. import DepartmentForm from "./components/DepartmentForm.vue";
  243. import type { ISearchConfig, IContentConfig } from "@/components/CURD/types";
  244. import { useCrudList } from "@/components/CURD/useCrudList";
  245. import { useLoadingAction } from "@/composables/useLoadingAction";
  246. import { useRoute } from "vue-router";
  247. import { ref, reactive, computed, watch } from "vue";
  248. import { ElMessage, ElMessageBox } from "element-plus";
  249. import { useEnterpriseStore } from "@/store/modules/enterprise.store";
  250. import QRCode from "qrcode";
  251. const route = useRoute();
  252. const { searchRef, contentRef, handleQueryClick, handleResetClick, refreshList } = useCrudList();
  253. const { searchRef: deptSearchRef, contentRef: deptContentRef, handleQueryClick: handleDeptQueryClick, handleResetClick: handleDeptResetClick, refreshList: refreshDeptList } = useCrudList();
  254. const formRef = ref();
  255. const deptFormRef = ref();
  256. const { pageLoading, loadingText, execute: loadingExecute } = useLoadingAction();
  257. const searchConfig = reactive<ISearchConfig>({
  258. permPrefix: "module_payment:employee",
  259. colon: true,
  260. isExpandable: true,
  261. showNumber: 3,
  262. form: { labelWidth: "auto" },
  263. formItems: [
  264. {
  265. prop: "employee_name",
  266. label: "员工姓名",
  267. type: "input",
  268. attrs: { placeholder: "请输入员工姓名", clearable: true },
  269. },
  270. {
  271. prop: "employee_mobile",
  272. label: "手机号",
  273. type: "input",
  274. attrs: { placeholder: "请输入手机号", clearable: true },
  275. },
  276. {
  277. prop: "employee_no",
  278. label: "员工工号",
  279. type: "input",
  280. attrs: { placeholder: "请输入员工工号", clearable: true },
  281. },
  282. ],
  283. });
  284. const dialogVisible = reactive({
  285. title: "",
  286. visible: false,
  287. type: "create" as "create" | "update" | "detail",
  288. entity: "employee" as "employee" | "department",
  289. });
  290. const inviteDialogVisible = reactive({
  291. visible: false,
  292. title: "员工签约激活链接",
  293. data: null as any,
  294. });
  295. const qrcodeDataUrl = ref("");
  296. const useEnterprise = useEnterpriseStore();
  297. const currentEmployeeId = ref<string>("");
  298. const currentDepartmentId = ref<string>("");
  299. const currentRowEnterpriseId = ref<string>("");
  300. const currentEnterpriseId = computed(() => useEnterprise.getCurrentEnterprise?.enterprise_id || "");
  301. // 部门搜索配置
  302. const deptSearchConfig = reactive<ISearchConfig>({
  303. permPrefix: "module_payment:department",
  304. colon: true,
  305. isExpandable: true,
  306. showNumber: 3,
  307. form: { labelWidth: "auto" },
  308. formItems: [
  309. {
  310. prop: "name",
  311. label: "部门名称",
  312. type: "input",
  313. attrs: { placeholder: "请输入部门名称", clearable: true },
  314. },
  315. // {
  316. // prop: "code",
  317. // label: "部门编码",
  318. // type: "input",
  319. // attrs: { placeholder: "请输入部门编码", clearable: true },
  320. // },
  321. // {
  322. // prop: "status",
  323. // label: "状态",
  324. // type: "select",
  325. // options: Object.entries(DEPARTMENT_STATUS_LABEL).map(([value, label]) => ({
  326. // label,
  327. // value,
  328. // })),
  329. // attrs: { placeholder: "请选择状态", clearable: true },
  330. // },
  331. ],
  332. });
  333. // 部门列表列配置
  334. const deptContentCols = reactive<
  335. Array<{
  336. prop?: string;
  337. label?: string;
  338. show?: boolean;
  339. }>
  340. >([
  341. { prop: "selection", label: "选择框", show: false },
  342. // { prop: "index", label: "序号", show: true },
  343. { prop: "name", label: "部门名称", show: true },
  344. // { prop: "code", label: "部门编码", show: true },
  345. // { prop: "parent_name", label: "上级部门", show: true },
  346. // { prop: "leader_employee_name", label: "部门负责人", show: true },
  347. // { prop: "sort_order", label: "排序值", show: true },
  348. // { prop: "status", label: "状态", show: true },
  349. { prop: "created_time", label: "创建时间", show: true },
  350. { prop: "operation", label: "操作", show: true },
  351. ]);
  352. const contentCols = reactive<
  353. Array<{
  354. prop?: string;
  355. label?: string;
  356. show?: boolean;
  357. }>
  358. >([
  359. { prop: "selection", label: "选择框", show: false },
  360. { prop: "index", label: "序号", show: true },
  361. { prop: "employee_name", label: "员工姓名", show: true },
  362. { prop: "employee_no", label: "员工工号", show: true },
  363. { prop: "employee_id", label: "员工ID", show: true },
  364. { prop: "employee_mobile", label: "手机号", show: true },
  365. { prop: "employee_email", label: "邮箱", show: true },
  366. { prop: "status", label: "激活状态", show: true },
  367. { prop: "created_time", label: "创建时间", show: true },
  368. { prop: "operation", label: "操作", show: true },
  369. ]);
  370. const contentConfig = reactive<IContentConfig<EmployeePageQuery>>({
  371. permPrefix: "module_payment:employee",
  372. pk: "employee_id",
  373. cols: contentCols as IContentConfig["cols"],
  374. hideColumnFilter: false,
  375. toolbar: [],
  376. defaultToolbar: ["refresh", "filter", "import", "export"],
  377. pagination: {
  378. pageSize: 10,
  379. pageSizes: [10, 20, 30, 50],
  380. },
  381. request: { page_no: "page_no", page_size: "page_size" },
  382. indexAction: async (params) => {
  383. const query: EmployeePageQuery = {
  384. page_no: params.page_no,
  385. page_size: params.page_size,
  386. };
  387. const eid = currentEnterpriseId.value;
  388. if (eid) query.enterprise_id = eid;
  389. if (params.employee_name) query.employee_name = params.employee_name;
  390. if (params.employee_mobile) query.employee_mobile = params.employee_mobile;
  391. if (params.employee_no) query.employee_no = params.employee_no;
  392. const res = await EmployeeAPI.listEmployee(query);
  393. return {
  394. list: res.data.data?.items || [],
  395. total: Number(res.data.data?.total) || 0,
  396. };
  397. },
  398. });
  399. // 部门列表配置
  400. const deptContentConfig = reactive<IContentConfig<DepartmentPageQuery>>({
  401. permPrefix: "module_payment:department",
  402. pk: "department_id",
  403. cols: deptContentCols as IContentConfig["cols"],
  404. hideColumnFilter: false,
  405. toolbar: [],
  406. defaultToolbar: ["refresh", "filter", "import", "export"],
  407. pagination: {
  408. pageSize: 10,
  409. pageSizes: [10, 20, 30, 50],
  410. },
  411. request: { page_no: "page_no", page_size: "page_size" },
  412. initialParams: computed(() => ({
  413. enterprise_id: currentEnterpriseId.value,
  414. })) as Record<string, unknown>,
  415. indexAction: async (params) => {
  416. const query: DepartmentPageQuery = {
  417. page_no: params.page_no,
  418. page_size: params.page_size,
  419. };
  420. if (params.enterprise_id) query.enterprise_id = params.enterprise_id;
  421. if (params.name) query.department_name = params.name;
  422. if (params.code) query.department_code = params.code;
  423. if (params.status) query.status = params.status;
  424. const res = await DepartmentAPI.listDepartment(query);
  425. // 转换数据结构以匹配前端表格显示
  426. const items = res.data.data?.items.map((item: any) => ({
  427. department_id: item.department_id,
  428. name: item.department_name,
  429. code: item.department_code,
  430. parent_id: item.parent_department_id,
  431. parent_name: '', // 可以根据parent_id查询上级部门名称
  432. leader_employee_name: item.leader_employee_name,
  433. sort_order: item.sort_order,
  434. status: item.status,
  435. created_time: item.created_time,
  436. updated_time: item.updated_time
  437. })) || [];
  438. return {
  439. list: items,
  440. total: Number(res.data.data?.total) || 0,
  441. };
  442. },
  443. });
  444. function handleOpenDialog(type: "create" | "update" | "detail", employeeId: string, enterpriseId?: string) {
  445. dialogVisible.type = type;
  446. dialogVisible.entity = "employee";
  447. currentEmployeeId.value = employeeId;
  448. currentRowEnterpriseId.value = enterpriseId || "";
  449. if (type === "create") {
  450. dialogVisible.title = "添加员工";
  451. } else if (type === "update") {
  452. dialogVisible.title = "编辑员工";
  453. } else {
  454. dialogVisible.title = "员工详情";
  455. }
  456. dialogVisible.visible = true;
  457. }
  458. function handleOpenDeptDialog(type: "create" | "update" | "detail", departmentId: string) {
  459. dialogVisible.type = type;
  460. dialogVisible.entity = "department";
  461. currentDepartmentId.value = departmentId;
  462. if (type === "create") {
  463. dialogVisible.title = "添加部门";
  464. } else if (type === "update") {
  465. dialogVisible.title = "编辑部门";
  466. } else {
  467. dialogVisible.title = "部门详情";
  468. }
  469. dialogVisible.visible = true;
  470. }
  471. async function handleCloseDialog() {
  472. dialogVisible.visible = false;
  473. }
  474. function handleSubmit() {
  475. formRef.value?.submitForm();
  476. }
  477. function handleResetForm() {
  478. formRef.value?.resetForm();
  479. }
  480. function handleSaveAndAddNext() {
  481. formRef.value?.handleSaveAndAddNext();
  482. }
  483. function handleDeptSubmit(isContinue = false) {
  484. deptFormRef.value?.submitForm(isContinue);
  485. }
  486. function handleFormSuccess() {
  487. dialogVisible.visible = false;
  488. refreshList();
  489. }
  490. function handleDeptFormSuccess() {
  491. dialogVisible.visible = false;
  492. refreshDeptList();
  493. }
  494. async function handleDeleteDepartment(departmentId: string, departmentName: string) {
  495. try {
  496. const enterpriseId = currentEnterpriseId.value;
  497. if (!enterpriseId) {
  498. ElMessage.warning("请先选择企业");
  499. return;
  500. }
  501. await ElMessageBox.confirm(
  502. `确定要删除部门「${departmentName}」吗?`,
  503. "删除部门",
  504. {
  505. confirmButtonText: "确定",
  506. cancelButtonText: "取消",
  507. type: "warning"
  508. }
  509. );
  510. const res = await DepartmentAPI.deleteDepartment(departmentId, enterpriseId);
  511. if (res.data.code === 200) {
  512. ElMessage.success("删除部门成功");
  513. refreshDeptList();
  514. } else {
  515. ElMessage.error(res.data.message || "删除部门失败");
  516. }
  517. } catch (error: any) {
  518. if (error.message !== "cancel") {
  519. console.error("删除部门失败:", error);
  520. ElMessage.error("删除部门失败");
  521. }
  522. }
  523. }
  524. // 获取员工签约激活链接
  525. async function handleGetInviteLink(row: any) {
  526. try {
  527. const enterpriseId = row.enterprise_id || currentEnterpriseId.value;
  528. if (!enterpriseId) {
  529. ElMessage.warning("请先选择企业");
  530. return;
  531. }
  532. const res = await EmployeeAPI.inviteQuery({
  533. enterprise_id: enterpriseId,
  534. employee_id: row.employee_id,
  535. create_share_code: "Y", // 默认生成签约吱口令
  536. });
  537. if (res.data.data) {
  538. inviteDialogVisible.data = res.data.data;
  539. inviteDialogVisible.visible = true;
  540. }
  541. } catch (error) {
  542. ElMessage.error("获取签约链接失败");
  543. }
  544. }
  545. // 复制链接
  546. async function copyInviteLink(text: string) {
  547. try {
  548. await navigator.clipboard.writeText(text);
  549. ElMessage.success("链接已复制到剪贴板");
  550. } catch (err) {
  551. ElMessage.error("复制失败,请手动复制");
  552. }
  553. }
  554. watch(
  555. () => inviteDialogVisible.data?.sign_url,
  556. async (url) => {
  557. if (url) {
  558. try {
  559. qrcodeDataUrl.value = await QRCode.toDataURL(url, { width: 200, margin: 1 });
  560. } catch {
  561. qrcodeDataUrl.value = "";
  562. }
  563. } else {
  564. qrcodeDataUrl.value = "";
  565. }
  566. }
  567. );
  568. </script>
  569. <style scoped lang="scss">
  570. .employee-tabs {
  571. .el-tabs__nav {
  572. background-color: #fff;
  573. }
  574. .el-tabs__content {
  575. padding: 32px;
  576. color: #6b778c;
  577. font-size: 32px;
  578. font-weight: 600;
  579. }
  580. }
  581. .invite-link-container {
  582. .link-item {
  583. display: flex;
  584. align-items: center;
  585. gap: 8px;
  586. .el-link {
  587. flex: 1;
  588. word-break: break-all;
  589. }
  590. }
  591. .qrcode-item {
  592. display: flex;
  593. align-items: flex-start;
  594. gap: 16px;
  595. }
  596. .qrcode-container {
  597. display: flex;
  598. flex-direction: column;
  599. align-items: center;
  600. }
  601. .qrcode {
  602. width: 200px;
  603. height: 200px;
  604. border: 1px solid #e0e0e0;
  605. }
  606. .qrcode-tip {
  607. margin-top: 8px;
  608. font-size: 14px;
  609. color: #666;
  610. }
  611. .qrcode-wrapper {
  612. display: flex;
  613. flex-direction: column;
  614. align-items: center;
  615. padding: 8px 0;
  616. }
  617. .qrcode-img {
  618. width: 200px;
  619. height: 200px;
  620. border: 1px solid #e0e0e0;
  621. border-radius: 4px;
  622. }
  623. .qrcode-loading {
  624. color: #909399;
  625. font-size: 14px;
  626. }
  627. .qrcode-error {
  628. padding: 20px;
  629. color: #f56c6c;
  630. }
  631. }
  632. </style>