383 lines
12 KiB
Vue
383 lines
12 KiB
Vue
<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>
|