yyb
16 小时以前 efc0c3a697969503634138d7881543f4099b81ca
审批模板导入只能从已有模板导入
已修改19个文件
833 ■■■■ 文件已修改
src/views/officeProcessAutomation/ApproveManage/approve-list/approveListConstants.js 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/ApproveManage/approve-list/useApproveList.js 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/ApproveManage/approve-template/components/FormConfigEditor.vue 104 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/ApproveManage/approve-template/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/ApproveManage/approve-template/useApproveTemplate.js 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/AttendManage/leave-apply/index.vue 56 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/AttendManage/overtime-apply/index.vue 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/EnterpriseNews/news-manage/enterpriseNewsUtils.js 179 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/EnterpriseNews/news-manage/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/EnterpriseNews/news-manage/useEnterpriseNews.js 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/HrManage/regular-apply/index.vue 34 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/HrManage/transfer-apply/index.vue 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/HrManage/work-handover/index.vue 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/NoticeAnnouncement/notice-manage/noticeAnnouncementUtils.js 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/NoticeAnnouncement/notice-manage/useNoticeAnnouncement.js 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/ReimburseManage/cost-reimburse/costReimburseUtils.js 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/ReimburseManage/cost-reimburse/useCostReimburse.js 104 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/ReimburseManage/travel-reimburse/travelReimburseUtils.js 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/ReimburseManage/travel-reimburse/useTravelReimburse.js 88 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/officeProcessAutomation/ApproveManage/approve-list/approveListConstants.js
@@ -35,16 +35,6 @@
  { 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);
src/views/officeProcessAutomation/ApproveManage/approve-list/useApproveList.js
@@ -34,7 +34,6 @@
  buildApproveInstanceDto,
  buildEditFormFromInstanceRow,
  buildInstanceDto,
  clearLegacyApproveListStorage,
  createEmptySubmitForm,
  mapInstanceFromApi,
  mapSubmitTemplateCard,
@@ -43,7 +42,6 @@
} from "./approveListConstants.js";
export function useApproveList() {
  clearLegacyApproveListStorage();
  const userStore = useUserStore();
  const tableData = ref([]);
src/views/officeProcessAutomation/ApproveManage/approve-template/components/FormConfigEditor.vue
@@ -21,12 +21,20 @@
          </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>
@@ -38,7 +46,7 @@
      <el-empty
        v-if="!inner.fields.length"
        class="fce-empty"
        description="暂无填报项,可添加或从预设快速导入"
        description="暂无填报项,可添加或从已有审批模板导入"
        :image-size="72"
      />
@@ -288,14 +296,25 @@
<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,
@@ -306,6 +325,8 @@
const props = defineProps({
  modelValue: { type: Object, default: () => createEmptyFormConfigData() },
  /** 编辑当前模板时排除自身,避免从自己导入 */
  excludeTemplateId: { type: [String, Number], default: null },
});
const emit = defineEmits(["update:modelValue"]);
@@ -313,6 +334,9 @@
const inner = reactive(createEmptyFormConfigData());
const { loading: optionSourceLoading, ensureForFields, getOptions } = useSelectOptionSources();
const templateImportOptions = ref([]);
const templateImportLoading = ref(false);
function typeLabel(type) {
  return formFieldTypeLabel(type);
@@ -435,10 +459,66 @@
  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>
@@ -496,6 +576,10 @@
  align-items: center;
  gap: 8px;
}
.import-tag {
  margin-left: 8px;
  vertical-align: middle;
}
.fce-empty {
  padding: 24px 0;
src/views/officeProcessAutomation/ApproveManage/approve-template/index.vue
@@ -178,7 +178,7 @@
        <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>
src/views/officeProcessAutomation/ApproveManage/approve-template/useApproveTemplate.js
@@ -23,8 +23,6 @@
} 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: "自定义" },
@@ -37,17 +35,7 @@
  );
}
function clearLegacyStorage() {
  try {
    localStorage.removeItem(LEGACY_STORAGE_KEY);
  } catch {
    /* ignore */
  }
}
export function useApproveTemplate() {
  clearLegacyStorage();
  const templateTypeOptions = ref([...FALLBACK_TEMPLATE_TYPE_OPTIONS]);
  function templateTypeLabel(type) {
src/views/officeProcessAutomation/AttendManage/leave-apply/index.vue
@@ -498,13 +498,9 @@
  );
}
/** 本地模拟:根据用户生成稳定「假期余额」占位 */
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) {
@@ -547,45 +543,7 @@
  }
}
/** 本地模拟列表数据 */
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: "",
@@ -789,7 +747,7 @@
    window.open(url, "_blank");
    return;
  }
  proxy?.$modal?.msgSuccess?.(`已模拟下载:${row.name}`);
  proxy?.$modal?.msgWarning?.("暂无下载地址");
}
async function openFormDialog(mode, row) {
@@ -874,7 +832,7 @@
      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) {
@@ -887,7 +845,7 @@
        createTime: prev.createTime ?? dayjs().format("YYYY-MM-DD HH:mm:ss"),
      };
    }
    proxy?.$modal?.msgSuccess?.("保存成功(本地模拟)");
    proxy?.$modal?.msgSuccess?.("保存成功");
  }
  formDialog.visible = false;
  handleQuery();
