变更预识别修改
This commit is contained in:
@@ -199,6 +199,7 @@ export const generatedRoutes: GeneratedRoute[] = [
|
||||
component: 'view.my_highriskdetail',
|
||||
meta: {
|
||||
title: '高风险场景详情',
|
||||
constant: true,
|
||||
hideInMenu: true,
|
||||
i18nKey: 'route.my_highriskdetail'
|
||||
}
|
||||
@@ -310,6 +311,7 @@ export const generatedRoutes: GeneratedRoute[] = [
|
||||
meta: {
|
||||
title: '修改密码',
|
||||
i18nKey: 'route.systemmanage_changepassword',
|
||||
constant: true,
|
||||
order: 5
|
||||
}
|
||||
},
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { request } from '../request';
|
||||
|
||||
/** 获取列表
|
||||
*
|
||||
*/
|
||||
export function pendingListApi() {
|
||||
return request({
|
||||
url: `/api/my-tasks`,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
@@ -97,6 +97,66 @@ export function formatTimestamp(timestamp: number | string | Date | null,format:
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 任意时间格式 → 时间戳(支持 只有小时)
|
||||
* @param {string|number|Date} input
|
||||
* @param {'ms'|'s'} unit 默认毫秒
|
||||
*/
|
||||
export function anyToTimestamp(input: string | number | Date | null | undefined, unit = 'ms') {
|
||||
if (input == null || input === '') return null;
|
||||
|
||||
const toResult = (ms: number) =>
|
||||
Number.isNaN(ms) ? null : unit === 's' ? Math.floor(ms / 1000) : ms;
|
||||
|
||||
// 1. 数字
|
||||
if (typeof input === 'number') return toResult(input);
|
||||
|
||||
// 2. Date
|
||||
if (input instanceof Date) return toResult(input.getTime());
|
||||
|
||||
let str = String(input).trim();
|
||||
|
||||
// 3. 纯数字字符串
|
||||
if (/^\d+$/.test(str)) return toResult(Number(str));
|
||||
|
||||
// 4. 统一分隔符
|
||||
str = str
|
||||
.replace(/[年月]/g, '-')
|
||||
.replace(/[日号]/g, '')
|
||||
.replace(/[时分]/g, ':')
|
||||
.replace(/秒/g, '')
|
||||
.replace(/[./]/g, '-')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
|
||||
// 5. 手动解析:小时、分钟、秒、毫秒全部可选
|
||||
const m = str.match(
|
||||
/^(\d{4})-(\d{1,2})-(\d{1,2})(?:[ T](\d{1,2}))?(?::(\d{1,2}))?(?::(\d{1,2}))?(?:\.(\d{1,3}))?/
|
||||
);
|
||||
if (m) {
|
||||
const [, y, mo, d, h = '0', mi = '0', s = '0', ms = '0'] = m;
|
||||
const t = new Date(
|
||||
+y,
|
||||
+mo - 1,
|
||||
+d,
|
||||
+h,
|
||||
+mi,
|
||||
+s,
|
||||
+ms.padEnd(3, '0')
|
||||
).getTime();
|
||||
if (!Number.isNaN(t)) return toResult(t);
|
||||
}
|
||||
|
||||
// 6. 兜底
|
||||
let t = new Date(str.replace(' ', 'T')).getTime();
|
||||
if (!Number.isNaN(t)) return toResult(t);
|
||||
|
||||
t = Date.parse(str);
|
||||
if (!Number.isNaN(t)) return toResult(t);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
// 辅助函数:Fisher-Yates 洗牌算法
|
||||
function shuffle(arr:any) {
|
||||
for (let i = arr.length - 1; i > 0; i--) {
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useChangeStore } from '@/store/modules/change';
|
||||
import { queryUnsavedApi, saveChangeApi, submitChangeApi, getChangeDetailApi } from '@/service/api/change';
|
||||
import { systemConfigApi, templateListApi, } from '@/service/api/system';
|
||||
import { orgListApi } from '@/service/api/user';
|
||||
import { formatTimestamp } from "@/utils/common";
|
||||
import { formatTimestamp, anyToTimestamp } from "@/utils/common";
|
||||
import ChangePre from "./modules/changePre.vue";
|
||||
import ChangeApply from "./modules/changeApply.vue";
|
||||
import ChangeTraining from "./modules/changeTraining.vue";
|
||||
@@ -230,7 +230,7 @@ const submitConfirm = async (type: string) => {
|
||||
|
||||
// 变更申请表
|
||||
if(type==='form'){
|
||||
if(changeApplyRules(form)){
|
||||
if(changeApplyRules(form?.apply_form)){
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -254,6 +254,11 @@ const getChangeDetail = async () => {
|
||||
changeNo.value = data.change_no;
|
||||
changePreForm.value = data.precheck;
|
||||
changeApplyForm.value = data.apply_form;
|
||||
|
||||
// 解析时间戳
|
||||
changeApplyForm.value.plan_use_date = anyToTimestamp(changeApplyForm.value.plan_use_date);
|
||||
changeApplyForm.value.restore_deadline = anyToTimestamp(changeApplyForm.value.restore_deadline);
|
||||
|
||||
riskAnalysisForm.value = data.risk_records;
|
||||
changeTrainingForm.value = data.train;
|
||||
pssrForm.value = data.pssr;
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, h, ref, watch } from 'vue'
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { NButton, NDataTable, NInput, NPopconfirm } from 'naive-ui'
|
||||
import type { DataTableColumns } from 'naive-ui'
|
||||
import { Icon } from '@iconify/vue'
|
||||
import RichTextInput from './RichTextInput.vue'
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
|
||||
interface ChangeItem {
|
||||
item: string
|
||||
before_text: string
|
||||
after_text: string
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
data: ChangeItem[]
|
||||
editable?: boolean
|
||||
@@ -135,7 +137,14 @@ const columns = computed<DataTableColumns<ChangeItem>>(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="change-compare-table">
|
||||
<div class="border" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
||||
<div class="flex items-center justify-between px-4 py-2 border-b">
|
||||
<div class="flex items-center">
|
||||
<Icon icon="lucide:list-checks" class="size-16px text-emerald-500" />
|
||||
<span class="font-semibold text-base ml-2">{{data.length ? `变更识别结果(${data.length}项)` : '变更识别结果(空表 · 可手动录入,或由 AI 识别生成)'}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="change-compare-table p-4">
|
||||
<NDataTable
|
||||
:columns="columns"
|
||||
:data="localData"
|
||||
@@ -149,6 +158,7 @@ const columns = computed<DataTableColumns<ChangeItem>>(() => {
|
||||
</NButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -7,12 +7,14 @@ const props = withDefaults(defineProps<{
|
||||
minRows?: number
|
||||
maxRows?: number
|
||||
disabled?: boolean
|
||||
bordered?: boolean
|
||||
}>(), {
|
||||
modelValue: '',
|
||||
placeholder: '',
|
||||
minRows: 1,
|
||||
maxRows: 100,
|
||||
disabled: false,
|
||||
bordered: true,
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -235,7 +237,7 @@ onBeforeUnmount(() => {
|
||||
<template>
|
||||
<div
|
||||
class="rich-text-input"
|
||||
:class="{ 'is-focused': isFocused, 'is-disabled': disabled }"
|
||||
:class="{ 'is-focused': isFocused, 'is-disabled': disabled, 'bordered': bordered }"
|
||||
>
|
||||
<!-- 工具栏 -->
|
||||
<div v-show="isFocused && !disabled" class="rti-toolbar">
|
||||
@@ -296,12 +298,14 @@ onBeforeUnmount(() => {
|
||||
<style scoped>
|
||||
.rich-text-input {
|
||||
position: relative;
|
||||
border: 1px solid #e5e8ef;
|
||||
border-radius: 6px;
|
||||
background: #fafbfc;
|
||||
transition: all 0.2s ease;
|
||||
overflow: visible;
|
||||
}
|
||||
.bordered {
|
||||
border: 1px solid #e5e8ef;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.rich-text-input:hover {
|
||||
border-color: #2080f0;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<!-- 变更预识别 -->
|
||||
<script setup lang="ts">
|
||||
import { inject, watch, ref } from 'vue';
|
||||
import { inject, watch, ref, computed } from 'vue';
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { AiError, aiApplication, aiRecognize, aiRiskPreAnalysis } from "@/service/api/ai";
|
||||
import ChangeCompareTable from "./ChangeCompareTable.vue";
|
||||
import PreRisk from "./preRisk.vue";
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
|
||||
@@ -17,18 +18,16 @@ const aiFail = (e: any) => {
|
||||
if (e instanceof AiError) window.$message?.warning(`AI 分析失败:${e.message},可手动填写`);
|
||||
else window.$message?.warning("AI 分析失败,可手动填写");
|
||||
};
|
||||
// 是否是ai生成
|
||||
const isAiGenerated = ref<boolean>(false);
|
||||
// 变更识别loading
|
||||
const preLoading = ref<boolean>(false);
|
||||
// 生成变更申请单loading
|
||||
const generateLoading = ref<boolean>(false);
|
||||
// 风险预分析loading
|
||||
const analyzing = ref<boolean>(false);
|
||||
// 编辑风险预分析
|
||||
const preRiskEdit = ref<string>('');
|
||||
// 是否显示险预分析
|
||||
const preRiskShow = ref<boolean>(false);
|
||||
const preRiskShow = computed(() => form.value.severity !== '' || form.value.probability !== '' || form.value.protection !== '');
|
||||
// 是否显示变更识别结果
|
||||
const identifyShow = computed(() => form.value.compare_rows.length > 0);
|
||||
// 表单
|
||||
const form = ref<{
|
||||
description: string,
|
||||
@@ -54,7 +53,6 @@ const runIdentify = async () => {
|
||||
try {
|
||||
const res = await aiRecognize(form.value.description);
|
||||
form.value.compare_rows = (res?.rows ?? []).map((x, i) => ({ item: x.item, before_text: x.before_text, after_text: x.after_text }));
|
||||
isAiGenerated.value = true;
|
||||
window.$message?.success(`AI 变更识别完成,共 ${form.value.compare_rows.length} 项,请逐项人工确认(可直接修改)`);
|
||||
} catch (e: any) {
|
||||
form.value.compare_rows = [];
|
||||
@@ -79,7 +77,6 @@ const runRiskAnalysis = async () => {
|
||||
probability: res?.probability ?? form.value.probability,
|
||||
protection: res?.protection ?? form.value.protection,
|
||||
};
|
||||
preRiskShow.value = true;
|
||||
window.$message?.success("AI 风险预分析报告已生成(危害严重性 / 事件概率 / 保护措施影响),内容支持手动修改");
|
||||
} catch (e: any) {
|
||||
aiFail(e);
|
||||
@@ -115,9 +112,6 @@ const saveDraft = () => {
|
||||
defineExpose({form})
|
||||
|
||||
watch(() => props.currentForm, (newVal:any) => {
|
||||
if(newVal.severity){
|
||||
preRiskShow.value = true;
|
||||
}
|
||||
const {
|
||||
attachments,
|
||||
change_id,
|
||||
@@ -140,7 +134,7 @@ watch(() => props.currentForm, (newVal:any) => {
|
||||
<Icon icon="lucide:sparkles" class="size-16px text-violet-500" />
|
||||
<span class="font-semibold text-base ml-2">变更内容描述</span>
|
||||
</div>
|
||||
<NButton ghost type="primary" @click="runIdentify" :disabled="btnDisabled">
|
||||
<NButton ghost type="primary" @click="runIdentify" :disabled="btnDisabled || !form.description.trim()">
|
||||
<Icon v-if="preLoading" icon="ri:loader-4-fill" class="size-16px animate-spin" />
|
||||
<Icon v-else icon="fluent:wand-24-regular" class="size-16px" />
|
||||
变更识别
|
||||
@@ -151,52 +145,17 @@ watch(() => props.currentForm, (newVal:any) => {
|
||||
</div>
|
||||
</div>
|
||||
<!-- 变更识别结果 -->
|
||||
<div class="border" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
||||
<div class="flex items-center justify-between px-4 py-2 border-b">
|
||||
<div class="flex items-center">
|
||||
<Icon icon="lucide:list-checks" class="size-16px text-emerald-500" />
|
||||
<span class="font-semibold text-base ml-2">{{form.compare_rows.length ? `变更识别结果(${form.compare_rows.length}项)` : '变更识别结果(空表 · 可手动录入,或由 AI 识别生成)'}}</span>
|
||||
</div>
|
||||
<span v-if="isAiGenerated" class="inline-flex items-center gap-1 rounded-full border border-violet-200 bg-violet-100 px-2 py-0.5 text-xs font-medium text-violet-700">
|
||||
✨ AI 生成 · 需人工确认
|
||||
</span>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<ChangeCompareTable
|
||||
:data="form.compare_rows"
|
||||
:editable="true"
|
||||
@update:data="(d) => form.compare_rows = d"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="identifyShow">
|
||||
<ChangeCompareTable
|
||||
:data="form.compare_rows"
|
||||
:editable="true"
|
||||
@update:data="(d) => form.compare_rows = d"
|
||||
/>
|
||||
</template>
|
||||
<!-- 风险预分析报告 -->
|
||||
<div v-if="preRiskShow" class="border" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
||||
<div class="flex items-center justify-between px-4 py-2 border-b">
|
||||
<div class="flex items-center">
|
||||
<Icon icon="lucide:list-checks" class="size-16px text-emerald-500" />
|
||||
<span class="font-semibold text-base ml-2">风险预分析报告</span>
|
||||
</div>
|
||||
<span class="inline-flex items-center gap-1 rounded-full border border-violet-200 bg-violet-100 px-2 py-0.5 text-xs font-medium text-violet-700">
|
||||
✨ AI 生成 · 需人工确认
|
||||
</span>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<div class="space-y-4">
|
||||
<div v-for="s in ([{ key: 'severity', name: '危害严重性分析' }, { key: 'probability', name: '事件概率分析' }, { key: 'protection', name: '保护措施影响分析' }])" :key="s.key" class="rounded-lg border">
|
||||
<div class="flex items-center gap-2 border-b bg-slate-50/60 px-4 py-2">
|
||||
<span class="text-sm font-semibold text-slate-700">{{ s.name }}</span>
|
||||
<NButton text type="primary" class="ml-auto text-xs hover:underline" @click="preRiskEdit = preRiskEdit === s.key ? '' : s.key">
|
||||
{{ preRiskEdit === s.key ? "完成" : "编辑" }}
|
||||
</NButton>
|
||||
</div>
|
||||
<NInput v-model:value="form[s.key]" :readonly="preRiskEdit !== s.key" type="textarea" :autosize="{ minRows: 2 }" :bordered="false" autofocus />
|
||||
</div>
|
||||
<div class="rounded-lg bg-amber-50 px-3 py-2 text-xs leading-5 text-amber-700">
|
||||
AI 风险预分析仅供参考,用于申请阶段的快速研判;正式风险识别请前往「风险分析记录表」标签页,使用 HAZOP / JSA / 检查表法开展并留存记录。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="preRiskShow">
|
||||
<PreRisk :formInfo="form" @update:data="(key:'severity' | 'probability' | 'protection',val:string) => form[key] = val" />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-x-4 gap-y-2 bg-[#EAF0F9] px-4 py-2.5 shadow-[0_-2px_8px_rgba(0,0,0,0.05)]">
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { Icon } from '@iconify/vue'
|
||||
import RichTextInput from './RichTextInput.vue'
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
const props = defineProps(['formInfo'])
|
||||
const emit = defineEmits(['update:data']);
|
||||
|
||||
const form = ref<any>(null);
|
||||
watch(() => props.formInfo,
|
||||
(val: any) => {
|
||||
// 深度拷贝以避免直接修改 props,并确保响应式
|
||||
form.value = val
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
)
|
||||
// 编辑风险预分析
|
||||
const preRiskEdit = ref<boolean>(false);
|
||||
// 更新数据
|
||||
const onCellChange = (key:string,val:string) => {
|
||||
form.value[key] = val;
|
||||
emit('update:data', key, val)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="border" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
||||
<div class="flex items-center justify-between px-4 py-2 border-b">
|
||||
<div class="flex items-center">
|
||||
<Icon icon="lucide:list-checks" class="size-16px text-emerald-500" />
|
||||
<span class="font-semibold text-base ml-2">风险预分析报告</span>
|
||||
</div>
|
||||
<NButton text type="primary" class="ml-auto text-xs hover:underline" @click="preRiskEdit = !preRiskEdit">
|
||||
{{ preRiskEdit ? "完成" : "编辑" }}
|
||||
</NButton>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<div class="space-y-4">
|
||||
<div v-for="s in ([{ key: 'severity', name: '危害严重性分析' }, { key: 'probability', name: '事件概率分析' }, { key: 'protection', name: '保护措施影响分析' }])" :key="s.key" class="rounded-lg border">
|
||||
<div class="flex items-center gap-2 border-b bg-slate-50/60 px-4 py-2">
|
||||
<span class="text-sm font-semibold text-slate-700">{{ s.name }}</span>
|
||||
</div>
|
||||
<RichTextInput :disabled="!preRiskEdit" :modelValue="form[s.key]" :minRows="2" :bordered="false" @update:modelValue="(v) => onCellChange(s.key,v)" />
|
||||
</div>
|
||||
<div class="rounded-lg bg-amber-50 px-3 py-2 text-xs leading-5 text-amber-700">
|
||||
AI 风险预分析仅供参考,用于申请阶段的快速研判;正式风险识别请前往「风险分析记录表」标签页,使用 HAZOP / JSA / 检查表法开展并留存记录。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -33,7 +33,7 @@ watch(() => props.currentForm, (newVal:any) => {
|
||||
};
|
||||
});
|
||||
// 3. 过滤掉不需要的字段
|
||||
const omitFields = ['created_at', 'updated_at', 'change_id', 'group_type', 'group_name'];
|
||||
const omitFields = ['created_at', 'updated_at', 'change_id', 'group_name'];
|
||||
const newResult = result.map((group:any) => ({
|
||||
...group,
|
||||
items: group.items.map((item:any) =>
|
||||
|
||||
@@ -6,7 +6,6 @@ import { useThemeStore } from '@/store/modules/theme';
|
||||
|
||||
const appStore = useAppStore();
|
||||
const themeStore = useThemeStore();
|
||||
const gap = computed(() => (appStore.isMobile ? 0 : 16));
|
||||
|
||||
const emit = defineEmits<{ go: [v: string, preset?: string];}>();
|
||||
interface MyChange {
|
||||
|
||||
+204
-19
@@ -3,10 +3,11 @@ import { ref, computed } from 'vue';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { useRouterPush } from '@/hooks/common/router';
|
||||
|
||||
const appStore = useAppStore();
|
||||
const themeStore = useThemeStore();
|
||||
const gap = computed(() => (appStore.isMobile ? 0 : 16));
|
||||
const { routerPushByKey } = useRouterPush();
|
||||
|
||||
const emit = defineEmits<{ go: [v: string, preset?: string];}>();
|
||||
interface MyChange {
|
||||
@@ -45,14 +46,14 @@ const specialTab = computed(() => currentTab.value === "tempOverdue" || currentT
|
||||
const myChanges = ref<MyChange[]>([
|
||||
{ id: "MOC-2026-R01-0035", title: "R-201 反应釜搅拌器电机更换(55kW→75kW 防爆电机)", level: "重要", dur: "永久", stage: "pending", status: "审批中", detail: "专业会签中(周设备、王海燕待签)", applyTime: "2026-07-28 09:12", meta: "计划投用 2026-08-10", updateTime: "2026-07-30 14:20", node: "专业会签", members: [{ name: "王仪表", done: true }, { name: "周设备", done: false }, { name: "王海燕", done: false }] },
|
||||
{ id: "MOC-2026-R01-0029", title: "操作规程修订(夏季工况)", level: "一般", dur: "临时", stage: "pending", status: "审批中", detail: "部门审核(李主任)", applyTime: "2026-06-20 11:26", meta: "计划投用 2026-07-01", updateTime: "2026-06-21 08:40", deadline: "2026-08-01", left: -2, extended: false, node: "部门审核", members: [{ name: "李主任", done: false }] },
|
||||
{ id: "MOC-2026-R02-0046", title: "循环水加药泵冲程临时调整", level: "一般", dur: "临时", stage: "approved", status: "已批准", detail: "培训 / PSSR 资料待上传(线下签字后拍照上传)", applyTime: "2026-07-26 10:15", meta: "批准 2026-07-30 · 待实施", updateTime: "2026-07-30 16:20", deadline: "2026-08-20", left: 17, extended: false, node: "投用准备", members: [{ name: "张工艺", done: false }] },
|
||||
{ id: "MOC-2026-R02-0047", title: "氮气吹扫流程临时调整", level: "一般", dur: "临时", stage: "approved", status: "已批准", detail: "已到期未办理恢复 / 延期 / 转永久", applyTime: "2026-07-10 10:05", meta: "批准 2026-07-12 · 待实施", updateTime: "2026-07-31 08:10", deadline: "2026-07-31", left: -3, extended: false, node: "实施执行" },
|
||||
{ id: "MOC-2026-R02-0045", title: "P-204 出口管线伴热改造", level: "一般", dur: "永久", stage: "approved", status: "已批准", detail: "因装置检修窗口调整未实施", applyTime: "2026-07-15 09:40", meta: "审批通过 2026-07-20 · 计划投用 2026-07-30", updateTime: "2026-07-20 11:00", node: "实施执行" },
|
||||
{ id: "MOC-2026-R02-0046", title: "循环水加药泵冲程临时调整", level: "一般", dur: "临时", stage: "pass", status: "已批准", detail: "培训 / PSSR 资料待上传(线下签字后拍照上传)", applyTime: "2026-07-26 10:15", meta: "批准 2026-07-30 · 待实施", updateTime: "2026-07-30 16:20", deadline: "2026-08-20", left: 17, extended: false, node: "投用准备", members: [{ name: "张工艺", done: false }] },
|
||||
{ id: "MOC-2026-R02-0047", title: "氮气吹扫流程临时调整", level: "一般", dur: "临时", stage: "pass", status: "已批准", detail: "已到期未办理恢复 / 延期 / 转永久", applyTime: "2026-07-10 10:05", meta: "批准 2026-07-12 · 待实施", updateTime: "2026-07-31 08:10", deadline: "2026-07-31", left: -3, extended: false, node: "实施执行" },
|
||||
{ id: "MOC-2026-R02-0045", title: "P-204 出口管线伴热改造", level: "一般", dur: "永久", stage: "pass", status: "已批准", detail: "因装置检修窗口调整未实施", applyTime: "2026-07-15 09:40", meta: "审批通过 2026-07-20 · 计划投用 2026-07-30", updateTime: "2026-07-20 11:00", node: "实施执行" },
|
||||
{ id: "MOC-2026-R02-0042", title: "循环水旁滤器临时绕流运行", level: "一般", dur: "临时", stage: "inuse", status: "待验收", detail: "验收材料准备中", applyTime: "2026-07-28 14:10", meta: "投用 2026-08-02 · 到期 2026-08-09", updateTime: "2026-08-02 08:30", deadline: "2026-08-09", left: 6, extended: false, node: "变更验收" },
|
||||
{ id: "MOC-2026-R02-0043", title: "空压站干燥器再生周期调整", level: "一般", dur: "永久", stage: "inuse", status: "待验收", detail: "运行数据收集中(验收周期 30 天)", applyTime: "2026-07-18 10:25", meta: "投用 2026-07-25 · 计划验收 2026-08-24", updateTime: "2026-07-25 09:12", node: "运行验证" },
|
||||
{ id: "MOC-2026-R01-0039", title: "V-102 安全阀起跳压力临时调整(紧急补办)", level: "一般", dur: "临时", urgent: true, stage: "closing", status: "待关闭", detail: "待关闭确认(资料更新核查中)", applyTime: "2026-07-15 19:40", meta: "已投用 · 到期 2026-07-27", updateTime: "2026-07-27 09:02", deadline: "2026-07-27", left: -4, extended: false, node: "关闭确认", members: [{ name: "钱峰", done: false }] },
|
||||
]);
|
||||
const tempOverdueList = computed(() => (role.value === "applicant" ? myChanges.value : []).filter((c: MyChange) => c.stage === "approved" && c.dur === "临时" && (c.left ?? 0) < 0));
|
||||
const tempOverdueList = computed(() => (role.value === "applicant" ? myChanges.value : []).filter((c: MyChange) => c.stage === "pass" && c.dur === "临时" && (c.left ?? 0) < 0));
|
||||
const closeOverdueList = computed(() => (role.value === "applicant" ? myChanges.value : []).filter((c: MyChange) => c.stage === "closing" && (c.left ?? 0) < 0));
|
||||
|
||||
interface ListType {
|
||||
@@ -64,7 +65,7 @@ interface ListType {
|
||||
const list = ref<ListType[]>([
|
||||
{
|
||||
id: "MOC-2026-R01-001",
|
||||
title: '审批流转中',
|
||||
title: '待我审批',
|
||||
status: 'pending',
|
||||
list: [
|
||||
{
|
||||
@@ -110,10 +111,189 @@ const list = ref<ListType[]>([
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "MOC-2026-R01-002",
|
||||
title: '我的申请',
|
||||
status: 'my',
|
||||
list: [
|
||||
{
|
||||
id: "MOC-2026-R02-0001",
|
||||
title:'循环水加药泵冲程临时调整',
|
||||
level: "一般",
|
||||
dur: "临时",
|
||||
stage: "approved",
|
||||
status: "已批准",
|
||||
node: "投用准备",
|
||||
overdue: false,//是否超期
|
||||
near: false,//是否临期
|
||||
extended: false,//是否延期
|
||||
extendedCount: 0,//延期次数
|
||||
day: 0,
|
||||
urgent: false,
|
||||
members: [{ name: "张工艺", done: false }],
|
||||
applyTime: "2026-07-26 10:15",
|
||||
updateTime: "2026-07-30 16:20",
|
||||
meta: "批准 2026-07-30 · 待实施",
|
||||
deadline: "2026-08-20",
|
||||
detail: "培训 / PSSR 资料待上传(线下签字后拍照上传)",
|
||||
},
|
||||
{
|
||||
id: "MOC-2026-R02-0002",
|
||||
title:'氮气吹扫流程临时调整',
|
||||
level: "一般",
|
||||
dur: "临时",
|
||||
stage: "approved",
|
||||
status: "已批准",
|
||||
node: "实施执行",
|
||||
overdue: true,
|
||||
near: false,
|
||||
extended: false,
|
||||
extendedCount: 0,
|
||||
day: 3,
|
||||
urgent: false,
|
||||
members: [],
|
||||
applyTime: "2026-07-10 10:05",
|
||||
updateTime: "2026-07-31 08:10",
|
||||
meta: "批准 2026-07-12 · 待实施",
|
||||
deadline: "2026-07-31",
|
||||
detail: "已到期未办理恢复 / 延期 / 转永久",
|
||||
},
|
||||
{
|
||||
id: "MOC-2026-R02-0003",
|
||||
title:'P-204 出口管线伴热改造',
|
||||
level: "一般",
|
||||
dur: "永久",
|
||||
stage: "approved",
|
||||
status: "已批准",
|
||||
node: "实施执行",
|
||||
overdue: false,
|
||||
near: false,
|
||||
extended: false,
|
||||
extendedCount: 0,
|
||||
day: 0,
|
||||
urgent: false,
|
||||
members: [],
|
||||
applyTime: "2026-07-15 09:40",
|
||||
updateTime: "2026-07-20 11:00",
|
||||
meta: "审批通过 2026-07-20 · 计划投用 2026-07-30",
|
||||
detail: "因装置检修窗口调整未实施",
|
||||
},
|
||||
{
|
||||
id: "MOC-2026-R02-0004",
|
||||
title:'循环水旁滤器临时绕流运行',
|
||||
level: "一般",
|
||||
dur: "临时",
|
||||
stage: "inuse",
|
||||
status: "待验收",
|
||||
node: "变更验收",
|
||||
overdue: false,
|
||||
near: true,
|
||||
extended: false,
|
||||
extendedCount: 0,
|
||||
day: 6,
|
||||
urgent: false,
|
||||
members: [],
|
||||
applyTime: "2026-07-28 14:10",
|
||||
updateTime: "2026-08-02 08:30",
|
||||
deadline: "2026-08-09",
|
||||
meta: "投用 2026-08-02 · 到期 2026-08-09",
|
||||
detail: "验收材料准备中",
|
||||
},
|
||||
{
|
||||
id: "MOC-2026-R02-0005",
|
||||
title:'空压站干燥器再生周期调整',
|
||||
level: "一般",
|
||||
dur: "永久",
|
||||
stage: "inuse",
|
||||
status: "待验收",
|
||||
node: "运行验证",
|
||||
overdue: false,
|
||||
near: false,
|
||||
extended: false,
|
||||
extendedCount: 0,
|
||||
day: 0,
|
||||
urgent: false,
|
||||
members: [],
|
||||
applyTime: "2026-07-18 10:25",
|
||||
updateTime: "2026-07-25 09:12",
|
||||
meta: "投用 2026-07-25 · 计划验收 2026-08-24",
|
||||
detail: "运行数据收集中(验收周期 30 天)",
|
||||
},
|
||||
{
|
||||
id: "MOC-2026-R02-0006",
|
||||
title:'V-102 安全阀起跳压力临时调整(紧急补办)',
|
||||
level: "一般",
|
||||
dur: "临时",
|
||||
stage: "closing",
|
||||
status: "待关闭",
|
||||
node: "关闭确认",
|
||||
overdue: true,
|
||||
near: false,
|
||||
extended: false,
|
||||
extendedCount: 0,
|
||||
day: 4,
|
||||
urgent: true,
|
||||
members: [{ name: "钱峰", done: false }],
|
||||
applyTime: "2026-07-15 19:40",
|
||||
updateTime: "2026-07-27 09:02",
|
||||
deadline: "2026-07-27",
|
||||
meta: "已投用 · 到期 2026-07-27",
|
||||
detail: "待关闭确认(资料更新核查中)",
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "MOC-2026-R01-001",
|
||||
title: '审批流转中',
|
||||
status: 'approval',
|
||||
list: [
|
||||
{
|
||||
id: "MOC-2026-R01-0001",
|
||||
title:'R-201 反应釜搅拌器电机更换(55kW→75kW 防爆电机)',
|
||||
level: "重要",
|
||||
dur: "永久",
|
||||
stage: "pending",
|
||||
status: "审批中",
|
||||
node: "专业会签",
|
||||
overdue: false,//是否超期
|
||||
near: false,//是否临期
|
||||
extended: false,//是否延期
|
||||
extendedCount: 0,//延期次数
|
||||
day: 0,
|
||||
urgent: false,
|
||||
members: [{ name: "王仪表", done: true },{ name: "周设备", done: false },{ name: "王海燕", done: false }],
|
||||
applyTime: "2026-07-28 09:12",
|
||||
updateTime: "2026-07-30 14:20",
|
||||
meta: "计划投用 2026-08-10",
|
||||
detail: "专业会签中(周设备、王海燕待签)"
|
||||
},
|
||||
{
|
||||
id: "MOC-2026-R01-0002",
|
||||
title:'操作规程修订(夏季工况)',
|
||||
level: "一般",
|
||||
dur: "临时",
|
||||
stage: "pending",
|
||||
status: "审批中",
|
||||
node: "部门审核",
|
||||
overdue: true,
|
||||
near: false,
|
||||
extended: false,
|
||||
extendedCount: 0,
|
||||
day: 2,
|
||||
urgent: false,
|
||||
members: [{ name: "李主任", done: false }],
|
||||
applyTime: "2026-06-20 11:26",
|
||||
updateTime: "2026-06-21 08:40",
|
||||
meta: "计划投用 2026-07-01",
|
||||
deadline: "2026-08-01",
|
||||
detail: "部门审核(李主任)"
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "MOC-2026-R01-002",
|
||||
title: '审批通过',
|
||||
status: 'approved',
|
||||
status: 'pass',
|
||||
list: [
|
||||
{
|
||||
id: "MOC-2026-R02-0001",
|
||||
@@ -305,10 +485,7 @@ const opts = computed<Opt[]>(() => {
|
||||
|
||||
// 打开详情
|
||||
const openDetail = (c: MyChange,type:string = '') => {
|
||||
if(type==='reject'){
|
||||
window.$message?.info("演示:打开详情");
|
||||
return;
|
||||
}
|
||||
routerPushByKey("my_highriskdetail",{ query: { id: c.id.toString() } });
|
||||
};
|
||||
// 打开进度
|
||||
const openProgress = (c: MyChange,type:string = '') => {
|
||||
@@ -355,7 +532,7 @@ const revokeSave = () => {
|
||||
setTimeout(() => {
|
||||
revokeSaving.value = false;
|
||||
revokeModal.value = false;
|
||||
const pendingGroup = list.value.find((item: any) => item.status === 'pending');
|
||||
const pendingGroup = list.value.find((item: any) => item.status === 'approval');
|
||||
if (pendingGroup) {
|
||||
// 过滤掉匹配 targetId 的项
|
||||
pendingGroup.list = pendingGroup.list.filter((item:any) => item.id !== currentRow.value?.id);
|
||||
@@ -367,7 +544,7 @@ const revokeSave = () => {
|
||||
|
||||
<template>
|
||||
<NSpace vertical :size="16">
|
||||
<!-- 待我处理 -->
|
||||
<!-- 我的任务 -->
|
||||
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
||||
<div
|
||||
class="h-100% flex flex-wrap items-center gap-2 border border-slate-200 px-3 py-2 mb-3"
|
||||
@@ -408,15 +585,23 @@ const revokeSave = () => {
|
||||
:key="item.id"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<!-- 待我审批 -->
|
||||
<Icon v-if="item.status==='pending'" icon="ix:success" class="size-16px" :style="{ color: themeStore.otherColor.success }" />
|
||||
<!-- 我的申请 -->
|
||||
<Icon v-if="item.status==='my'" icon="akar-icons:file" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
||||
<!-- 审批流转中/已通过 -->
|
||||
<Icon v-if="item.status==='pending' || item.status==='approved'" icon="akar-icons:file" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
||||
<Icon v-if="item.status==='approval' || item.status==='pass'" icon="akar-icons:file" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
||||
<!-- 被驳回处理 -->
|
||||
<Icon v-if="item.status==='reject'" icon="system-uicons:reset" class="size-16px" :style="{ color: themeStore.themeColor }" />
|
||||
<p class="text-13px font-700 text-#64749a">{{item.title}}</p>
|
||||
<!-- 待我审批 -->
|
||||
<p v-if="item.status==='pending'" class="text-11px text-slate-400">共 {{ item.list.length }} 项 · 流程当前节点轮到您处理 · 点击进入审批</p>
|
||||
<!-- 我的申请 -->
|
||||
<p v-if="item.status==='my'" class="text-11px text-slate-400">共 {{item.list.length}} 项</p>
|
||||
<!-- 审批流转中 -->
|
||||
<p v-if="item.status==='pending'" class="text-11px text-#94a3b8">共 {{item.list.length}} 项 · 审批流程推进中 · 可查看进度或撤回</p>
|
||||
<p v-if="item.status==='approval'" class="text-11px text-#94a3b8">共 {{item.list.length}} 项 · 审批流程推进中 · 可查看进度或撤回</p>
|
||||
<!-- 已通过 -->
|
||||
<p v-if="item.status==='approved'" class="text-11px text-#94a3b8">共 {{item.list.length}} 项 · 待实施 / 待验收 / 待关闭 · 处置申请与资料关闭在行内办理</p>
|
||||
<p v-if="item.status==='pass'" class="text-11px text-#94a3b8">共 {{item.list.length}} 项 · 待实施 / 待验收 / 待关闭 · 处置申请与资料关闭在行内办理</p>
|
||||
<!-- 驳回处理 -->
|
||||
<p v-if="item.status==='reject'" class="text-11px text-#94a3b8">{{item.list.length}} 项待处理</p>
|
||||
</div>
|
||||
@@ -428,7 +613,7 @@ const revokeSave = () => {
|
||||
>
|
||||
<!-- 左区 -->
|
||||
<!-- 审批流转中/已通过 -->
|
||||
<div v-if="item.status==='pending' || item.status==='approved'" class="min-w-0 flex-1">
|
||||
<div v-if="item.status==='approval' || item.status==='pass'" class="min-w-0 flex-1">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<span class="font-mono text-[11px] text-slate-400">{{ c.id }}</span>
|
||||
<NTag v-if="c.level==='重要'" size="small" type="error">{{c.level}}</NTag>
|
||||
@@ -490,13 +675,13 @@ const revokeSave = () => {
|
||||
</div>
|
||||
<!-- 右区 -->
|
||||
<!-- 审批流转中 -->
|
||||
<div v-if="c.stage === 'pending'" class="flex gap-1.5">
|
||||
<div v-if="c.stage === 'approval'" class="flex gap-1.5">
|
||||
<NButton class="text-xs" text type="primary" @click="openDetail(c)">详情</NButton>
|
||||
<NButton class="text-xs" text type="warning" @click="openProgress(c)">进度</NButton>
|
||||
<NButton class="text-xs" text type="error" @click="openWithdraw(c)">撤回</NButton>
|
||||
</div>
|
||||
<!-- 审批通过、待验收、待关闭 -->
|
||||
<div v-if="c.stage === 'approved' || c.stage === 'inuse' || c.stage === 'closing'" class="flex gap-1.5">
|
||||
<div v-if="c.stage === 'pass' || c.stage === 'inuse' || c.stage === 'closing'" class="flex gap-1.5">
|
||||
<NButton class="text-xs" text type="primary" @click="openDetail(c)">详情</NButton>
|
||||
<NButton class="text-xs" text type="warning" @click="openProgress(c)">进度</NButton>
|
||||
<NButton class="text-xs" text type="primary" @click="openDisposal(c)">处置申请</NButton>
|
||||
|
||||
@@ -171,6 +171,7 @@ const getAllPaths = (tree:any) => {
|
||||
}
|
||||
// 获取所有路由
|
||||
const getAllRoutes = () => {
|
||||
console.log(router.getRoutes())
|
||||
const filtered = router.getRoutes().filter((item:any) => {
|
||||
// 条件1:有name且非常量
|
||||
if (item.name === undefined || item.meta?.constant === true) return false;
|
||||
@@ -182,6 +183,8 @@ const getAllRoutes = () => {
|
||||
})
|
||||
const tree = filtered.map((item:any) => copyTitleToTop(item));
|
||||
routeTree.value = tree;
|
||||
|
||||
console.log(tree)
|
||||
allPaths.value = getAllPaths(tree);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user