From 55e155a169b63cea3262d95cf51705ef173a36c2 Mon Sep 17 00:00:00 2001
From: tuqin <tuqin@tuqin.cn>
Date: 星期一, 12 十月 2026 10:09:40 +0800
Subject: [PATCH] feat(hrm): 合同生成与模板下载、员工转正、导入导出字段对齐
---
src/api/hrm/employee/index.ts | 12 ++
src/views/hrm/employee/contract/index.vue | 156 +++++++++++++++++++++++-------
src/views/hrm/employee/index.vue | 43 ++++++++
src/views/hrm/employee/contract/modules/attachment-modal.vue | 47 +++++++++
src/api/hrm/employee/contract/index.ts | 25 +++++
5 files changed, 241 insertions(+), 42 deletions(-)
diff --git a/src/api/hrm/employee/contract/index.ts b/src/api/hrm/employee/contract/index.ts
index a252c3e..5b60ca2 100644
--- a/src/api/hrm/employee/contract/index.ts
+++ b/src/api/hrm/employee/contract/index.ts
@@ -154,3 +154,28 @@
{ file, updateSupport },
);
}
+
+/** 鐢熸垚骞朵笅杞藉悎鍚屾枃妗o紙docx锛�
+ * @param templateType 1-鍔冲姩鍚堝悓(绠�鐗�) 2-鍔冲姩鍚堝悓(鏍囧噯) 3-鍔冲姟鍚堝悓
+ */
+export function generateContractDocx(id: number, templateType: number) {
+ return requestClient.download(
+ `/hrm/employee-contract/generate-docx?id=${id}&templateType=${templateType}`,
+ );
+}
+
+/** 鐢熸垚鍚堝悓骞跺洖瀛樹负鍚堝悓闄勪欢锛岃繑鍥� blobId */
+export function generateContractArchive(id: number, templateType: number) {
+ return requestClient.post<number>(
+ `/hrm/employee-contract/generate?id=${id}&templateType=${templateType}`,
+ );
+}
+
+/** 涓嬭浇鍚堝悓绌虹櫧妯℃澘
+ * @param type 1-鍔冲姩鍚堝悓(绠�鐗�) 2-鍔冲姩鍚堝悓(鏍囧噯) 3-鍔冲姟鍚堝悓
+ */
+export function downloadContractTemplate(type: number) {
+ return requestClient.download(
+ `/hrm/employee-contract/download-template?type=${type}`,
+ );
+}
diff --git a/src/api/hrm/employee/index.ts b/src/api/hrm/employee/index.ts
index 0afd4a7..7e1ae52 100644
--- a/src/api/hrm/employee/index.ts
+++ b/src/api/hrm/employee/index.ts
@@ -175,3 +175,15 @@
export function deleteEmergencyContact(id: number) {
return requestClient.delete(`/hrm/employee/emergency-contact/delete?id=${id}`);
}
+
+/** 鍛樺伐杞锛堜粎鍔冲姩鍚堝悓鍙浆姝o級 */
+export function regularizeEmployee(id: number) {
+ return requestClient.put(`/hrm/employee/regular?id=${id}`);
+}
+
+/** 寰呰浆姝f彁閱掑垪琛紙璇曠敤涓�佸姵鍔ㄥ悎鍚屻�佽瘯鐢ㄦ湡鍗冲皢鍒版湡锛� */
+export function getRegularizeReminders(days = 7) {
+ return requestClient.get<HrmEmployeeApi.Employee[]>(
+ `/hrm/employee/regularize-reminders?days=${days}`,
+ );
+}
diff --git a/src/views/hrm/employee/contract/index.vue b/src/views/hrm/employee/contract/index.vue
index 02aa33c..c28ef57 100644
--- a/src/views/hrm/employee/contract/index.vue
+++ b/src/views/hrm/employee/contract/index.vue
@@ -3,12 +3,17 @@
import type { HrmEmployeeContractApi } from '#/api/hrm/employee/contract';
import { Page, useVbenModal } from '#/packages/effects/common-ui/src';
+import { downloadFileFromBlobPart } from '#/packages/utils/src';
-import { message, Tag } from 'ant-design-vue';
+import { DownOutlined } from '@ant-design/icons-vue';
+import { Button, Dropdown, Menu, message, Modal, Tag } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteEmployeeContract,
+ downloadContractTemplate,
+ generateContractArchive,
+ generateContractDocx,
getEmployeeContractPage,
} from '#/api/hrm/employee/contract';
import { $t } from '#/locales';
@@ -17,6 +22,7 @@
import Form from './modules/form.vue';
import ImportForm from './modules/import-form.vue';
import Terminate from './modules/terminate.vue';
+import Attachment from './modules/attachment-modal.vue';
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form,
@@ -30,6 +36,11 @@
const [TerminateModal, terminateModalApi] = useVbenModal({
connectedComponent: Terminate,
+ destroyOnClose: true,
+});
+
+const [AttachmentModal, attachmentModalApi] = useVbenModal({
+ connectedComponent: Attachment,
destroyOnClose: true,
});
@@ -48,6 +59,21 @@
importModalApi.open();
}
+/** 涓嬭浇鍚堝悓绌虹櫧妯℃澘锛歵ype 1-鍔冲姩鍚堝悓绠�鐗� 2-鍔冲姩鍚堝悓鏍囧噯 3-鍔冲姟鍚堝悓 */
+const TEMPLATE_NAMES: Record<number, string> = {
+ 1: '鍔冲姩鍚堝悓锛堢畝鐗堬級.docx',
+ 2: '鍔冲姩鍚堝悓锛堟爣鍑嗭級.docx',
+ 3: '鍔冲姟鍚堝悓.doc',
+};
+async function handleDownloadTemplate(type: number) {
+ const data = await downloadContractTemplate(type);
+ downloadFileFromBlobPart({
+ fileName: TEMPLATE_NAMES[type] || '鍚堝悓妯℃澘',
+ source: data,
+ });
+ message.success('妯℃澘宸蹭笅杞�');
+}
+
/** 缂栬緫鍚堝悓 */
function handleEdit(row: HrmEmployeeContractApi.EmployeeContract) {
formModalApi.setData({ id: row.id, formType: 'update' }).open();
@@ -61,6 +87,60 @@
/** 瑙i櫎/缁堟鍚堝悓 */
function handleTerminate(row: HrmEmployeeContractApi.EmployeeContract) {
terminateModalApi.setData({ id: row.id }).open();
+}
+
+/** 鏌ョ湅鍚堝悓闄勪欢 */
+function handleAttachments(row: HrmEmployeeContractApi.EmployeeContract) {
+ attachmentModalApi.setData({ id: row.id }).open();
+}
+
+/** 鐢熸垚鍚堝悓锛坉ocx锛夛細templateType 1-鍔冲姩鍚堝悓绠�鐗� 2-鍔冲姩鍚堝悓鏍囧噯 3-鍔冲姟鍚堝悓 */
+async function handleGenerate(
+ row: HrmEmployeeContractApi.EmployeeContract,
+ templateType: number,
+) {
+ // 1. 鐢熸垚骞跺洖瀛樹负鍚堝悓闄勪欢
+ await generateContractArchive(row.id!, templateType);
+ // 2. 鍚屾椂涓嬭浇涓�浠�
+ const data = await generateContractDocx(row.id!, templateType);
+ const genName =
+ { 1: '鍔冲姩鍚堝悓(绠�鐗�).docx', 2: '鍔冲姩鍚堝悓(鏍囧噯).docx', 3: '鍔冲姟鍚堝悓.docx' }[
+ templateType
+ ] || '鍚堝悓.docx';
+ downloadFileFromBlobPart({
+ fileName: `${row.contractNo || row.id}-${genName}`,
+ source: data,
+ });
+ message.success('鍚堝悓宸茬敓鎴愶紝骞跺綊妗e埌璇ュ悎鍚岄檮浠�');
+ handleRefresh();
+}
+
+/** 鈥滄洿澶氣�濊彍鍗曪細淇敼 / 缁 / 瑙i櫎缁堟 / 鍒犻櫎 */
+function handleMore({ key }: { key: number | string }, row: HrmEmployeeContractApi.EmployeeContract) {
+ switch (key) {
+ case 'edit': {
+ handleEdit(row);
+ break;
+ }
+ case 'renew': {
+ handleRenew(row);
+ break;
+ }
+ case 'terminate': {
+ handleTerminate(row);
+ break;
+ }
+ case 'delete': {
+ Modal.confirm({
+ title: `纭鍒犻櫎鍚堝悓銆�${row.contractNo || row.id}銆戝悧锛焋,
+ okText: '鍒犻櫎',
+ okType: 'danger',
+ cancelText: '鍙栨秷',
+ onOk: () => handleDelete(row),
+ });
+ break;
+ }
+ }
}
/** 鍒犻櫎鍚堝悓 */
@@ -110,6 +190,7 @@
<FormModal @success="handleRefresh" />
<ImportModal @success="handleRefresh" />
<TerminateModal @success="handleRefresh" />
+ <AttachmentModal />
<Grid table-title="鍛樺伐鍚堝悓鍒楄〃">
<template #parentNo="{ row }">
<span>{{ row.parentNo || '-' }}</span>
@@ -119,6 +200,18 @@
<span v-else>-</span>
</template>
<template #toolbar-tools>
+ <Dropdown>
+ <Button class="mr-2" type="primary">
+ 涓嬭浇鍚堝悓妯℃澘 <DownOutlined />
+ </Button>
+ <template #overlay>
+ <Menu @click="({ key }) => handleDownloadTemplate(Number(key))">
+ <Menu.Item key="1">鍔冲姩鍚堝悓锛堢畝鐗堬級</Menu.Item>
+ <Menu.Item key="2">鍔冲姩鍚堝悓锛堟爣鍑嗭級</Menu.Item>
+ <Menu.Item key="3">鍔冲姟鍚堝悓</Menu.Item>
+ </Menu>
+ </template>
+ </Dropdown>
<TableAction
:actions="[
{
@@ -139,44 +232,29 @@
/>
</template>
<template #actions="{ row }">
- <TableAction
- :actions="[
- {
- label: '缁',
- type: 'link',
- icon: ACTION_ICON.ADD,
- auth: ['hrm:employee-contract:create'],
- ifShow: isTerminable(row),
- onClick: handleRenew.bind(null, row),
- },
- {
- label: $t('common.edit'),
- type: 'link',
- icon: ACTION_ICON.EDIT,
- auth: ['hrm:employee-contract:update'],
- onClick: handleEdit.bind(null, row),
- },
- {
- label: '瑙i櫎/缁堟',
- type: 'link',
- icon: ACTION_ICON.CLOSE,
- auth: ['hrm:employee-contract:update'],
- ifShow: isTerminable(row),
- onClick: handleTerminate.bind(null, row),
- },
- {
- label: $t('common.delete'),
- type: 'link',
- danger: true,
- icon: ACTION_ICON.DELETE,
- auth: ['hrm:employee-contract:delete'],
- popConfirm: {
- title: $t('ui.actionMessage.deleteConfirm', ['鍛樺伐鍚堝悓']),
- confirm: handleDelete.bind(null, row),
- },
- },
- ]"
- />
+ <Dropdown>
+ <a class="text-primary" @click.prevent>鐢熸垚鍚堝悓 <DownOutlined /></a>
+ <template #overlay>
+ <Menu @click="({ key }) => handleGenerate(row, Number(key))">
+ <Menu.Item key="1">鍔冲姩鍚堝悓锛堢畝鐗堬級</Menu.Item>
+ <Menu.Item key="2">鍔冲姩鍚堝悓锛堟爣鍑嗭級</Menu.Item>
+ <Menu.Item key="3">鍔冲姟鍚堝悓</Menu.Item>
+ </Menu>
+ </template>
+ </Dropdown>
+ <a class="ml-3 text-primary" @click="handleAttachments(row)">闄勪欢</a>
+ <Dropdown>
+ <a class="ml-3 text-primary" @click.prevent>鏇村 <DownOutlined /></a>
+ <template #overlay>
+ <Menu @click="(e) => handleMore(e, row)">
+ <Menu.Item key="edit">淇敼</Menu.Item>
+ <Menu.Item v-if="isTerminable(row)" key="renew">缁</Menu.Item>
+ <Menu.Item v-if="isTerminable(row)" key="terminate">瑙i櫎/缁堟</Menu.Item>
+ <Menu.Divider />
+ <Menu.Item danger key="delete">鍒犻櫎</Menu.Item>
+ </Menu>
+ </template>
+ </Dropdown>
</template>
</Grid>
</Page>
diff --git a/src/views/hrm/employee/contract/modules/attachment-modal.vue b/src/views/hrm/employee/contract/modules/attachment-modal.vue
new file mode 100644
index 0000000..56817be
--- /dev/null
+++ b/src/views/hrm/employee/contract/modules/attachment-modal.vue
@@ -0,0 +1,47 @@
+<script lang="ts" setup>
+import type { HrmEmployeeContractApi } from '#/api/hrm/employee/contract';
+
+import { ref } from 'vue';
+
+import { useVbenModal } from '#/packages/effects/common-ui/src';
+
+import { getEmployeeContract } from '#/api/hrm/employee/contract';
+
+const attachments = ref<HrmEmployeeContractApi.AttachmentItem[]>([]);
+const loading = ref(false);
+
+const [Modal, modalApi] = useVbenModal({
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ return;
+ }
+ const data = modalApi.getData<{ id?: number }>();
+ if (!data?.id) {
+ return;
+ }
+ loading.value = true;
+ try {
+ const detail = await getEmployeeContract(data.id);
+ attachments.value = detail.attachmentList || [];
+ } finally {
+ loading.value = false;
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal title="鍚堝悓闄勪欢" class="w-[560px]">
+ <div v-if="loading" class="py-6 text-center text-gray-400">鍔犺浇涓�...</div>
+ <div v-else-if="attachments.length === 0" class="py-6 text-center text-gray-400">
+ 鏆傛棤闄勪欢锛堝彲鍦ㄣ�屾洿澶� 鈫� 淇敼銆嶉噷涓婁紶锛屾垨鐢ㄣ�岀敓鎴愬悎鍚屻�嶇敓鎴愬悗鑷姩褰掓。锛�
+ </div>
+ <ul v-else class="max-h-[50vh] overflow-auto pl-5">
+ <li v-for="f in attachments" :key="f.id" class="mb-2 list-disc">
+ <a :href="f.url" target="_blank" rel="noopener" class="text-primary">
+ {{ f.name || `闄勪欢${f.id}` }}
+ </a>
+ </li>
+ </ul>
+ </Modal>
+</template>
diff --git a/src/views/hrm/employee/index.vue b/src/views/hrm/employee/index.vue
index b1fba92..57f2d62 100644
--- a/src/views/hrm/employee/index.vue
+++ b/src/views/hrm/employee/index.vue
@@ -5,15 +5,15 @@
import { Page, useVbenModal } from '#/packages/effects/common-ui/src';
import { downloadFileFromBlobPart } from '#/packages/utils/src';
-import { Button, message, Progress, Tag } from 'ant-design-vue';
+import { Button, message, Progress, Tag, Alert } from 'ant-design-vue';
-import { ref } from 'vue';
+import { onMounted, ref } from 'vue';
import { DICT_TYPE } from '#/packages/constants/src';
import { getDictLabel } from '#/packages/effects/hooks/src';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
-import { deleteEmployee, exportEmployee, getEmployeePage } from '#/api/hrm/employee';
+import { deleteEmployee, exportEmployee, getEmployeePage, getRegularizeReminders, regularizeEmployee } from '#/api/hrm/employee';
import { $t } from '#/locales';
import { useGridColumns, useGridFormSchema } from './data';
@@ -116,6 +116,26 @@
importModalApi.open();
}
+/** 寰呰浆姝f彁閱掓暟 */
+const regularizeCount = ref(0);
+async function refreshRegularizeReminders() {
+ try {
+ const list = await getRegularizeReminders(7);
+ regularizeCount.value = list?.length ?? 0;
+ } catch {
+ regularizeCount.value = 0;
+ }
+}
+onMounted(refreshRegularizeReminders);
+
+/** 鍛樺伐杞锛堜粎鍔冲姩鍚堝悓鍙浆姝o級 */
+async function handleRegularize(row: HrmEmployeeApi.Employee) {
+ await regularizeEmployee(row.id!);
+ message.success(`銆�${row.name}銆戝凡杞`);
+ handleRefresh();
+ refreshRegularizeReminders();
+}
+
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
@@ -150,6 +170,13 @@
<Page auto-content-height>
<ImportModal @success="handleRefresh" />
<FormModal @success="handleRefresh" />
+ <Alert
+ v-if="regularizeCount > 0"
+ class="mb-3"
+ :message="`寰呰浆姝f彁閱掞細鏈� ${regularizeCount} 鍚嶅憳宸ヨ瘯鐢ㄦ湡鍗冲皢鍒版湡锛岃鍙婃椂杞`"
+ show-icon
+ type="warning"
+ />
<Grid table-title="鍛樺伐鍒楄〃">
<template #toolbar-tools>
<TableAction
@@ -207,6 +234,16 @@
onClick: handleEdit.bind(null, row),
},
{
+ label: '杞',
+ type: 'link',
+ auth: ['hrm:employee:update'],
+ ifShow: () => row.employeeStatus === 2,
+ popConfirm: {
+ title: `纭灏嗐��${row.name}銆戣浆姝e悧锛燂紙浠呭姵鍔ㄥ悎鍚屽彲杞锛屼笉鍚皟宀楋級`,
+ confirm: handleRegularize.bind(null, row),
+ },
+ },
+ {
label: $t('common.delete'),
type: 'link',
danger: true,
--
Gitblit v1.9.3