系统配置接口完成

This commit is contained in:
2026-08-28 15:04:44 +08:00
parent 6651cb6749
commit a473951901
4 changed files with 332 additions and 325 deletions
+134 -123
View File
@@ -4,26 +4,21 @@ import { useAppStore } from '@/store/modules/app';
import { Icon } from '@iconify/vue'
import { useThemeStore } from '@/store/modules/theme';
import { getColorWithOpacity, tagTextColor, tagBgColor, tagBorderColor } from '@/utils/common';
import { systemConfigApi, addSystemConfigApi, editSystemConfigApi, deleteSystemConfigApi } from '@/service/api/system';
import { systemConfigApi, addSystemConfigApi, editSystemConfigApi, deleteSystemConfigApi, updateConfigApi } from '@/service/api/system';
const appStore = useAppStore();
const themeStore = useThemeStore();
const gap = computed(() => (appStore.isMobile ? 0 : 16));
// AI 辅助功能配置
const aiEnabled = ref<boolean>(true);
const aiConfig = ref<{address:string, mode:number|null, key:string,timeout:null|number}>({
address:'',
mode:null,
key:'',
timeout:null,
})
const aiTested = ref<string>("none");
// *********************AI 辅助功能配置*********************//
const aiConfig = ref<any[]>([])
const switchLoading = ref<boolean>(false);
const modeOptions = ref<{label:string, value:number}[]>([
{label:'kimi-k2(企业私有化部署)',value:1}
])
const testing = ref<boolean>(false);
const showLog = ref<boolean>(false);
const AI_LOGS = [
{ scene: "AI-01 变更识别", user: "张工艺", change: "MOC-2026-R01-0041", time: "08-24 09:12", cost: "1.8s", adopted: "修改后采纳" },
{ scene: "AI-03 风险预分析", user: "张工艺", change: "MOC-2026-R01-0041", time: "08-24 09:15", cost: "2.4s", adopted: "采纳" },
@@ -32,6 +27,34 @@ const AI_LOGS = [
{ scene: "AI-09 关闭辅助判定", user: "系统管理员", change: "MOC-2026-R01-0035", time: "08-23 15:02", cost: "1.5s", adopted: "采纳" },
];
// AI 辅助功能切换
const toggle = async (v: boolean, item:any) => {
switchLoading.value = true;
const {error} = await updateConfigApi({items:[{
id:item.id,
label:item.label,
remark:item.remark,
value:v ? '1' : '0'
}]});
if(!error){
item.value = v ? '1' : '0';
window.$message?.success(v ? "AI 辅助功能已启用" : "AI 辅助功能已关闭");
}
switchLoading.value = false;
}
// 测试连接
const testConn = () => {
testing.value = true;
setTimeout(() => {
testing.value = false;
window.$message?.success(`连接测试通过:${aiConfig.value.mode} 响应正常(耗时 0.8s`);
}, 900);
}
// 保存ai配置
const saveAiCfg = () => {
window.$message?.success("AI 接口配置已保存(配置变更留痕,立即生效)");
}
// *********************变更类型*********************//
const typeRef = ref<any>(null);
@@ -212,27 +235,24 @@ const levelSave = async (e:MouseEvent) => {
// *********************变更编号规则*********************//
const ruleLoading = ref<boolean>(false);
const previewNumber = computed(() => {
let prefix = ruleConfig.value.find((item:any) => item.item_key === 'prefix')?.value || '';
let year = '';
let code = ruleConfig.value.find((item:any) => item.item_key === 'unit_code')?.value || '';
let digit = '';
const currentYear = new Date().getFullYear();
if (ruleInfo.value.year === 'yyyy') {
if (ruleConfig.value.find((item:any) => item.item_key === 'year_format')?.value === 'yyyy') {
year = currentYear.toString();
} else if (ruleInfo.value.year === 'yy') {
} else if (ruleConfig.value.find((item:any) => item.item_key === 'year_format')?.value === 'yy') {
year = currentYear.toString().substring(2);
}
if (ruleInfo.value.digit) {
digit = '0'.repeat(ruleInfo.value.digit - 1) + '1';
if (ruleConfig.value.find((item:any) => item.item_key === 'seq_length')?.value) {
digit = '0'.repeat(ruleConfig.value.find((item:any) => item.item_key === 'seq_length')?.value - 1) + '1';
}
return ruleInfo.value.prefix+'-' + year + '-' + ruleInfo.value.code + '-' + digit;
return prefix+'-' + year + '-' + code + '-' + digit;
});
const ruleInfo = ref<{prefix:string, year:null|string, code:string|null, digit:number|null, rule:string|null}>({
prefix:'',
year:null,
code: null,
digit: null,
rule: null,
})
const ruleConfig = ref<any[]>([])
const yearOptions = ref<{label:string, value:string}[]>([
{label:'四位年份(yyyy',value:'yyyy'},
{label:'两位年份(yy',value:'yy'},
@@ -247,7 +267,21 @@ const ruleOptions = ref<{label:string, value:string}[]>([
{label:'连续不重置',value:'CONTINUE'},
])
// ---------- 风险矩阵设置(按分析方法) ----------
// 保存编号规则
const saveRule = async () => {
if(ruleConfig.value.find((item:any) => item.item_key === 'prefix')?.value===''){
return window.$message?.warning(`请输入${ruleConfig.value.find((item:any) => item.item_key === 'prefix')?.label}`);
}
ruleLoading.value = true;
const {error} = await updateConfigApi({items:ruleConfig.value});
if(!error){
window.$message?.success("保存成功");
}
ruleLoading.value = false;
}
// *************** 风险矩阵设置(按分析方法) ***************//
const dimensionModal = ref<boolean>(false);
const dimensionLoading = ref<boolean>(false);
const dimForm = ref({ s: [], l: [] });
@@ -299,22 +333,6 @@ const cellLevel = (s:number, l:number) => {
const sc = cellScore(s, l);
return curMatrix.value.levels.find((lv:{min:number,max:number}) => sc >= lv.min && sc <= lv.max);
}
// AI 辅助功能切换
const toggle = (v: boolean) => {
window.$message?.success(v ? "AI 辅助功能已启用" : "AI 辅助功能已关闭");
aiEnabled.value = v;
}
const testConn = () => {
testing.value = true;
setTimeout(() => {
testing.value = false;
aiTested.value = "ok";
window.$message?.success(`连接测试通过:${aiConfig.value.mode} 响应正常(耗时 0.8s`);
}, 900);
}
const saveAiCfg = () => {
window.$message?.success("AI 接口配置已保存(配置变更留痕,立即生效)");
}
const dimensionSave = () => {
dimensionLoading.value = true;
setTimeout(() => {
@@ -340,17 +358,11 @@ const getConfig = async () => {
// 解析 AI 辅助功能配置
const aiAssistGroup = data.find((group:{group_key:string}) => group.group_key === 'ai_assist');
if (aiAssistGroup) {
const items = aiAssistGroup.items;
items.forEach((item:any) => {
switch (item.item_key) {
case 'enabled': aiEnabled.value = item.value === '1'; break;
case 'api_url': aiConfig.value.address = item.value; break;
case 'model': aiConfig.value.mode = item.value; break;
case 'api_key': aiConfig.value.key = item.value; break;
case 'timeout_seconds': aiConfig.value.timeout = Number(item.value); break;
default: break;
}
});
aiConfig.value = aiAssistGroup.items;
const target = aiConfig.value.find((item:any) => item.item_key === 'timeout_seconds');
if (target) {
target.value = Number(target.value);
}
}
// 解析 变更类型设置
const changeTypeGroup = data.find((group:{group_key:string}) => group.group_key === 'change_type');
@@ -365,17 +377,11 @@ const getConfig = async () => {
// 解析 变更编号规则
const changeCodeGroup = data.find((group:{group_key:string}) => group.group_key === 'change_no_rule');
if (changeCodeGroup) {
const items = changeCodeGroup.items;
items.forEach((item:any) => {
switch (item.item_key) {
case 'prefix': ruleInfo.value.prefix = item.value; break;
case 'year_format': ruleInfo.value.year = item.value; break;
case 'unit_code': ruleInfo.value.code = item.value; break;
case 'seq_length': ruleInfo.value.digit = Number(item.value); break;
case 'seq_reset': ruleInfo.value.rule = item.value; break;
default: break;
}
});
ruleConfig.value = changeCodeGroup.items;
const target = ruleConfig.value.find((item:any) => item.item_key === 'seq_length');
if (target) {
target.value = Number(target.value);
}
}
}
}
@@ -405,59 +411,62 @@ onMounted(() => {
<Icon icon="lucide:sparkles" class="size-14px text-violet-500 mr-1" />AI 辅助功能
</p>
<div class="py-3 px-4">
<div class="rounded-lg border p-4">
<div class="flex items-center justify-between">
<div class="text-sm font-medium">启用 AI 辅助</div>
<NSwitch size="small" :value="aiEnabled" @update:value="toggle" />
<template v-for="item in aiConfig" :key="item.item_key">
<div v-if="item.item_key==='enabled'" class="rounded-lg border p-4">
<div class="flex items-center justify-between">
<div class="text-sm font-medium">{{item.label}}</div>
<NSwitch size="small" :value="item.value==='1'" :loading="switchLoading" @update:value="(v)=>toggle(v,item)" />
</div>
<div class="mt-1 text-xs text-slate-500">
{{item.remark}}
</div>
</div>
<div class="mt-1 text-xs text-slate-500">
开启后变更申请界面显示变更识别按钮AI 自动完成变更前后比较生成变更申请表均支持手动修改关闭后全部内容手动填写
<div v-if="item.item_key==='enabled'" class="mt-3 flex items-center gap-2 rounded-lg px-3 py-2 text-xs"
:class="item.value==='1' ? 'bg-[var(--success-bg)] text-[var(--success-color)]' : 'bg-[var(--warning-bg)] text-[var(--warning-color)]'">
<Icon v-if="item.value==='1'" icon="material-symbols:check-circle-outline" class="size-16px" />
<Icon v-else icon="proicons:alert-triangle" class="size-16px" />
当前状态{{ item.value==='1' ? "已启用 —— 变更申请界面提供 AI 变更识别与申请表生成" : "已关闭 —— 变更申请界面为纯手动模式" }}
</div>
</div>
<div class="mt-3 flex items-center gap-2 rounded-lg px-3 py-2 text-xs"
:class="aiEnabled ? 'bg-[var(--success-bg)] text-[var(--success-color)]' : 'bg-[var(--warning-bg)] text-[var(--warning-color)]'">
<Icon v-if="aiEnabled" icon="material-symbols:check-circle-outline" class="size-16px" />
<Icon v-else icon="proicons:alert-triangle" class="size-16px" />
当前状态{{ aiEnabled ? "已启用 —— 变更申请界面提供 AI 变更识别与申请表生成" : "已关闭 —— 变更申请界面为纯手动模式" }}
</div>
</template>
<div class="space-y-2 border rounded-md p-3 mt-3">
<div class="flex items-center">
<Icon icon="bx:plug" class="size-14px text-slate-400 mr-1" />
AI 接口配置
<NTag v-if="aiTested === 'ok'" size="small" type="success" class="ml-auto">连接正常</NTag>
</div>
<div class="flex items-center">
<p class="w-100px text-left">接口地址</p>
<div class="flex-1">
<NInput v-model:value="aiConfig.address" placeholder="请输入接口地址" />
<template v-for="item in aiConfig" :key="item.item_key">
<div v-if="item.item_key==='api_url'" class="flex items-center">
<p class="w-100px text-left">{{item.label}}</p>
<div class="flex-1">
<NInput v-model:value="item.value" placeholder="请输入接口地址" />
</div>
</div>
</div>
<div class="flex items-center">
<p class="w-100px text-left">模型</p>
<div class="flex-1">
<NSelect v-model:value="aiConfig.mode" :options="modeOptions" placeholder="请选择模型" />
<div v-if="item.item_key==='model'" class="flex items-center">
<p class="w-100px text-left">{{item.label}}</p>
<div class="flex-1">
<NSelect v-model:value="item.value" :options="modeOptions" placeholder="请选择模型" />
</div>
</div>
</div>
<div class="flex items-center">
<p class="w-100px text-left">API Key</p>
<div class="flex-1">
<NInput v-model:value="aiConfig.key" placeholder="请输入API Key" />
<div v-if="item.item_key==='api_key'" class="flex items-center">
<p class="w-100px text-left">{{item.label}}</p>
<div class="flex-1">
<NInput v-model:value="item.value" placeholder="请输入API Key" />
</div>
</div>
</div>
<div class="flex items-center">
<p class="w-100px text-left">超时</p>
<div class="flex-1">
<NInputNumber v-model:value="aiConfig.timeout" :min="1" :show-button="false" :precision="0" placeholder="请输入超时时间" />
<div v-if="item.item_key==='timeout_seconds'" class="flex items-center">
<p class="w-100px text-left">{{item.label}}</p>
<div class="flex-1">
<NInputNumber v-model:value="item.value" :min="1" :show-button="false" :precision="0" placeholder="请输入超时时间" />
</div>
</div>
</div>
<div class="flex items-center gap-3">
</template>
<!-- <div class="flex items-center gap-3">
<NButton size="small" type="primary" class="text-xs" :loading="testing" @click="testConn">测试连接</NButton>
<NButton size="small" class="text-xs" @click="saveAiCfg">保存配置</NButton>
<NButton size="small" class="text-xs" @click="showLog = true">
<Icon icon="akar-icons:file" class="size-14px mr-1" />
调用记录查询
</NButton>
</div>
</div> -->
</div>
</div>
</NCard>
@@ -610,40 +619,42 @@ onMounted(() => {
</div>
<div class="py-3 px-4">
<div class="space-y-2">
<div class="flex items-center">
<p class="w-100px text-left">编号前缀</p>
<div class="flex-1">
<NInput v-model:value="ruleInfo.prefix" placeholder="请输入编号前缀" />
<template v-for="item in ruleConfig" :key="item.item_key">
<div v-if="item.item_key==='prefix'" class="flex items-center">
<p class="w-100px text-left">{{item.label}}</p>
<div class="flex-1">
<NInput v-model:value="item.value" :placeholder="`请输入${item.label}`" />
</div>
</div>
</div>
<div class="flex items-center">
<p class="w-100px text-left">年份格式</p>
<div class="flex-1">
<NSelect v-model:value="ruleInfo.year" :options="yearOptions" placeholder="请选择年份格式" />
<div v-if="item.item_key==='year_format'" class="flex items-center">
<p class="w-100px text-left">年份格式</p>
<div class="flex-1">
<NSelect v-model:value="item.value" :options="yearOptions" :placeholder="`请选择${item.label}`" />
</div>
</div>
</div>
<div class="flex items-center">
<p class="w-100px text-left">装置代码段</p>
<div class="flex-1">
<NSelect v-model:value="ruleInfo.code" :options="codeOptions" placeholder="请选择装置代码段" />
<div v-if="item.item_key==='unit_code'" class="flex items-center">
<p class="w-100px text-left">{{item.label}}</p>
<div class="flex-1">
<NSelect v-model:value="item.value" :options="codeOptions" :placeholder="`请选择${item.label}`" />
</div>
</div>
</div>
<div class="flex items-center">
<p class="w-100px text-left">流水号位数</p>
<div class="flex-1">
<NInputNumber v-model:value="ruleInfo.digit" :min="1" :show-button="false" :precision="0" placeholder="请输入流水号位数" />
<div v-if="item.item_key==='seq_length'" class="flex items-center">
<p class="w-100px text-left">{{item.label}}</p>
<div class="flex-1">
<NInputNumber v-model:value="item.value" :min="1" :show-button="false" :precision="0" :placeholder="`请输入${item.label}`" />
</div>
</div>
</div>
<div class="flex items-center">
<p class="w-100px text-left">流水重置规则</p>
<div class="flex-1">
<NSelect v-model:value="ruleInfo.rule" :options="ruleOptions" placeholder="请选择流水重置规则" />
<div v-if="item.item_key==='seq_reset'" class="flex items-center">
<p class="w-100px text-left">{{item.label}}</p>
<div class="flex-1">
<NSelect v-model:value="item.value" :options="ruleOptions" :placeholder="`请选择${item.label}`" />
</div>
</div>
</div>
</template>
<div class="flex items-center p-3 bg-[#f8fafc] rounded-md">
<span>编号示例预览</span>
<span class="text-[var(--theme-color)]">{{previewNumber}}</span>
<NButton size="small" type="primary" class="text-xs ml-auto">保存规则</NButton>
<NButton size="small" type="primary" class="text-xs ml-auto" :loading="ruleLoading" @click="saveRule">保存规则</NButton>
</div>
</div>
<p class="mt-3 text-xs text-slate-400">保存草稿即预占编号提交后正式生效;规则修改不影响历史单据编号</p>