Files
OnboardXFront/src/views/biz/onboarding/entry-config/components/VariableDefinitionPanel.vue
T
2026-09-10 16:43:23 +08:00

383 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="variable-panel">
<div class="toolbar">
<el-input
v-model="query.keyword"
placeholder="搜索变量编码 / 变量名称"
clearable
style="width: 240px"
@keyup.enter="loadData"
@clear="loadData"
/>
<el-select
v-model="query.variableCategory"
placeholder="变量分类"
clearable
style="width: 180px"
@change="loadData"
>
<el-option label="全部变量分类" value="" />
<el-option label="员工登记表变量 (employee)" value="employee" />
<el-option label="人事预设参数 (hr)" value="hr" />
<el-option label="公司信息变量 (company)" value="company" />
<el-option label="系统预置变量 (system)" value="system" />
</el-select>
<el-button type="primary" icon="Search" @click="loadData">查询</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
<div class="spacer"></div>
<el-button v-auth="'onboardingEntryConfigAdd'" type="primary" icon="Plus" @click="openCreate">新增自定义变量</el-button>
</div>
<el-alert
title="变量字典库定义了签署材料 DOCX 中所有合法的占位符(如 {{employee.EmployeeName}})。上传 Word 时系统会自动校验占位符是否在此字典中,防止拼写错误。"
type="info"
show-icon
:closable="false"
class="tip-alert"
/>
<div class="table-container">
<el-table :data="tableData" border stripe height="100%" v-loading="loading">
<el-table-column prop="variableCode" label="变量全编码 (DOCX占位符)" min-width="260">
<template #default="{ row }">
<div class="code-col">
<el-tag :type="categoryTag(row.variableCategory)" effect="light" size="small" class="tag-badge">
{{ categoryText(row.variableCategory) }}
</el-tag>
<code class="code-font">{{ formatPlaceholder(row.variableCode) }}</code>
</div>
</template>
</el-table-column>
<el-table-column prop="variableName" label="中文显示名称" min-width="160" />
<el-table-column prop="sortCode" label="排序" width="80" align="center" sortable />
<el-table-column prop="dataType" label="数据类型" width="100" align="center">
<template #default="{ row }">
<el-tag size="small" type="info">{{ row.dataType || 'string' }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="isBuiltIn" label="内置标准" width="120" align="center">
<template #default="{ row }">
<el-tag size="small" :type="row.isBuiltIn ? 'success' : 'warning'">
{{ row.isBuiltIn ? '入职登记表' : '自定义' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="required" label="必填" width="80" align="center">
<template #default="{ row }">
<el-tag size="small" :type="row.required ? 'danger' : 'info'">
{{ row.required ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="remark" label="说明备注" min-width="140" show-overflow-tooltip />
<el-table-column label="操作" width="160" align="center" fixed="right">
<template #default="{ row }">
<div style="display: flex; align-items: center; justify-content: center; gap: 8px; white-space: nowrap;">
<el-button
v-auth="'onboardingEntryConfigEdit'"
link
type="primary"
icon="Edit"
:disabled="!canManage(row)"
:title="disabledReason(row)"
@click="openEdit(row)"
>编辑</el-button>
<el-button
v-auth="'onboardingEntryConfigDelete'"
v-if="!row.isBuiltIn"
link
type="danger"
icon="Delete"
:disabled="!canManage(row)"
:title="disabledReason(row)"
@click="remove(row)"
>
删除
</el-button>
</div>
</template>
</el-table-column>
</el-table>
</div>
<div class="pagination-box">
<el-pagination
v-model:current-page="query.pageNum"
v-model:page-size="query.pageSize"
:total="total"
:page-sizes="[20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
@size-change="loadData"
@current-change="loadData"
/>
</div>
<!-- 弹窗 -->
<el-dialog v-model="visible" :title="form.id ? '编辑变量' : '新增自定义变量'" width="560px" destroy-on-close>
<el-form ref="formRef" :model="form" :rules="rules" label-width="110px">
<el-form-item label="变量分类" prop="variableCategory" required>
<el-select v-model="form.variableCategory" :disabled="form.isBuiltIn" style="width: 100%">
<el-option label="员工变量 (employee.)" value="employee" />
<el-option label="人事预设参数 (hr.)" value="hr" />
<el-option label="公司信息 (company.)" value="company" />
<el-option label="系统变量 (system.)" value="system" />
</el-select>
</el-form-item>
<el-form-item label="变量全编码" prop="variableCode" required>
<el-input
v-model="form.variableCode"
placeholder="如 employee.WeChat 或 hr.SpecialSubsidy"
:disabled="Boolean(form.id)"
/>
<div class="form-tip">规范格式:分类.变量名,例如 employee.CarNo</div>
</el-form-item>
<el-form-item label="中文名称" prop="variableName" required>
<el-input v-model="form.variableName" placeholder="如 微信号、特殊津贴" />
</el-form-item>
<el-form-item label="数据类型">
<el-select v-model="form.dataType" style="width: 100%">
<el-option label="文本 (string)" value="string" />
<el-option label="数字 / 金额 (number)" value="number" />
<el-option label="日期 (date)" value="date" />
</el-select>
</el-form-item>
<el-form-item label="默认值">
<el-input v-model="form.defaultValue" placeholder="未填写时的默认值" />
</el-form-item>
<el-form-item label="是否必填">
<el-switch v-model="form.required" />
</el-form-item>
<el-form-item label="排序码">
<el-input-number v-model="form.sortCode" :min="1" :max="9999" />
</el-form-item>
<el-form-item label="说明备注">
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="变量业务说明" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="save">保存</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from "vue";
import { ElMessage, ElMessageBox, FormInstance, FormRules } from "element-plus";
import { onboardingTaskApi } from "@/api/modules/biz/onboarding";
import { isMessageBoxConfirmed } from "@/utils/messageBox";
import { useOnboardingPermission } from "@/utils/onboardingPermission";
const { canManage, disabledReason } = useOnboardingPermission("onboardingEntryConfigManageAll");
const loading = ref(false);
const saving = ref(false);
const visible = ref(false);
const tableData = ref<any[]>([]);
const total = ref(0);
const formRef = ref<FormInstance>();
const query = reactive({
keyword: "",
variableCategory: "",
pageNum: 1,
pageSize: 50
});
const form = reactive<any>({
id: undefined,
variableCode: "",
variableName: "",
variableCategory: "employee",
dataType: "string",
defaultValue: "",
isBuiltIn: false,
required: false,
sortCode: 100,
remark: ""
});
const rules: FormRules = {
variableCode: [{ required: true, message: "请输入变量编码", trigger: "blur" }],
variableName: [{ required: true, message: "请输入中文显示名称", trigger: "blur" }],
variableCategory: [{ required: true, message: "请选择分类", trigger: "change" }]
};
function categoryTag(cat: string) {
switch (cat) {
case "employee": return "primary";
case "hr": return "warning";
case "company": return "success";
default: return "info";
}
}
function categoryText(cat: string) {
switch (cat) {
case "employee": return "员工变量";
case "hr": return "人事参数";
case "company": return "公司信息";
default: return "系统变量";
}
}
function formatPlaceholder(code?: string) {
return code ? `{{${code}}}` : "";
}
async function loadData() {
loading.value = true;
try {
const res = await onboardingTaskApi.variableDefinitionPage(query);
tableData.value = res.data?.list || [];
total.value = res.data?.total || 0;
} finally {
loading.value = false;
}
}
function resetQuery() {
query.keyword = "";
query.variableCategory = "";
query.pageNum = 1;
loadData();
}
function openCreate() {
Object.assign(form, {
id: undefined,
variableCode: "",
variableName: "",
variableCategory: "employee",
dataType: "string",
defaultValue: "",
isBuiltIn: false,
required: false,
sortCode: 100,
remark: ""
});
visible.value = true;
}
function openEdit(row: any) {
Object.assign(form, {
id: row.id,
variableCode: row.variableCode,
variableName: row.variableName,
variableCategory: row.variableCategory,
dataType: row.dataType || "string",
defaultValue: row.defaultValue || "",
isBuiltIn: row.isBuiltIn || false,
required: row.required || false,
sortCode: row.sortCode || 100,
remark: row.remark || ""
});
visible.value = true;
}
async function save() {
await formRef.value?.validate();
saving.value = true;
try {
if (form.id) {
await onboardingTaskApi.editVariableDefinition(form);
ElMessage.success("变量修改成功");
} else {
await onboardingTaskApi.addVariableDefinition(form);
ElMessage.success("自定义变量添加成功");
}
visible.value = false;
loadData();
} finally {
saving.value = false;
}
}
async function remove(row: any) {
const confirmed = await isMessageBoxConfirmed(
ElMessageBox.confirm(`确认删除自定义变量“${row.variableCode}”?`, "删除确认", { type: "warning" })
);
if (!confirmed) return;
await onboardingTaskApi.deleteVariableDefinitions([row.id]);
ElMessage.success("删除成功");
loadData();
}
onMounted(loadData);
</script>
<style scoped>
.variable-panel {
height: 100%;
display: flex;
flex-direction: column;
box-sizing: border-box;
overflow: hidden;
}
.toolbar {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 10px;
flex-shrink: 0;
}
.spacer {
flex: 1;
}
.tip-alert {
margin-bottom: 10px;
flex-shrink: 0;
}
.table-container {
flex: 1;
height: 0;
overflow: hidden;
}
.code-col {
display: inline-flex;
align-items: center;
gap: 6px;
white-space: nowrap;
}
.tag-badge {
flex-shrink: 0;
}
.code-font {
font-family: Consolas, Monaco, monospace;
font-weight: 600;
color: #b45309;
background: #fef3c7;
padding: 2px 6px;
border-radius: 4px;
white-space: nowrap;
}
.pagination-box {
display: flex;
justify-content: flex-end;
margin-top: 12px;
flex-shrink: 0;
}
.form-tip {
font-size: 12px;
color: #94a3b8;
margin-top: 4px;
}
</style>