src/views/officeProcessAutomation/AttendManage/overtime-apply/index.vue
@@ -404,40 +404,7 @@
  }
}
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: "",
@@ -652,7 +619,7 @@
    window.open(url, "_blank");
    return;
  }
  proxy?.$modal?.msgSuccess?.(`已模拟下载:${row.name}`);
  proxy?.$modal?.msgWarning?.("暂无下载地址");
}
function handleExport() {
@@ -814,7 +781,7 @@
      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) {
@@ -827,7 +794,7 @@
        createTime: prev.createTime ?? dayjs().format("YYYY-MM-DD HH:mm:ss"),
      };
    }
    proxy?.$modal?.msgSuccess?.("保存成功(本地模拟)");
    proxy?.$modal?.msgSuccess?.("保存成功");
  }
  formDialog.visible = false;
  handleQuery();
src/views/officeProcessAutomation/EnterpriseNews/news-manage/enterpriseNewsUtils.js
@@ -42,17 +42,8 @@
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: "研发部" },
@@ -124,13 +115,12 @@
}
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: "",
  }));
}
@@ -149,158 +139,9 @@
  };
}
/** @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() {
src/views/officeProcessAutomation/EnterpriseNews/news-manage/index.vue
@@ -339,7 +339,7 @@
  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 = "";
}
src/views/officeProcessAutomation/EnterpriseNews/news-manage/useEnterpriseNews.js
@@ -10,7 +10,6 @@
  PUBLISH_ROLE_OPTIONS,
  DEPT_OPTIONS,
  createEmptyForm,
  createInitialMockNews,
  loadStoredNews,
  saveStoredNews,
  getUnreadEmployees,
@@ -24,7 +23,7 @@
export function useEnterpriseNews() {
  const stored = loadStoredNews();
  const allRows = ref(stored?.length ? stored : createInitialMockNews());
  const allRows = ref(stored?.length ? stored : []);
  const searchForm = reactive({
    keyword: "",
@@ -361,7 +360,7 @@
    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 || [];
@@ -377,7 +376,7 @@
    }
  }
  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);
src/views/officeProcessAutomation/HrManage/regular-apply/index.vue
@@ -424,33 +424,7 @@
  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: "",
@@ -609,7 +583,7 @@
    window.open(url, "_blank");
    return;
  }
  proxy?.$modal?.msgSuccess?.(`已模拟下载:${row.name}`);
  proxy?.$modal?.msgWarning?.("暂无下载地址");
}
function openAddWithTemplate() {
@@ -679,7 +653,7 @@
  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) {
@@ -691,7 +665,7 @@
        approvalResult: prev.approvalResult ?? "pending",
      };
    }
    proxy?.$modal?.msgSuccess?.("保存成功(本地模拟)");
    proxy?.$modal?.msgSuccess?.("保存成功");
  }
  formDialog.visible = false;
  handleQuery();
src/views/officeProcessAutomation/HrManage/transfer-apply/index.vue
@@ -517,37 +517,7 @@
  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: "",
@@ -740,7 +710,7 @@
      ...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] : {};
@@ -751,7 +721,7 @@
        ...payload,
      };
    }
    proxy?.$modal?.msgSuccess?.("保存成功(本地模拟)");
    proxy?.$modal?.msgSuccess?.("保存成功");
  }
  formDialog.visible = false;
  handleQuery();
src/views/officeProcessAutomation/HrManage/work-handover/index.vue
@@ -502,51 +502,7 @@
  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: "",
@@ -759,7 +715,7 @@
      ...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] : {};
@@ -770,7 +726,7 @@
        ...payload,
      };
    }
    proxy?.$modal?.msgSuccess?.("保存成功(本地模拟)");
    proxy?.$modal?.msgSuccess?.("保存成功");
  }
  formDialog.visible = false;
  handleQuery();
src/views/officeProcessAutomation/NoticeAnnouncement/notice-manage/noticeAnnouncementUtils.js
@@ -90,68 +90,9 @@
  };
}
/** @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() {
src/views/officeProcessAutomation/NoticeAnnouncement/notice-manage/useNoticeAnnouncement.js
@@ -9,7 +9,6 @@
  READ_SCOPE_OPTIONS,
  DEPT_OPTIONS,
  createEmptyForm,
  createInitialMockNotices,
  loadStoredNotices,
  saveStoredNotices,
  nextNoticeNo,
@@ -22,7 +21,7 @@
export function useNoticeAnnouncement() {
  const stored = loadStoredNotices();
  const allRows = ref(stored?.length ? stored : createInitialMockNotices());
  const allRows = ref(stored?.length ? stored : []);
  const searchForm = reactive({
    keyword: "",
src/views/officeProcessAutomation/ReimburseManage/cost-reimburse/costReimburseUtils.js
@@ -65,12 +65,12 @@
  },
};
/** 审批角色与模拟审批人 */
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: "合规审核",
};
/** 按金额预设审批链 */
@@ -151,19 +151,16 @@
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) {
@@ -171,7 +168,7 @@
  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}`;
}
src/views/officeProcessAutomation/ReimburseManage/cost-reimburse/useCostReimburse.js
@@ -42,105 +42,7 @@
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: "",
@@ -508,7 +410,7 @@
        applyTime: now,
        createTime: now,
      });
      proxy?.$modal?.msgSuccess?.("提交成功,已进入审批(本地模拟)");
      proxy?.$modal?.msgSuccess?.("提交成功");
    } else {
      const idx = allRows.value.findIndex((r) => r.id === form.id);
      if (idx !== -1) {
@@ -525,7 +427,7 @@
          createTime: prev.createTime,
        };
      }
      proxy?.$modal?.msgSuccess?.("保存成功(本地模拟)");
      proxy?.$modal?.msgSuccess?.("保存成功");
    }
    formDialog.visible = false;
    handleQuery();
src/views/officeProcessAutomation/ReimburseManage/travel-reimburse/travelReimburseUtils.js
@@ -137,19 +137,10 @@
  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) {
src/views/officeProcessAutomation/ReimburseManage/travel-reimburse/useTravelReimburse.js
@@ -32,92 +32,10 @@
  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);
@@ -534,7 +452,7 @@
        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) {
@@ -549,7 +467,7 @@
          createTime: prev.createTime,
        };
      }
      proxy?.$modal?.msgSuccess?.("保存成功(本地模拟)");
      proxy?.$modal?.msgSuccess?.("保存成功");
    }
    formDialog.visible = false;
    handleQuery();