chore: 统一换行符为LF,与.gitattributes规范一致(内容无变化)

This commit is contained in:
2026-08-26 10:08:33 +08:00
parent c78f0660f7
commit 8657f83c16
34 changed files with 12737 additions and 12737 deletions
+184 -184
View File
@@ -1,184 +1,184 @@
<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>
<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>