Files
moc/src/views/my/processDetail/index.vue
T

185 lines
10 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.
<script setup lang="ts">
import { onMounted, ref, computed, watch } from 'vue';
import { Icon } from '@iconify/vue'
import { useThemeStore } from '@/store/modules/theme';
import { useRouterPush } from '@/hooks/common/router';
import { useRoute } from 'vue-router';
const route = useRoute();
const { routerPushByKey } = useRouterPush();
const themeStore = useThemeStore();
const sc = ref<any>(null);
const cur = ref<number>(0);
const playing = ref(false);
const curStageIdx = computed(() => (sc.value ? sc.value.stages.indexOf(sc.value.steps[cur.value].stage) : 0));
const finished = computed(() => (sc.value ? cur.value >= sc.value?.steps?.length - 1 : false));
const ROLES_NAME: Record<string, string> = {
applicant: "申请人(张工艺)", process: "专业会签(王仪表)", process2: "专业会签(周设备)",
safety: "安全工程师(王海燕)", dept: "车间主任(李主任)", vp: "分管领导(刘副总)", admin: "系统管理员",
quality: "质量工程师(陈质量)", viewer: "非审批人员(吴新)",
};
const VIEW_NAME: Record<string, string> = {
apply: "变更申请", tasks: "我的任务", approvals: "审批中心", execution: "PSSR / 验收", closure: "关闭管理",
};
let timer: ReturnType<typeof setTimeout> | undefined;
watch([playing, cur, sc], ([p, c, s]) => {
if (timer) { clearTimeout(timer); timer = undefined; }
if (!p || !s) return;
if (c >= s.steps.length - 1) { playing.value = false; return; }
timer = setTimeout(() => { cur.value = Math.min(cur.value + 1, (sc.value?.steps.length ?? 1) - 1); }, 2000);
});
function jumpFor(s: any): { role: string; view: string } | null {
const a = s.actor;
if (a === "系统") return null;
let role: string;
if (a.includes("张工艺")) role = "applicant";
else if (a.includes("李主任")) role = "dept";
else if (a.includes("王仪表")) role = "process";
else if (a.includes("周设备")) role = "process2";
else if (a.includes("刘副总")) role = "vp";
else if (a.includes("专人") || a.includes("安全")) role = "safety";
else if (a.includes("岗位") || a.includes("维修班")) role = "applicant";
else if (a.includes("车间")) role = "dept";
else return null;
let view: string;
if (role === "applicant") view = s.stage === "变更申请" ? "apply" : "tasks";
else if (s.stage.includes("审批")) view = "approvals";
else if (s.stage === "关闭") view = "closure";
else view = "execution";
return { role, view };
}
function prev() { playing.value = false; cur.value = Math.max(0, cur.value - 1); }
function next() { playing.value = false; if (sc.value) cur.value = Math.min(sc.value.steps.length - 1, cur.value + 1); }
function reset() { playing.value = false; cur.value = 0; }
function back() {
routerPushByKey("home_process");
}
function jumpTo() {
routerPushByKey("home_initiatechange");
}
onMounted(() => {
if(route.query && route.query.params){
sc.value = JSON.parse(route.query.params as string);
}
})
</script>
<template>
<NSpace vertical :size="16">
<!-- 流程演示详情 -->
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<div class="space-y-3">
<div class="flex flex-wrap items-center gap-2">
<NButton ghost type="primary" class="text-sm font-medium" @click="back">
<Icon icon="ep:back" class="size-16px mr-1" /> 返回示例列表
</NButton>
<span class="text-base font-bold text-slate-800">{{ sc?.no }} {{ sc?.title }}</span>
<NTag size="small" v-for="t in sc?.tags" :key="t.text" :class="t.cls">{{ t.text }}</NTag>
<span class="text-xs text-slate-400">示例变更{{ sc?.changeName }}</span>
</div>
<!-- 阶段进度条 -->
<div class="flex items-center gap-1 overflow-x-auto rounded-lg border bg-white p-3">
<div v-for="(st, i) in sc?.stages" :key="st" class="flex shrink-0 items-center gap-1">
<span
class="flex items-center gap-1.5 rounded-full px-2.5 py-1 text-xs font-medium"
:class="i < curStageIdx ? 'bg-emerald-50 text-emerald-600' : i === curStageIdx ? 'bg-[var(--theme-color)] text-white' : 'bg-slate-100 text-slate-400'"
:style="{ '--theme-color': themeStore.themeColor }"
>
<Icon v-if="i < curStageIdx" icon="ix:success" class="size-14px" />
<span v-else class="flex h-3.5 w-3.5 items-center justify-center rounded-full border text-[10px]" :class="i === curStageIdx ? 'border-white' : 'border-slate-300'">{{ Number(i) + 1 }}</span>
{{ st }}
</span>
<Icon v-if="Number(i) < sc?.stages?.length - 1" icon="formkit:right" class="size-12px text-slate-300" />
</div>
</div>
<!-- 控制条 -->
<div class="flex flex-wrap items-center gap-2 rounded-lg border bg-white px-3 py-2">
<NButton ghost size="small" class="text-xs" :disabled="cur === 0" @click="prev">
<Icon icon="formkit:left" class="size-12px" /> 上一步
</NButton>
<NButton type="primary" size="small" class="text-xs text-white" :disabled="finished" @click="next">
下一步 <Icon icon="formkit:right" class="size-12px" />
</NButton>
<NButton ghost size="small" class="text-xs" :disabled="finished && !playing" @click="playing = !playing">
<template v-if="playing"><Icon icon="material-symbols:pause-outline-rounded" class="size-14px" /> 暂停</template>
<template v-else><Icon icon="griddy-icons:play" class="size-14px" /> 自动播放</template>
</NButton>
<NButton ghost size="small" class="text-xs" @click="reset">
<Icon icon="system-uicons:reset" class="size-12px" /> 重置
</NButton>
<span class="ml-auto text-xs text-slate-400"> {{ cur + 1 }} / {{ sc?.steps?.length }} {{ finished ? " · 流程已走通 ✓" : "" }}</span>
</div>
<!-- 完成横幅 -->
<div v-if="finished" class="flex flex-wrap items-center gap-2 rounded-lg border border-emerald-200 bg-emerald-50 px-4 py-2.5 text-sm text-emerald-700">
<Icon icon="ix:success" class="size-16px" />
<b>流程已走通</b> {{ sc.steps.length }} {{ sc.stages.length }} 个阶段全部闭环全程操作留痕资料归档公告发布完整可追溯
</div>
<!-- 步骤时间线 -->
<div class="space-y-2">
<div
v-for="(s, i) in sc?.steps" :key="i" @click="playing = false; cur = Number(i)"
class="flex w-full cursor-pointer items-start gap-3 rounded-lg border px-3 py-2.5 text-left transition"
:class="i === cur ? 'border-[var(--theme-color)] bg-[#EAF0F9]/50 shadow-sm' : Number(i) < cur ? 'border-emerald-100 bg-emerald-50/30' : 'bg-white hover:border-slate-300'"
:style="{ '--theme-color': themeStore.themeColor }"
>
<span
class="mt-0.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-[11px] font-bold"
:class="Number(i) < cur ? 'bg-emerald-500 text-white' : i === cur ? 'bg-[var(--theme-color)] text-white' : 'bg-slate-100 text-slate-400'"
:style="{ '--theme-color': themeStore.themeColor }"
>
<Icon v-if="Number(i) < cur" icon="ix:success" class="size-16px" />
<template v-else>{{ Number(i) + 1 }}</template>
</span>
<span class="min-w-0 flex-1">
<span class="flex flex-wrap items-center gap-x-2 gap-y-1">
<span class="flex items-center gap-1 text-xs text-slate-500">
<Icon icon="lucide:user-round" class="size-14px" />
{{ s.actor }}{{ s.role ? `${s.role}` : "" }}
</span>
<span class="rounded bg-slate-100 px-1.5 text-[10px] text-slate-400">{{ s.stage }}</span>
<span class="text-sm font-medium" :class="i === cur ? 'text-[#17407F]' : 'text-slate-700'">{{ s.title }}</span>
<NTag v-if="s.branch" class="border-violet-200 bg-violet-50 text-violet-600">{{ s.branch }}</NTag>
<span class="ml-auto shrink-0 font-mono text-[11px] text-slate-400">{{ s.time }}</span>
<NButton ghost type="primary" size="tiny" v-if="jumpFor(s) && s.actor !== '系统'"
class="text-[10px] font-medium"
:title="`切换到${ROLES_NAME[jumpFor(s)!.role]}的「${VIEW_NAME[jumpFor(s)!.view] ?? jumpFor(s)!.view}」界面`"
@click.stop="jumpTo"
>
进入界面
</NButton>
</span>
<span v-if="i === cur && (s.detail || s.system)" class="mt-2 block space-y-1.5 border-t border-[#ACC6E5]/40 pt-2">
<span v-if="s.detail" class="block text-xs leading-relaxed text-slate-600"><b class="text-slate-500">内容</b>{{ s.detail }}</span>
<span
v-if="s.system"
class="flex items-start gap-1.5 text-xs leading-relaxed"
:style="{ color: themeStore.themeColor }"
>
<Icon icon="lucide:sparkles" class="mt-0.8 size-12px" />{{ s.system }}
</span>
</span>
</span>
</div>
</div>
</div>
</NCard>
</NSpace>
</template>
<style scoped>
</style>
<style scoped lang="scss">
.scroll {
height: calc(100vh - 185px);
overflow-y: auto;
}
</style>