| | |
| | | { value: "cancelled", label: "已撤销" }, |
| | | ]; |
| | | |
| | | export const LEGACY_APPROVE_LIST_STORAGE_KEY = "oa_unified_approve_list_v1"; |
| | | |
| | | export function clearLegacyApproveListStorage() { |
| | | try { |
| | | localStorage.removeItem(LEGACY_APPROVE_LIST_STORAGE_KEY); |
| | | } catch { |
| | | /* ignore */ |
| | | } |
| | | } |
| | | |
| | | /** 提交弹窗:模板卡片(来自后端列表) */ |
| | | export function mapSubmitTemplateCard(row) { |
| | | const cfg = parseFormConfigToData(row?.formConfig); |
| | |
| | | buildApproveInstanceDto, |
| | | buildEditFormFromInstanceRow, |
| | | buildInstanceDto, |
| | | clearLegacyApproveListStorage, |
| | | createEmptySubmitForm, |
| | | mapInstanceFromApi, |
| | | mapSubmitTemplateCard, |
| | |
| | | } from "./approveListConstants.js"; |
| | | |
| | | export function useApproveList() { |
| | | clearLegacyApproveListStorage(); |
| | | const userStore = useUserStore(); |
| | | |
| | | const tableData = ref([]); |
| | |
| | | </el-tag> |
| | | </div> |
| | | <div class="fce-toolbar-actions"> |
| | | <el-dropdown trigger="click" @command="applyPreset"> |
| | | <el-button size="small">从预设导入</el-button> |
| | | <el-dropdown trigger="click" @visible-change="onImportDropdownVisible" @command="importFromTemplate"> |
| | | <el-button size="small" :loading="templateImportLoading">从已有模板导入</el-button> |
| | | <template #dropdown> |
| | | <el-dropdown-menu> |
| | | <el-dropdown-item v-for="p in FORM_CONFIG_PRESETS" :key="p.key" :command="p.key"> |
| | | {{ p.label }} |
| | | <el-dropdown-item v-if="!templateImportOptions.length" disabled> |
| | | 暂无其他审批模板 |
| | | </el-dropdown-item> |
| | | <el-dropdown-item |
| | | v-for="t in templateImportOptions" |
| | | :key="t.id" |
| | | :command="t.id" |
| | | > |
| | | <span>{{ t.label }}</span> |
| | | <el-tag v-if="!t.enabled" size="small" type="info" class="import-tag">已停用</el-tag> |
| | | </el-dropdown-item> |
| | | </el-dropdown-menu> |
| | | </template> |
| | |
| | | <el-empty |
| | | v-if="!inner.fields.length" |
| | | class="fce-empty" |
| | | description="暂无填报项,可添加或从预设快速导入" |
| | | description="暂无填报项,可添加或从已有审批模板导入" |
| | | :image-size="72" |
| | | /> |
| | | |
| | |
| | | |
| | | <script setup> |
| | | import { Bottom, Delete, Plus, Top } from "@element-plus/icons-vue"; |
| | | import { reactive, watch } from "vue"; |
| | | import { |
| | | FORM_CONFIG_PRESETS, |
| | | getApprovalTemplateDetail, |
| | | listApprovalTemplate, |
| | | TEMPLATE_TYPE_BUILTIN, |
| | | TEMPLATE_TYPE_CUSTOM, |
| | | } from "@/api/officeProcessAutomation/approvalTemplate.js"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import { reactive, ref, watch } from "vue"; |
| | | import { |
| | | mapEnabledFromApi, |
| | | unwrapTemplateDetail, |
| | | unwrapTemplateList, |
| | | } from "../approveTemplateConstants.js"; |
| | | import { |
| | | FORM_FIELD_TYPE_OPTIONS, |
| | | applyFormConfigPreset, |
| | | createEmptyFormConfigData, |
| | | createEmptyFormField, |
| | | formFieldTypeLabel, |
| | | parseFormConfigToData, |
| | | } from "../formConfigUtils.js"; |
| | | import { |
| | | SELECT_OPTION_SOURCE, |
| | |
| | | |
| | | const props = defineProps({ |
| | | modelValue: { type: Object, default: () => createEmptyFormConfigData() }, |
| | | /** 编辑当前模板时排除自身,避免从自己导入 */ |
| | | excludeTemplateId: { type: [String, Number], default: null }, |
| | | }); |
| | | |
| | | const emit = defineEmits(["update:modelValue"]); |
| | |
| | | const inner = reactive(createEmptyFormConfigData()); |
| | | |
| | | const { loading: optionSourceLoading, ensureForFields, getOptions } = useSelectOptionSources(); |
| | | |
| | | const templateImportOptions = ref([]); |
| | | const templateImportLoading = ref(false); |
| | | |
| | | function typeLabel(type) { |
| | | return formFieldTypeLabel(type); |
| | |
| | | emitOut(); |
| | | } |
| | | |
| | | function applyPreset(key) { |
| | | const data = applyFormConfigPreset(key); |
| | | async function loadTemplateImportOptions() { |
| | | templateImportLoading.value = true; |
| | | try { |
| | | const [customRes, builtinRes] = await Promise.all([ |
| | | listApprovalTemplate(TEMPLATE_TYPE_CUSTOM), |
| | | listApprovalTemplate(TEMPLATE_TYPE_BUILTIN), |
| | | ]); |
| | | const excludeId = |
| | | props.excludeTemplateId != null && props.excludeTemplateId !== "" |
| | | ? String(props.excludeTemplateId) |
| | | : ""; |
| | | templateImportOptions.value = [...unwrapTemplateList(customRes), ...unwrapTemplateList(builtinRes)] |
| | | .filter((row) => row?.id != null && String(row.id) !== excludeId) |
| | | .map((row) => ({ |
| | | id: row.id, |
| | | label: row.templateName || `模板 #${row.id}`, |
| | | enabled: mapEnabledFromApi(row.enabled), |
| | | })); |
| | | } catch { |
| | | templateImportOptions.value = []; |
| | | ElMessage.error("加载审批模板列表失败"); |
| | | } finally { |
| | | templateImportLoading.value = false; |
| | | } |
| | | } |
| | | |
| | | function onImportDropdownVisible(visible) { |
| | | if (visible) loadTemplateImportOptions(); |
| | | } |
| | | |
| | | async function importFromTemplate(templateId) { |
| | | if (!templateId) return; |
| | | if (inner.fields.length) { |
| | | try { |
| | | await ElMessageBox.confirm("将覆盖当前填报项配置,是否继续?", "从模板导入", { |
| | | type: "warning", |
| | | confirmButtonText: "继续导入", |
| | | cancelButtonText: "取消", |
| | | }); |
| | | } catch { |
| | | return; |
| | | } |
| | | } |
| | | templateImportLoading.value = true; |
| | | try { |
| | | const res = await getApprovalTemplateDetail(templateId); |
| | | const row = unwrapTemplateDetail(res); |
| | | const data = parseFormConfigToData(row?.formConfig); |
| | | if (!data.fields?.length) { |
| | | ElMessage.warning("该模板未配置填报项"); |
| | | return; |
| | | } |
| | | syncFromProps(data); |
| | | emitOut(); |
| | | ElMessage.success(`已导入「${row.templateName || "模板"}」的填报项`); |
| | | } catch { |
| | | ElMessage.error("加载模板详情失败"); |
| | | } finally { |
| | | templateImportLoading.value = false; |
| | | } |
| | | } |
| | | </script> |
| | | |
| | |
| | | align-items: center; |
| | | gap: 8px; |
| | | } |
| | | .import-tag { |
| | | margin-left: 8px; |
| | | vertical-align: middle; |
| | | } |
| | | |
| | | .fce-empty { |
| | | padding: 24px 0; |
| | |
| | | |
| | | <el-form-item label="填报配置"> |
| | | |
| | | <FormConfigEditor v-model="form.formConfigData" /> |
| | | <FormConfigEditor v-model="form.formConfigData" :exclude-template-id="form.id" /> |
| | | |
| | | <p class="flow-tip">配置提交审批时需填写的表单项,保存后写入 formConfig(JSON)。</p> |
| | | |
| | |
| | | } from "./approveTemplateConstants.js"; |
| | | import { parseFormConfigToData } from "./formConfigUtils.js"; |
| | | |
| | | const LEGACY_STORAGE_KEY = "oa_approve_template_custom_v1"; |
| | | |
| | | const FALLBACK_TEMPLATE_TYPE_OPTIONS = [ |
| | | { value: 0, label: "系统内置" }, |
| | | { value: 1, label: "自定义" }, |
| | |
| | | ); |
| | | } |
| | | |
| | | function clearLegacyStorage() { |
| | | try { |
| | | localStorage.removeItem(LEGACY_STORAGE_KEY); |
| | | } catch { |
| | | /* ignore */ |
| | | } |
| | | } |
| | | |
| | | export function useApproveTemplate() { |
| | | clearLegacyStorage(); |
| | | |
| | | const templateTypeOptions = ref([...FALLBACK_TEMPLATE_TYPE_OPTIONS]); |
| | | |
| | | function templateTypeLabel(type) { |
| | |
| | | ); |
| | | } |
| | | |
| | | /** 本地模拟:根据用户生成稳定「假期余额」占位 */ |
| | | function mockLeaveBalance(u) { |
| | | if (!u) return undefined; |
| | | const idStr = String(u.userId ?? u.id ?? "0"); |
| | | let s = 0; |
| | | for (let i = 0; i < idStr.length; i++) s += idStr.charCodeAt(i); |
| | | return Math.round(((s % 130) / 10 + 5) * 100) / 100; |
| | | /** 假期余额(对接考勤 API 前不展示假数据) */ |
| | | function mockLeaveBalance() { |
| | | return undefined; |
| | | } |
| | | |
| | | function filterUsersByQuery(query) { |
| | |
| | | } |
| | | } |
| | | |
| | | /** 本地模拟列表数据 */ |
| | | const allRows = ref([ |
| | | { |
| | | id: "1", |
| | | applicantId: "mock_1", |
| | | applicantNo: "zhangsan", |
| | | applicantName: "张三", |
| | | leaveType: "annual", |
| | | leaveBalanceDays: 12, |
| | | leaveStartTime: "2026-05-10 09:00:00", |
| | | leaveEndTime: "2026-05-12 18:00:00", |
| | | leaveDurationDays: 2.38, |
| | | leaveReason: "年休假返乡探亲。", |
| | | approvalMode: "parallel", |
| | | approverIds: [], |
| | | approverNames: "", |
| | | approvalResult: "pending", |
| | | attachmentList: [{ name: "车票订单.pdf" }], |
| | | createTime: "2026-05-09 10:20:00", |
| | | }, |
| | | { |
| | | id: "2", |
| | | applicantId: "mock_2", |
| | | applicantNo: "lisi", |
| | | applicantName: "李四", |
| | | leaveType: "sick", |
| | | leaveBalanceDays: 0, |
| | | leaveStartTime: "2026-05-14 08:30:00", |
| | | leaveEndTime: "2026-05-14 12:00:00", |
| | | leaveDurationDays: 0.15, |
| | | leaveReason: "上午门诊复查。", |
| | | approvalMode: "or_sign", |
| | | approverIds: [], |
| | | approverNames: "", |
| | | approvalResult: "approved", |
| | | attachmentList: [], |
| | | createTime: "2026-05-13 16:00:00", |
| | | }, |
| | | ]); |
| | | const allRows = ref([]); |
| | | |
| | | const searchForm = reactive({ |
| | | applicantKeyword: "", |
| | |
| | | window.open(url, "_blank"); |
| | | return; |
| | | } |
| | | proxy?.$modal?.msgSuccess?.(`已模拟下载:${row.name}`); |
| | | proxy?.$modal?.msgWarning?.("暂无下载地址"); |
| | | } |
| | | |
| | | async function openFormDialog(mode, row) { |
| | |
| | | approvalResult: "pending", |
| | | createTime: dayjs().format("YYYY-MM-DD HH:mm:ss"), |
| | | }); |
| | | proxy?.$modal?.msgSuccess?.("新增成功(本地模拟)"); |
| | | proxy?.$modal?.msgSuccess?.("新增成功"); |
| | | } else { |
| | | const idx = allRows.value.findIndex((r) => r.id === form.id); |
| | | if (idx !== -1) { |
| | |
| | | createTime: prev.createTime ?? dayjs().format("YYYY-MM-DD HH:mm:ss"), |
| | | }; |
| | | } |
| | | proxy?.$modal?.msgSuccess?.("保存成功(本地模拟)"); |
| | | proxy?.$modal?.msgSuccess?.("保存成功"); |
| | | } |
| | | formDialog.visible = false; |
| | | handleQuery(); |
| | |
| | | } |
| | | } |
| | | |
| | | const allRows = ref([ |
| | | { |
| | | id: "1", |
| | | applicantId: "mock_1", |
| | | applicantNo: "zhangsan", |
| | | applicantName: "张三", |
| | | overtimeType: "weekday", |
| | | overtimeDate: "2026-05-10", |
| | | overtimeStartTime: "2026-05-10 18:00:00", |
| | | overtimeEndTime: "2026-05-10 21:30:00", |
| | | overtimeHours: 3.5, |
| | | overtimeReason: "项目上线保障。", |
| | | approvalFlowNodes: demoApprovalFlowNodes(), |
| | | approvalResult: "pending", |
| | | attachmentList: [{ name: "任务单.pdf" }], |
| | | createTime: "2026-05-09 10:20:00", |
| | | }, |
| | | { |
| | | id: "2", |
| | | applicantId: "mock_2", |
| | | applicantNo: "lisi", |
| | | applicantName: "李四", |
| | | overtimeType: "weekend", |
| | | overtimeDate: "2026-05-11", |
| | | overtimeStartTime: "2026-05-11 09:00:00", |
| | | overtimeEndTime: "2026-05-11 12:15:00", |
| | | overtimeHours: 3.25, |
| | | overtimeReason: "客户现场支持。", |
| | | approvalFlowNodes: demoApprovalFlowNodes(), |
| | | approvalResult: "approved", |
| | | attachmentList: [], |
| | | createTime: "2026-05-10 16:00:00", |
| | | }, |
| | | ]); |
| | | const allRows = ref([]); |
| | | |
| | | const searchForm = reactive({ |
| | | applicantKeyword: "", |
| | |
| | | window.open(url, "_blank"); |
| | | return; |
| | | } |
| | | proxy?.$modal?.msgSuccess?.(`已模拟下载:${row.name}`); |
| | | proxy?.$modal?.msgWarning?.("暂无下载地址"); |
| | | } |
| | | |
| | | function handleExport() { |
| | |
| | | approvalResult: "pending", |
| | | createTime: dayjs().format("YYYY-MM-DD HH:mm:ss"), |
| | | }); |
| | | proxy?.$modal?.msgSuccess?.("新增成功(本地模拟)"); |
| | | proxy?.$modal?.msgSuccess?.("新增成功"); |
| | | } else { |
| | | const idx = allRows.value.findIndex((r) => r.id === form.id); |
| | | if (idx !== -1) { |
| | |
| | | createTime: prev.createTime ?? dayjs().format("YYYY-MM-DD HH:mm:ss"), |
| | | }; |
| | | } |
| | | proxy?.$modal?.msgSuccess?.("保存成功(本地模拟)"); |
| | | proxy?.$modal?.msgSuccess?.("保存成功"); |
| | | } |
| | | formDialog.visible = false; |
| | | handleQuery(); |
| | |
| | | |
| | | export const STORAGE_KEY = "oa_enterprise_news_v1"; |
| | | |
| | | /** 演示用目标受众(后期对接组织架构) */ |
| | | export const MOCK_AUDIENCE = [ |
| | | { userId: "u1", employeeNo: "zhangsan", name: "张三", deptName: "研发部", isManagement: false }, |
| | | { userId: "u2", employeeNo: "lisi", name: "李四", deptName: "研发部", isManagement: false }, |
| | | { userId: "u3", employeeNo: "wangwu", name: "王五", deptName: "行政部", isManagement: false }, |
| | | { userId: "u4", employeeNo: "zhaoliu", name: "赵六", deptName: "销售部", isManagement: false }, |
| | | { userId: "u5", employeeNo: "sunqi", name: "孙七", deptName: "财务部", isManagement: false }, |
| | | { userId: "u6", employeeNo: "zhouba", name: "周八", deptName: "总经办", isManagement: true }, |
| | | { userId: "u7", employeeNo: "wujiu", name: "吴九", deptName: "总经办", isManagement: true }, |
| | | { userId: "u8", employeeNo: "zhengshi", name: "郑十", deptName: "人力资源部", isManagement: false }, |
| | | ]; |
| | | /** 目标受众(对接组织架构 API 前为空) */ |
| | | export const MOCK_AUDIENCE = []; |
| | | |
| | | const DEPT_OPTIONS = [ |
| | | { value: "101", label: "研发部" }, |
| | |
| | | } |
| | | |
| | | function buildReadRecords(readUserIds = []) { |
| | | const set = new Set(readUserIds); |
| | | return MOCK_AUDIENCE.map((u) => ({ |
| | | userId: u.userId, |
| | | employeeNo: u.employeeNo, |
| | | name: u.name, |
| | | deptName: u.deptName, |
| | | readAt: set.has(u.userId) ? dayjs().subtract(2, "day").format("YYYY-MM-DD HH:mm:ss") : "", |
| | | return (readUserIds || []).map((userId) => ({ |
| | | userId, |
| | | employeeNo: "", |
| | | name: "", |
| | | deptName: "", |
| | | readAt: "", |
| | | lastRemindAt: "", |
| | | })); |
| | | } |
| | |
| | | }; |
| | | } |
| | | |
| | | /** @deprecated 不再注入演示数据,初始列表为空 */ |
| | | export function createInitialMockNews() { |
| | | const policyContent = |
| | | "<p><strong>2026 年考勤管理制度(试行)</strong></p><p>一、上班时间 9:00,弹性打卡窗口 8:30–9:30。</p><p>二、请假须提前在 OA 提交审批。</p><p>三、本制度自 2026-06-01 起执行。</p>"; |
| | | const cultureContent = |
| | | "<p>2026 企业年会圆满落幕!感谢每一位同事的参与,以下为精彩瞬间图集。</p>"; |
| | | const strategyContent = |
| | | "<p><strong>2026 下半年战略方向(内部)</strong></p><p>聚焦核心产品线升级与海外市场拓展,具体指标见附件。</p>"; |
| | | |
| | | const policyRow = { |
| | | id: "news_1", |
| | | newsNo: "EN202605150001", |
| | | title: "关于发布新考勤制度的通知", |
| | | summary: "请全体员工认真阅读并确认知悉,自 2026-06-01 起执行。", |
| | | newsType: "policy", |
| | | layoutTemplate: "policy", |
| | | contentHtml: policyContent, |
| | | coverImage: "", |
| | | mediaList: [], |
| | | attachmentList: [{ name: "考勤制度2026.pdf", url: "/mock/attendance-policy.pdf" }], |
| | | editorRole: "hr", |
| | | reviewerRole: "admin", |
| | | readScope: "all", |
| | | targetDeptIds: [], |
| | | targetUserIds: [], |
| | | publishStatus: "published", |
| | | publisherName: "人力资源部", |
| | | publishTime: "2026-05-15 10:00:00", |
| | | readRecords: buildReadRecords(["u6", "u7", "u8"]), |
| | | remindLogs: [], |
| | | likes: [], |
| | | comments: [], |
| | | versions: [ |
| | | { |
| | | versionNo: 1, |
| | | title: "关于发布新考勤制度的通知(征求意见稿)", |
| | | summary: "征求意见稿", |
| | | contentHtml: "<p>征求意见稿:上班时间 9:00……</p>", |
| | | newsType: "policy", |
| | | publishTime: "2026-05-10 09:00:00", |
| | | archivedAt: "2026-05-15 09:55:00", |
| | | changeNote: "定稿发布", |
| | | publisherName: "人力资源部", |
| | | }, |
| | | ], |
| | | versionNo: 2, |
| | | requireReadConfirm: true, |
| | | createTime: "2026-05-10 09:00:00", |
| | | updateTime: "2026-05-15 10:00:00", |
| | | }; |
| | | |
| | | const cultureRow = { |
| | | id: "news_2", |
| | | newsNo: "EN202605200002", |
| | | title: "2026 企业年会精彩瞬间", |
| | | summary: "年会图集上线,欢迎点赞留言,共建企业文化。", |
| | | newsType: "culture", |
| | | layoutTemplate: "gallery", |
| | | contentHtml: cultureContent, |
| | | coverImage: "/mock/annual-cover.jpg", |
| | | mediaList: [ |
| | | { type: "image", name: "开场.jpg", url: "/mock/annual-1.jpg" }, |
| | | { type: "image", name: "颁奖.jpg", url: "/mock/annual-2.jpg" }, |
| | | { type: "video", name: "年会花絮.mp4", url: "/mock/annual.mp4" }, |
| | | ], |
| | | attachmentList: [], |
| | | editorRole: "dept_manager", |
| | | reviewerRole: "admin", |
| | | readScope: "all", |
| | | targetDeptIds: [], |
| | | targetUserIds: [], |
| | | publishStatus: "published", |
| | | publisherName: "行政部", |
| | | publishTime: "2026-05-20 14:30:00", |
| | | readRecords: buildReadRecords(["u1", "u2", "u3", "u4", "u5", "u6", "u7"]), |
| | | remindLogs: [], |
| | | likes: [ |
| | | { userId: "u1", name: "张三", time: "2026-05-20 15:01:00" }, |
| | | { userId: "u2", name: "李四", time: "2026-05-20 15:05:00" }, |
| | | { userId: "u4", name: "赵六", time: "2026-05-20 16:20:00" }, |
| | | ], |
| | | comments: [ |
| | | { id: "c1", userId: "u1", name: "张三", content: "节目太精彩了!", time: "2026-05-20 15:10:00" }, |
| | | { id: "c2", userId: "u3", name: "王五", content: "期待明年再聚!", time: "2026-05-20 17:00:00" }, |
| | | ], |
| | | versions: [], |
| | | versionNo: 1, |
| | | requireReadConfirm: false, |
| | | createTime: "2026-05-20 14:00:00", |
| | | updateTime: "2026-05-20 14:30:00", |
| | | }; |
| | | |
| | | const strategyRow = { |
| | | id: "news_3", |
| | | newsNo: "EN202605220003", |
| | | title: "2026 下半年战略规划要点", |
| | | summary: "仅限管理层阅读,请勿对外传播。", |
| | | newsType: "announcement", |
| | | layoutTemplate: "briefing", |
| | | contentHtml: strategyContent, |
| | | coverImage: "", |
| | | mediaList: [], |
| | | attachmentList: [{ name: "战略指标.pdf", url: "/mock/strategy.pdf" }], |
| | | editorRole: "admin", |
| | | reviewerRole: "admin", |
| | | readScope: "management", |
| | | targetDeptIds: [], |
| | | targetUserIds: [], |
| | | publishStatus: "published", |
| | | publisherName: "总经办", |
| | | publishTime: "2026-05-22 09:00:00", |
| | | readRecords: buildReadRecords(["u6", "u7"]), |
| | | remindLogs: [], |
| | | likes: [], |
| | | comments: [], |
| | | versions: [], |
| | | versionNo: 1, |
| | | requireReadConfirm: false, |
| | | createTime: "2026-05-22 08:30:00", |
| | | updateTime: "2026-05-22 09:00:00", |
| | | }; |
| | | |
| | | const industryDraft = { |
| | | id: "news_4", |
| | | newsNo: "EN202605250004", |
| | | title: "制造业数字化转型趋势简报", |
| | | summary: "行业动态草稿,待管理员审核后发布。", |
| | | newsType: "industry", |
| | | layoutTemplate: "standard", |
| | | contentHtml: "<p>本期简报梳理工业互联网与 AI 质检应用案例……</p>", |
| | | coverImage: "", |
| | | mediaList: [], |
| | | attachmentList: [], |
| | | editorRole: "editor", |
| | | reviewerRole: "admin", |
| | | readScope: "all", |
| | | targetDeptIds: [], |
| | | targetUserIds: [], |
| | | publishStatus: "pending_review", |
| | | publisherName: "市场部", |
| | | publishTime: "", |
| | | readRecords: [], |
| | | remindLogs: [], |
| | | likes: [], |
| | | comments: [], |
| | | versions: [], |
| | | versionNo: 1, |
| | | requireReadConfirm: false, |
| | | createTime: "2026-05-25 11:00:00", |
| | | updateTime: "2026-05-25 11:00:00", |
| | | }; |
| | | |
| | | return [policyRow, cultureRow, strategyRow, industryDraft]; |
| | | return []; |
| | | } |
| | | |
| | | export function loadStoredNews() { |
| | |
| | | const name = (galleryInput.value || "").trim(); |
| | | if (!name) return; |
| | | form.mediaList = form.mediaList || []; |
| | | form.mediaList.push({ type: "image", name, url: `/mock/${name}` }); |
| | | form.mediaList.push({ type: "image", name, url: "" }); |
| | | galleryInput.value = ""; |
| | | } |
| | | |
| | |
| | | PUBLISH_ROLE_OPTIONS, |
| | | DEPT_OPTIONS, |
| | | createEmptyForm, |
| | | createInitialMockNews, |
| | | loadStoredNews, |
| | | saveStoredNews, |
| | | getUnreadEmployees, |
| | |
| | | |
| | | export function useEnterpriseNews() { |
| | | const stored = loadStoredNews(); |
| | | const allRows = ref(stored?.length ? stored : createInitialMockNews()); |
| | | const allRows = ref(stored?.length ? stored : []); |
| | | |
| | | const searchForm = reactive({ |
| | | keyword: "", |
| | |
| | | return { ok: true, count: selectedIds.length }; |
| | | } |
| | | |
| | | function toggleLike(row, userId = "u1", userName = "张三") { |
| | | function toggleLike(row, userId = "", userName = "") { |
| | | const hit = allRows.value.find((r) => r.id === row.id); |
| | | if (!hit) return; |
| | | hit.likes = hit.likes || []; |
| | |
| | | } |
| | | } |
| | | |
| | | function addComment(row, content, userId = "u1", userName = "张三") { |
| | | function addComment(row, content, userId = "", userName = "") { |
| | | const text = (content || "").trim(); |
| | | if (!text) return { ok: false, message: "请输入评论内容" }; |
| | | const hit = allRows.value.find((r) => r.id === row.id); |
| | |
| | | return "待审批"; |
| | | } |
| | | |
| | | /** 本地模拟数据源 */ |
| | | const allRows = ref([ |
| | | { |
| | | id: "1", |
| | | applicantName: "周明", |
| | | applyDate: "2026-05-01", |
| | | regularizationDate: "2026-06-01", |
| | | probationSummary: "试用期内完成模块开发与联调,熟悉业务流程。", |
| | | approvalMode: "parallel", |
| | | approverIds: [], |
| | | approverNames: "", |
| | | approvalResult: "pending", |
| | | attachmentList: [{ name: "工作总结.pdf" }, { name: "考核表.xlsx" }], |
| | | }, |
| | | { |
| | | id: "2", |
| | | applicantName: "吴芳", |
| | | applyDate: "2026-05-08", |
| | | regularizationDate: "2026-06-10", |
| | | probationSummary: "完成入职培训与岗位实践,达到岗位要求。", |
| | | approvalMode: "countersign", |
| | | approverIds: [], |
| | | approverNames: "", |
| | | approvalResult: "approved", |
| | | attachmentList: [], |
| | | }, |
| | | ]); |
| | | const allRows = ref([]); |
| | | |
| | | const searchForm = reactive({ |
| | | applicantName: "", |
| | |
| | | window.open(url, "_blank"); |
| | | return; |
| | | } |
| | | proxy?.$modal?.msgSuccess?.(`已模拟下载:${row.name}`); |
| | | proxy?.$modal?.msgWarning?.("暂无下载地址"); |
| | | } |
| | | |
| | | function openAddWithTemplate() { |
| | |
| | | if (formDialog.mode === "add") { |
| | | const id = `local_${Date.now()}`; |
| | | allRows.value.unshift({ id, ...payload, approvalResult: "pending" }); |
| | | proxy?.$modal?.msgSuccess?.("新增成功(本地模拟)"); |
| | | proxy?.$modal?.msgSuccess?.("新增成功"); |
| | | } else { |
| | | const idx = allRows.value.findIndex((r) => r.id === form.id); |
| | | if (idx !== -1) { |
| | |
| | | approvalResult: prev.approvalResult ?? "pending", |
| | | }; |
| | | } |
| | | proxy?.$modal?.msgSuccess?.("保存成功(本地模拟)"); |
| | | proxy?.$modal?.msgSuccess?.("保存成功"); |
| | | } |
| | | formDialog.visible = false; |
| | | handleQuery(); |
| | |
| | | return "待审批"; |
| | | } |
| | | |
| | | /** 本地模拟列表数据 */ |
| | | const allRows = ref([ |
| | | { |
| | | id: "1", |
| | | applicantId: "1001", |
| | | applicantName: "周明", |
| | | transferDate: "2026-05-20", |
| | | originalPostId: "post_dev", |
| | | originalPostName: "软件开发工程师", |
| | | targetPostId: "post_senior_dev", |
| | | targetPostName: "高级软件开发工程师", |
| | | approvalResult: "pending", |
| | | approvalMode: "parallel", |
| | | approverIds: [], |
| | | approverNames: "", |
| | | }, |
| | | { |
| | | id: "2", |
| | | applicantId: "1002", |
| | | applicantName: "吴芳", |
| | | transferDate: "2026-05-10", |
| | | originalPostId: "post_pm", |
| | | originalPostName: "产品经理", |
| | | targetPostId: "post_senior_pm", |
| | | targetPostName: "高级产品经理", |
| | | approvalResult: "approved", |
| | | approvalMode: "countersign", |
| | | approverIds: [], |
| | | approverNames: "张三、李四", |
| | | }, |
| | | ]); |
| | | const allRows = ref([]); |
| | | |
| | | const searchForm = reactive({ |
| | | applicantId: "", |
| | |
| | | ...payload, |
| | | approvalResult: "pending", |
| | | }); |
| | | proxy?.$modal?.msgSuccess?.("新增成功(本地模拟)"); |
| | | proxy?.$modal?.msgSuccess?.("新增成功"); |
| | | } else { |
| | | const idx = allRows.value.findIndex((r) => r.id === form.id); |
| | | const prev = idx !== -1 ? allRows.value[idx] : {}; |
| | |
| | | ...payload, |
| | | }; |
| | | } |
| | | proxy?.$modal?.msgSuccess?.("保存成功(本地模拟)"); |
| | | proxy?.$modal?.msgSuccess?.("保存成功"); |
| | | } |
| | | formDialog.visible = false; |
| | | handleQuery(); |
| | |
| | | return v === "transfer" ? "info" : ""; |
| | | } |
| | | |
| | | /** 本地模拟列表数据 */ |
| | | const allRows = ref([ |
| | | { |
| | | id: "1", |
| | | applicantId: "1001", |
| | | applicantName: "周明", |
| | | leaveDate: "2026-05-28", |
| | | handoverStatus: "in_progress", |
| | | handoverType: "resignation", |
| | | handoverPersonId: "1003", |
| | | handoverPersonName: "王强", |
| | | approvalResult: "pending", |
| | | approvalMode: "parallel", |
| | | approverIds: [], |
| | | approverNames: "", |
| | | }, |
| | | { |
| | | id: "2", |
| | | applicantId: "1002", |
| | | applicantName: "吴芳", |
| | | leaveDate: "2026-05-15", |
| | | handoverStatus: "completed", |
| | | handoverType: "transfer", |
| | | handoverPersonId: "1004", |
| | | handoverPersonName: "赵敏", |
| | | approvalResult: "approved", |
| | | approvalMode: "countersign", |
| | | approverIds: [], |
| | | approverNames: "张三、李四", |
| | | }, |
| | | { |
| | | id: "3", |
| | | applicantId: "1005", |
| | | applicantName: "陈浩", |
| | | leaveDate: "2026-04-20", |
| | | handoverStatus: "returned", |
| | | handoverType: "resignation", |
| | | handoverPersonId: "1006", |
| | | handoverPersonName: "刘洋", |
| | | approvalResult: "rejected", |
| | | approvalMode: "parallel", |
| | | approverIds: [], |
| | | approverNames: "李四", |
| | | }, |
| | | ]); |
| | | const allRows = ref([]); |
| | | |
| | | const searchForm = reactive({ |
| | | applicantId: "", |
| | |
| | | ...payload, |
| | | approvalResult: "pending", |
| | | }); |
| | | proxy?.$modal?.msgSuccess?.("新增成功(本地模拟)"); |
| | | proxy?.$modal?.msgSuccess?.("新增成功"); |
| | | } else { |
| | | const idx = allRows.value.findIndex((r) => r.id === form.id); |
| | | const prev = idx !== -1 ? allRows.value[idx] : {}; |
| | |
| | | ...payload, |
| | | }; |
| | | } |
| | | proxy?.$modal?.msgSuccess?.("保存成功(本地模拟)"); |
| | | proxy?.$modal?.msgSuccess?.("保存成功"); |
| | | } |
| | | formDialog.visible = false; |
| | | handleQuery(); |
| | |
| | | }; |
| | | } |
| | | |
| | | /** @deprecated 不再注入演示数据,初始列表为空 */ |
| | | export function createInitialMockNotices() { |
| | | return [ |
| | | { |
| | | id: "notice_1", |
| | | noticeNo: "NA202605100001", |
| | | title: "关于台风天气居家办公的紧急通知", |
| | | noticeType: "emergency", |
| | | priority: "urgent", |
| | | contentHtml: |
| | | "<p><strong>紧急通知</strong></p><p>受台风影响,明日(5月17日)全体员工居家办公,请各部门负责人做好工作安排与员工联络。</p>", |
| | | publishDate: "2026-05-16", |
| | | expireDate: "2026-05-20", |
| | | readScope: "all", |
| | | targetDeptIds: [], |
| | | requireReadConfirm: true, |
| | | publishStatus: "published", |
| | | publisherName: "行政部", |
| | | publishTime: "2026-05-16 08:30:00", |
| | | readCount: 128, |
| | | createTime: "2026-05-16 08:00:00", |
| | | updateTime: "2026-05-16 08:30:00", |
| | | }, |
| | | { |
| | | id: "notice_2", |
| | | noticeNo: "NA202605120002", |
| | | title: "2026年端午节放假安排公告", |
| | | noticeType: "employee", |
| | | priority: "high", |
| | | contentHtml: |
| | | "<p>根据国家法定节假日安排,端午节放假时间为 6月8日至6月10日,共3天。6月7日(周六)正常上班。</p>", |
| | | publishDate: "2026-05-12", |
| | | expireDate: "2026-06-15", |
| | | readScope: "all", |
| | | targetDeptIds: [], |
| | | requireReadConfirm: false, |
| | | publishStatus: "published", |
| | | publisherName: "人力资源部", |
| | | publishTime: "2026-05-12 10:00:00", |
| | | readCount: 256, |
| | | createTime: "2026-05-12 09:30:00", |
| | | updateTime: "2026-05-12 10:00:00", |
| | | }, |
| | | { |
| | | id: "notice_3", |
| | | noticeNo: "NA202605140003", |
| | | title: "办公区域消防演练通知", |
| | | noticeType: "company", |
| | | priority: "normal", |
| | | contentHtml: "<p>定于 5月25日 14:00 在总部大楼进行消防演练,请各部门提前安排人员参加。</p>", |
| | | publishDate: "2026-05-14", |
| | | expireDate: "2026-05-26", |
| | | readScope: "department", |
| | | targetDeptIds: ["101", "102", "103"], |
| | | requireReadConfirm: false, |
| | | publishStatus: "draft", |
| | | publisherName: "行政部", |
| | | publishTime: "", |
| | | readCount: 0, |
| | | createTime: "2026-05-14 15:00:00", |
| | | updateTime: "2026-05-14 15:00:00", |
| | | }, |
| | | ]; |
| | | return []; |
| | | } |
| | | |
| | | export function loadStoredNotices() { |
| | |
| | | READ_SCOPE_OPTIONS, |
| | | DEPT_OPTIONS, |
| | | createEmptyForm, |
| | | createInitialMockNotices, |
| | | loadStoredNotices, |
| | | saveStoredNotices, |
| | | nextNoticeNo, |
| | |
| | | |
| | | export function useNoticeAnnouncement() { |
| | | const stored = loadStoredNotices(); |
| | | const allRows = ref(stored?.length ? stored : createInitialMockNotices()); |
| | | const allRows = ref(stored?.length ? stored : []); |
| | | |
| | | const searchForm = reactive({ |
| | | keyword: "", |
| | |
| | | }, |
| | | }; |
| | | |
| | | /** 审批角色与模拟审批人 */ |
| | | export const MOCK_APPROVERS_BY_ROLE = { |
| | | direct_supervisor: { approverId: "mock_supervisor", approverName: "直属上级" }, |
| | | dept_manager: { approverId: "mock_manager", approverName: "部门经理" }, |
| | | cfo: { approverId: "mock_cfo", approverName: "财务总监" }, |
| | | compliance: { approverId: "mock_compliance", approverName: "合规审核" }, |
| | | /** 审批角色展示名(节点审批人须在前端选择) */ |
| | | export const APPROVAL_ROLE_LABELS = { |
| | | direct_supervisor: "直属上级", |
| | | dept_manager: "部门经理", |
| | | cfo: "财务总监", |
| | | compliance: "合规审核", |
| | | }; |
| | | |
| | | /** 按金额预设审批链 */ |
| | |
| | | |
| | | export function buildAutoApprovalFlow(amount, expenseCategory) { |
| | | const roles = resolveApprovalRoles(amount, expenseCategory); |
| | | return roles.map((role, i) => { |
| | | const mock = MOCK_APPROVERS_BY_ROLE[role] || { approverId: `mock_${role}`, approverName: role }; |
| | | return { |
| | | approverId: mock.approverId, |
| | | approverName: mock.approverName, |
| | | return roles.map((role, i) => ({ |
| | | approverId: null, |
| | | approverName: APPROVAL_ROLE_LABELS[role] || role, |
| | | roleKey: role, |
| | | sortOrder: i + 1, |
| | | nodeOrder: i + 1, |
| | | nodeStatus: i === 0 ? "process" : "wait", |
| | | approveOpinion: "", |
| | | approveTime: "", |
| | | }; |
| | | }); |
| | | })); |
| | | } |
| | | |
| | | export function getApprovalRuleHint(amount, expenseCategory) { |
| | |
| | | const rule = APPROVAL_AMOUNT_RULES.find((r) => amt <= r.maxAmount) || APPROVAL_AMOUNT_RULES[APPROVAL_AMOUNT_RULES.length - 1]; |
| | | const extra = CATEGORY_EXTRA_APPROVAL[expenseCategory] || []; |
| | | const extraText = extra.length |
| | | ? `;${expenseCategoryLabel(expenseCategory)}类另需:${extra.map((r) => MOCK_APPROVERS_BY_ROLE[r]?.approverName || r).join("、")}` |
| | | ? `;${expenseCategoryLabel(expenseCategory)}类另需:${extra.map((r) => APPROVAL_ROLE_LABELS[r] || r).join("、")}` |
| | | : ""; |
| | | return `${rule.description}${extraText}`; |
| | | } |
| | |
| | | export function useCostReimburse() { |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | const allRows = ref([ |
| | | { |
| | | id: "1", |
| | | reimburseNo: "CR202605100001", |
| | | applicantId: "mock_1", |
| | | employeeNo: "zhangsan", |
| | | employeeName: "张三", |
| | | applicantNo: "zhangsan", |
| | | applicantName: "张三", |
| | | expenseCategory: "office_procurement", |
| | | reimburseReason: "采购打印机硒鼓、A4纸等办公耗材。", |
| | | applyAmount: 680, |
| | | payee: "张三", |
| | | payeeAccount: "6222 **** **** 1234", |
| | | bankBranch: "中国工商银行杭州西湖支行", |
| | | expenseDetails: [ |
| | | { id: "d1", invoiceDate: "2026-05-08", expenseSubject: "office_supply", amount: 380, description: "A4复印纸" }, |
| | | { id: "d2", invoiceDate: "2026-05-08", expenseSubject: "office_supply", amount: 300, description: "硒鼓" }, |
| | | ], |
| | | attachmentList: [{ name: "采购发票.pdf", url: "/mock/invoice1.pdf" }], |
| | | approvalFlowNodes: demoFlowNodes(680, "office_procurement"), |
| | | currentNodeIndex: 0, |
| | | approvalResult: "pending", |
| | | rejectReason: "", |
| | | approvalRecords: [], |
| | | applyTime: "2026-05-10 09:15:00", |
| | | createTime: "2026-05-10 09:15:00", |
| | | deptId: "101", |
| | | deptName: "行政部", |
| | | }, |
| | | { |
| | | id: "2", |
| | | reimburseNo: "CR202605080002", |
| | | applicantId: "mock_2", |
| | | employeeNo: "lisi", |
| | | employeeName: "李四", |
| | | applicantNo: "lisi", |
| | | applicantName: "李四", |
| | | expenseCategory: "business_entertainment", |
| | | reimburseReason: "接待重点客户商务宴请。", |
| | | applyAmount: 3200, |
| | | payee: "李四", |
| | | payeeAccount: "6217 **** **** 5678", |
| | | bankBranch: "招商银行武汉光谷支行", |
| | | expenseDetails: [ |
| | | { id: "d3", invoiceDate: "2026-05-06", expenseSubject: "entertainment", amount: 3200, description: "客户宴请" }, |
| | | ], |
| | | attachmentList: [], |
| | | approvalFlowNodes: demoFlowNodes(3200, "business_entertainment").map((n, i) => ({ |
| | | ...n, |
| | | nodeStatus: i === 0 ? "error" : "wait", |
| | | approveOpinion: i === 0 ? "发票模糊需重传" : "", |
| | | approveTime: i === 0 ? "2026-05-09 14:20:00" : "", |
| | | })), |
| | | currentNodeIndex: 0, |
| | | approvalResult: "rejected", |
| | | rejectReason: "发票模糊需重传", |
| | | approvalRecords: [ |
| | | { operatorName: "直属上级", result: "rejected", opinion: "发票模糊需重传", time: "2026-05-09 14:20:00" }, |
| | | ], |
| | | applyTime: "2026-05-07 16:30:00", |
| | | createTime: "2026-05-07 16:30:00", |
| | | deptId: "102", |
| | | deptName: "销售部", |
| | | }, |
| | | { |
| | | id: "3", |
| | | reimburseNo: "CR202605050003", |
| | | applicantId: "mock_3", |
| | | employeeNo: "wangwu", |
| | | employeeName: "王五", |
| | | applicantNo: "wangwu", |
| | | applicantName: "王五", |
| | | expenseCategory: "communication", |
| | | reimburseReason: "5月因公话费报销。", |
| | | applyAmount: 198, |
| | | payee: "王五", |
| | | payeeAccount: "6228 **** **** 9012", |
| | | bankBranch: "中国建设银行成都高新支行", |
| | | expenseDetails: [ |
| | | { id: "d4", invoiceDate: "2026-05-05", expenseSubject: "phone", amount: 198, description: "话费账单" }, |
| | | ], |
| | | attachmentList: [{ name: "话费账单.jpg", url: "/mock/phone.jpg" }], |
| | | approvalFlowNodes: demoFlowNodes(198, "communication").map((n) => ({ |
| | | ...n, |
| | | nodeStatus: "finish", |
| | | approveOpinion: "同意", |
| | | approveTime: "2026-05-06 10:00:00", |
| | | })), |
| | | currentNodeIndex: 0, |
| | | approvalResult: "approved", |
| | | rejectReason: "", |
| | | approvalRecords: [{ operatorName: "直属上级", result: "approved", opinion: "同意", time: "2026-05-06 10:00:00" }], |
| | | applyTime: "2026-05-05 11:00:00", |
| | | createTime: "2026-05-05 11:00:00", |
| | | deptId: "103", |
| | | deptName: "技术部", |
| | | }, |
| | | ]); |
| | | const allRows = ref([]); |
| | | |
| | | const searchForm = reactive({ |
| | | applicantKeyword: "", |
| | |
| | | applyTime: now, |
| | | createTime: now, |
| | | }); |
| | | proxy?.$modal?.msgSuccess?.("提交成功,已进入审批(本地模拟)"); |
| | | proxy?.$modal?.msgSuccess?.("提交成功"); |
| | | } else { |
| | | const idx = allRows.value.findIndex((r) => r.id === form.id); |
| | | if (idx !== -1) { |
| | |
| | | createTime: prev.createTime, |
| | | }; |
| | | } |
| | | proxy?.$modal?.msgSuccess?.("保存成功(本地模拟)"); |
| | | proxy?.$modal?.msgSuccess?.("保存成功"); |
| | | } |
| | | formDialog.visible = false; |
| | | handleQuery(); |
| | |
| | | return { nodes, currentNodeIndex: idx, approvalResult: "rejected", rejectReason: opinion || "驳回" }; |
| | | } |
| | | |
| | | /** 模拟部门预算(与预算系统联动占位) */ |
| | | /** 部门预算(对接预算系统前返回空) */ |
| | | export function mockDeptBudget(deptId) { |
| | | const id = String(deptId || "default"); |
| | | let s = 0; |
| | | for (let i = 0; i < id.length; i++) s += id.charCodeAt(i); |
| | | const total = 500000 + (s % 200) * 1000; |
| | | const used = (s % 80) * 3500; |
| | | return { |
| | | deptId: id, |
| | | totalBudget: total, |
| | | usedAmount: used, |
| | | remainingAmount: Math.max(0, total - used), |
| | | }; |
| | | if (!deptId) return null; |
| | | return null; |
| | | } |
| | | |
| | | export function normalizeImportedRow(raw, idx) { |
| | |
| | | return String(u.status) === "0"; |
| | | } |
| | | |
| | | function demoFlowNodes(names = ["部门主管", "财务审核"]) { |
| | | return names.map((name, i) => ({ |
| | | approverId: `mock_${i + 1}`, |
| | | approverName: name, |
| | | sortOrder: i + 1, |
| | | nodeOrder: i + 1, |
| | | nodeStatus: i === 0 ? "process" : "wait", |
| | | approveOpinion: "", |
| | | approveTime: "", |
| | | })); |
| | | } |
| | | |
| | | export function useTravelReimburse() { |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | const allRows = ref([ |
| | | { |
| | | id: "1", |
| | | reimburseNo: "TR202605090001", |
| | | applicantId: "mock_1", |
| | | employeeNo: "zhangsan", |
| | | employeeName: "张三", |
| | | applicantNo: "zhangsan", |
| | | applicantName: "张三", |
| | | reimburseReason: "赴上海参加行业展会及客户拜访。", |
| | | travelStartTime: "2026-05-10 08:00:00", |
| | | travelEndTime: "2026-05-13 18:00:00", |
| | | travelDays: 4, |
| | | departurePlace: "杭州", |
| | | destination: "上海", |
| | | hotelStandard: 600, |
| | | hotelDays: 3, |
| | | livingSubsidy: 400, |
| | | applyAmount: 4580, |
| | | payee: "张三", |
| | | expenseDetails: [ |
| | | { id: "d1", invoiceDate: "2026-05-10", expenseSubject: "transport", amount: 553, description: "高铁往返" }, |
| | | { id: "d2", invoiceDate: "2026-05-11", expenseSubject: "hotel", amount: 1680, description: "酒店住宿" }, |
| | | ], |
| | | attachmentList: [{ name: "高铁票.pdf", url: "/mock/invoice1.pdf" }], |
| | | invoiceAttachments: [{ name: "高铁票.pdf", url: "/mock/invoice1.pdf" }], |
| | | approvalFlowNodes: demoFlowNodes(), |
| | | currentNodeIndex: 0, |
| | | approvalResult: "pending", |
| | | rejectReason: "", |
| | | approvalRecords: [], |
| | | needSpecialApproval: false, |
| | | deptId: "101", |
| | | deptName: "销售部", |
| | | travelTier: "tier1", |
| | | createTime: "2026-05-09 10:20:00", |
| | | }, |
| | | { |
| | | id: "2", |
| | | reimburseNo: "TR202605080002", |
| | | applicantId: "mock_2", |
| | | employeeNo: "lisi", |
| | | employeeName: "李四", |
| | | applicantNo: "lisi", |
| | | applicantName: "李四", |
| | | reimburseReason: "成都分公司技术支持。", |
| | | travelStartTime: "2026-05-05 09:00:00", |
| | | travelEndTime: "2026-05-07 17:00:00", |
| | | travelDays: 3, |
| | | departurePlace: "武汉", |
| | | destination: "成都", |
| | | hotelStandard: 450, |
| | | hotelDays: 2, |
| | | livingSubsidy: 240, |
| | | applyAmount: 2100, |
| | | payee: "李四", |
| | | expenseDetails: [{ id: "d3", invoiceDate: "2026-05-06", expenseSubject: "meal", amount: 180, description: "工作餐" }], |
| | | attachmentList: [], |
| | | invoiceAttachments: [], |
| | | approvalFlowNodes: demoFlowNodes().map((n, i) => ({ ...n, nodeStatus: "finish", approveOpinion: "同意", approveTime: "2026-05-08 11:00:00" })), |
| | | currentNodeIndex: 1, |
| | | approvalResult: "approved", |
| | | rejectReason: "", |
| | | approvalRecords: [{ operatorName: "部门主管", result: "approved", opinion: "同意", time: "2026-05-08 10:00:00" }], |
| | | needSpecialApproval: false, |
| | | deptId: "102", |
| | | deptName: "技术部", |
| | | travelTier: "tier2", |
| | | createTime: "2026-05-07 16:00:00", |
| | | }, |
| | | ]); |
| | | const allRows = ref([]); |
| | | |
| | | const searchForm = reactive({ applicantKeyword: "", travelStartFrom: "", travelEndTo: "" }); |
| | | const tableLoading = ref(false); |
| | |
| | | approvalRecords: [], |
| | | createTime: dayjs().format("YYYY-MM-DD HH:mm:ss"), |
| | | }); |
| | | proxy?.$modal?.msgSuccess?.("提交成功,已进入审批(本地模拟)"); |
| | | proxy?.$modal?.msgSuccess?.("提交成功"); |
| | | } else { |
| | | const idx = allRows.value.findIndex((r) => r.id === form.id); |
| | | if (idx !== -1) { |
| | |
| | | createTime: prev.createTime, |
| | | }; |
| | | } |
| | | proxy?.$modal?.msgSuccess?.("保存成功(本地模拟)"); |
| | | proxy?.$modal?.msgSuccess?.("保存成功"); |
| | | } |
| | | formDialog.visible = false; |
| | | handleQuery(); |