|
|
@@ -0,0 +1,683 @@
|
|
|
+<template>
|
|
|
+ <view class="page-no-tab">
|
|
|
+ <!-- 顶部基础信息 -->
|
|
|
+ <view class="detail-head" :style="{ background: headerGradient }">
|
|
|
+ <view class="head-base">
|
|
|
+ <view class="avatar head-avatar">{{ staff.avatar }}</view>
|
|
|
+ <view class="head-main">
|
|
|
+ <text class="head-name">{{ staff.name }}</text>
|
|
|
+ <text class="head-sub">{{ staff.department }} · {{ staff.position }}</text>
|
|
|
+ </view>
|
|
|
+ <text class="status-tag" :class="statusInfo.class">{{ statusInfo.label }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="head-meta">
|
|
|
+ <view><text>员工编号</text><text>{{ staff.id }}</text></view>
|
|
|
+ <view><text>入职日期</text><text>{{ staff.entryDate || '—' }}</text></view>
|
|
|
+ <view><text>预计离职</text><text>{{ staff.expectedLeaveDate || '—' }}</text></view>
|
|
|
+ <view><text>最后工作日</text><text>{{ staff.lastWorkDate || '—' }}</text></view>
|
|
|
+ <view><text>交接人</text><text>{{ staff.handover?.person || '—' }}</text></view>
|
|
|
+ <view><text>审批人</text><text>系统自动匹配</text></view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 进度 -->
|
|
|
+ <view class="progress-card card section">
|
|
|
+ <view class="progress-head">
|
|
|
+ <text class="progress-title">{{ summary.title }}</text>
|
|
|
+ <text class="progress-value">{{ summary.value }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="progress-bar">
|
|
|
+ <view class="progress-fill" :style="{ width: summary.rate + '%' }"></view>
|
|
|
+ </view>
|
|
|
+ <text class="progress-tip">{{ summary.tip }}</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 离职流程时间线 -->
|
|
|
+ <view class="section-title"><text>离职流程</text></view>
|
|
|
+ <view class="timeline card section">
|
|
|
+ <view
|
|
|
+ v-for="(s, idx) in stages"
|
|
|
+ :key="s.key"
|
|
|
+ class="timeline-row"
|
|
|
+ :class="{ 'is-done': stageState(s.key) === 'done', 'is-current': stageState(s.key) === 'current' }"
|
|
|
+ >
|
|
|
+ <view class="timeline-node">
|
|
|
+ <view class="node-dot"></view>
|
|
|
+ <view v-if="idx !== stages.length - 1" class="node-line"></view>
|
|
|
+ </view>
|
|
|
+ <view class="timeline-main">
|
|
|
+ <text class="timeline-label">{{ s.label }}</text>
|
|
|
+ <text class="timeline-desc">{{ s.desc }}</text>
|
|
|
+ </view>
|
|
|
+ <text v-if="stageState(s.key) === 'done'" class="status-tag status-approved">已完成</text>
|
|
|
+ <text v-else-if="stageState(s.key) === 'current'" class="status-tag status-processing">进行中</text>
|
|
|
+ <text v-else class="status-tag status-pending">待开始</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 工作交接清单 -->
|
|
|
+ <view class="section-title">
|
|
|
+ <text>工作交接</text>
|
|
|
+ <text class="section-more">{{ handoverDone }} / {{ handoverItems.length }} 已完成</text>
|
|
|
+ </view>
|
|
|
+ <view class="materials card section">
|
|
|
+ <view v-for="item in handoverItems" :key="item" class="material-row press" @click="onToggleHandover(item)">
|
|
|
+ <view class="material-icon" :class="effectiveHandoverDone(item) ? 'is-on' : 'is-off'">
|
|
|
+ {{ effectiveHandoverDone(item) ? '✓' : '·' }}
|
|
|
+ </view>
|
|
|
+ <view class="material-main">
|
|
|
+ <text class="material-name">{{ item }}</text>
|
|
|
+ <text class="muted material-desc">点击勾选表示交接完成</text>
|
|
|
+ </view>
|
|
|
+ <text
|
|
|
+ class="status-tag"
|
|
|
+ :class="effectiveHandoverDone(item) ? 'status-approved' : 'status-pending'"
|
|
|
+ >
|
|
|
+ {{ effectiveHandoverDone(item) ? '已交接' : '待交接' }}
|
|
|
+ </text>
|
|
|
+ </view>
|
|
|
+ <view v-if="!handoverItems.length" class="muted empty-tip">尚未配置交接事项</view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 信息模块 -->
|
|
|
+ <view class="section-title"><text>信息模块</text></view>
|
|
|
+ <view class="info-grid section">
|
|
|
+ <view class="info-card card">
|
|
|
+ <view class="info-head">
|
|
|
+ <text class="info-title">合同解除</text>
|
|
|
+ <text class="status-tag" :class="moduleClass(staff.contractTermination?.status)">
|
|
|
+ {{ staff.contractTermination?.status || '—' }}
|
|
|
+ </text>
|
|
|
+ </view>
|
|
|
+ <view class="info-row"><text>解除方式</text><text>{{ staff.contractTermination?.method || '—' }}</text></view>
|
|
|
+ <view class="info-row"><text>截止日期</text><text>{{ staff.contractTermination?.deadline || '—' }}</text></view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="info-card card">
|
|
|
+ <view class="info-head">
|
|
|
+ <text class="info-title">社保停缴</text>
|
|
|
+ <text class="status-tag" :class="moduleClass(staff.socialSecurity?.status)">
|
|
|
+ {{ staff.socialSecurity?.status || '—' }}
|
|
|
+ </text>
|
|
|
+ </view>
|
|
|
+ <view class="info-row"><text>停缴月份</text><text>{{ staff.socialSecurity?.stopMonth || '—' }}</text></view>
|
|
|
+ <view class="info-row"><text>参保城市</text><text>{{ staff.socialSecurity?.city || '—' }}</text></view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="info-card card">
|
|
|
+ <view class="info-head">
|
|
|
+ <text class="info-title">工作交接</text>
|
|
|
+ <text class="status-tag" :class="moduleClass('已交接')">
|
|
|
+ {{ staff.handover?.progress || '—' }}
|
|
|
+ </text>
|
|
|
+ </view>
|
|
|
+ <view class="info-row"><text>交接人</text><text>{{ staff.handover?.person || '—' }}</text></view>
|
|
|
+ <view class="info-row"><text>截止日期</text><text>{{ staff.handover?.deadline || '—' }}</text></view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="info-card card">
|
|
|
+ <view class="info-head">
|
|
|
+ <text class="info-title">资产归还</text>
|
|
|
+ <text class="status-tag" :class="moduleClass(staff.assetReturn?.status)">
|
|
|
+ {{ staff.assetReturn?.status || '—' }}
|
|
|
+ </text>
|
|
|
+ </view>
|
|
|
+ <view class="info-row info-items">
|
|
|
+ <text
|
|
|
+ v-for="a in assetList"
|
|
|
+ :key="a.name"
|
|
|
+ class="info-chip asset-chip press"
|
|
|
+ :class="effectiveAssetReturned(a.name) ? 'is-returned' : 'is-pending'"
|
|
|
+ @click="onToggleAsset(a.name)"
|
|
|
+ >
|
|
|
+ {{ effectiveAssetReturned(a.name) ? '✓' : '○' }} {{ a.name }}
|
|
|
+ </text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="info-card card info-card-wide">
|
|
|
+ <view class="info-head">
|
|
|
+ <text class="info-title">财务结算</text>
|
|
|
+ <text class="status-tag" :class="moduleClass(staff.financeSettle?.status)">
|
|
|
+ {{ staff.financeSettle?.status || '—' }}
|
|
|
+ </text>
|
|
|
+ </view>
|
|
|
+ <view class="info-row"><text>最后月薪</text><text>{{ staff.financeSettle?.lastSalary || '—' }}</text></view>
|
|
|
+ <view class="info-row"><text>年假折算</text><text>{{ staff.financeSettle?.annualLeave || '—' }}</text></view>
|
|
|
+ <view class="info-row"><text>补偿金</text><text>{{ staff.financeSettle?.compensation || '—' }}</text></view>
|
|
|
+ <view class="info-row"><text>发放日期</text><text>{{ staff.financeSettle?.payoutDate || '—' }}</text></view>
|
|
|
+ <view class="info-row"><text>补偿方案</text><text>{{ staff.compensation?.scheme || '—' }}</text></view>
|
|
|
+ <view v-if="staff.compensation?.note" class="info-row"><text>说明</text><text>{{ staff.compensation.note }}</text></view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="info-card card info-card-wide">
|
|
|
+ <view class="info-head">
|
|
|
+ <text class="info-title">账号注销 / 离职证明</text>
|
|
|
+ <text class="status-tag" :class="moduleClass(staff.accountOff?.status)">
|
|
|
+ {{ staff.accountOff?.status || '—' }}
|
|
|
+ </text>
|
|
|
+ </view>
|
|
|
+ <view class="info-row info-items">
|
|
|
+ <text
|
|
|
+ v-for="i in (staff.accountOff?.items || [])"
|
|
|
+ :key="i"
|
|
|
+ class="info-chip"
|
|
|
+ >{{ i }}</text>
|
|
|
+ <text v-if="!staff.accountOff?.items?.length" class="muted">尚未注销任何账号</text>
|
|
|
+ </view>
|
|
|
+ <view class="info-row"><text>离职证明</text><text :class="staff.certificate?.status === '已开具' ? 'value' : 'placeholder'">{{ staff.certificate?.status || '—' }}</text></view>
|
|
|
+ <view v-if="staff.certificate?.needCopy" class="info-row"><text>副本需求</text><text>需要纸质副本</text></view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="footer-tip muted">
|
|
|
+ <text>演示版:操作按钮仅作展示,真实数据接入后可对接审批 / 资产交接等接口。</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="footer-actions">
|
|
|
+ <template v-if="['pending', 'processing'].includes(staff.status)">
|
|
|
+ <view class="ghost-btn" @click="onReject">驳回</view>
|
|
|
+ <view class="primary-btn" @click="onRemind">催办</view>
|
|
|
+ </template>
|
|
|
+ <template v-else-if="staff.status === 'overdue'">
|
|
|
+ <view class="ghost-btn" @click="onRemind">催办</view>
|
|
|
+ <view class="primary-btn" @click="onForceComplete">强制归档</view>
|
|
|
+ </template>
|
|
|
+ <template v-else>
|
|
|
+ <view class="primary-btn full" @click="onConfirm">确认离职</view>
|
|
|
+ </template>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { computed, ref, watch } from "vue";
|
|
|
+import { onLoad } from "@dcloudio/uni-app";
|
|
|
+import { useAuthGuard } from "@/hooks/useAuthGuard";
|
|
|
+import { resignings, resignStatus, resignStages } from "@/data/mock";
|
|
|
+import { go } from "@/utils/router";
|
|
|
+import {
|
|
|
+ getResignDetail,
|
|
|
+ setResignStatus,
|
|
|
+ addResignAssetReturn,
|
|
|
+ setResignLocalState,
|
|
|
+} from "@/utils/storage";
|
|
|
+
|
|
|
+useAuthGuard();
|
|
|
+
|
|
|
+const stages = resignStages;
|
|
|
+const statusMap = resignStatus;
|
|
|
+const id = ref("");
|
|
|
+
|
|
|
+const staff = computed(() => {
|
|
|
+ return resignings.find((r) => r.id === id.value) || resignings[0];
|
|
|
+});
|
|
|
+
|
|
|
+// 本地状态:来自 storage
|
|
|
+const localState = ref({});
|
|
|
+
|
|
|
+function refreshLocal(empId) {
|
|
|
+ const detail = getResignDetail(empId);
|
|
|
+ localState.value = detail || {};
|
|
|
+}
|
|
|
+
|
|
|
+watch(
|
|
|
+ () => staff.value.id,
|
|
|
+ (next) => refreshLocal(next),
|
|
|
+ { immediate: true },
|
|
|
+);
|
|
|
+
|
|
|
+const statusInfo = computed(() => statusMap[staff.value.status] || statusMap.pending);
|
|
|
+
|
|
|
+const headerGradient = computed(() => {
|
|
|
+ const map = {
|
|
|
+ pending: "linear-gradient(145deg,#f6bd16,#f59a23)",
|
|
|
+ processing: "linear-gradient(145deg,#2479e9,#1363ce)",
|
|
|
+ done: "linear-gradient(145deg,#52c41a,#389e0d)",
|
|
|
+ overdue: "linear-gradient(145deg,#ee4d4d,#cf1322)",
|
|
|
+ cancelled: "linear-gradient(145deg,#9aa1aa,#6f7782)",
|
|
|
+ };
|
|
|
+ return map[staff.value.status] || map.pending;
|
|
|
+});
|
|
|
+
|
|
|
+// 进度计算
|
|
|
+const summary = computed(() => {
|
|
|
+ const total = stages.length;
|
|
|
+ const currentIdx = stages.findIndex((s) => s.key === staff.value.stage);
|
|
|
+ const done = currentIdx >= 0 ? currentIdx : 0;
|
|
|
+ const rate = Math.round((done / total) * 100);
|
|
|
+ let tip = "";
|
|
|
+ if (staff.value.status === "overdue") {
|
|
|
+ tip = "已超过最后工作日,请尽快推进交接与结算。";
|
|
|
+ } else if (staff.value.status === "done") {
|
|
|
+ tip = "离职流程已全部完成。";
|
|
|
+ } else if (staff.value.status === "cancelled") {
|
|
|
+ tip = "本次离职申请已撤回。";
|
|
|
+ } else {
|
|
|
+ tip = `当前进行:${stages[currentIdx]?.label || "—"}`;
|
|
|
+ }
|
|
|
+ return { title: "离职进度", value: rate + "%", rate, tip };
|
|
|
+});
|
|
|
+
|
|
|
+function stageState(key) {
|
|
|
+ const currentIdx = stages.findIndex((s) => s.key === staff.value.stage);
|
|
|
+ const idx = stages.findIndex((s) => s.key === key);
|
|
|
+ if (idx < currentIdx) return "done";
|
|
|
+ if (idx === currentIdx) return "current";
|
|
|
+ return "pending";
|
|
|
+}
|
|
|
+
|
|
|
+function moduleClass(status) {
|
|
|
+ const s = String(status || "");
|
|
|
+ if (
|
|
|
+ s === "已解除" || s === "已停缴" || s === "已交接" || s === "已归还" ||
|
|
|
+ s === "已结算" || s === "已注销" || s === "已开具" || s === "已签订" ||
|
|
|
+ s === "已发放" || s === "已完成"
|
|
|
+ ) {
|
|
|
+ return "status-approved";
|
|
|
+ }
|
|
|
+ if (s === "办理中" || s === "进行中" || s === "交接中" || s === "部分归还" || s === "部分注销") {
|
|
|
+ return "status-processing";
|
|
|
+ }
|
|
|
+ return "status-pending";
|
|
|
+}
|
|
|
+
|
|
|
+// 工作交接事项(来自 mock)
|
|
|
+const handoverItems = computed(() => staff.value.handover?.items || []);
|
|
|
+
|
|
|
+// 资产列表
|
|
|
+const assetList = computed(() => staff.value.assetReturn?.items || []);
|
|
|
+
|
|
|
+const handoverDone = computed(
|
|
|
+ () => handoverItems.value.filter((x) => effectiveHandoverDone(x)).length,
|
|
|
+);
|
|
|
+
|
|
|
+// 资产是否已归还(mock + 本地覆盖)
|
|
|
+function effectiveAssetReturned(name) {
|
|
|
+ const override = localState.value?.assetReturnMap?.[name];
|
|
|
+ if (typeof override === "boolean") return override;
|
|
|
+ const m = staff.value.assetReturn?.items?.find((x) => x.name === name);
|
|
|
+ return m ? !!m.returned : false;
|
|
|
+}
|
|
|
+
|
|
|
+// 工作交接事项是否已完成(mock + 本地)
|
|
|
+function effectiveHandoverDone(name) {
|
|
|
+ const override = localState.value?.handoverDone?.[name];
|
|
|
+ if (typeof override === "boolean") return override;
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+function onToggleHandover(name) {
|
|
|
+ const next = !effectiveHandoverDone(name);
|
|
|
+ const map = { ...(localState.value?.handoverDone || {}) };
|
|
|
+ map[name] = next;
|
|
|
+ setResignLocalState(staff.value.id, { handoverDone: map });
|
|
|
+ refreshLocal(staff.value.id);
|
|
|
+}
|
|
|
+
|
|
|
+function onToggleAsset(name) {
|
|
|
+ const next = !effectiveAssetReturned(name);
|
|
|
+ addResignAssetReturn(staff.value.id, name, next);
|
|
|
+ refreshLocal(staff.value.id);
|
|
|
+}
|
|
|
+
|
|
|
+function onRemind() {
|
|
|
+ uni.showToast({ title: "演示版:催办消息未发送", icon: "none" });
|
|
|
+}
|
|
|
+
|
|
|
+function onReject() {
|
|
|
+ uni.showModal({
|
|
|
+ title: "驳回离职申请",
|
|
|
+ content: `确认驳回「${staff.value.name}」的离职申请?该操作仅修改本机演示数据。`,
|
|
|
+ success: (r) => {
|
|
|
+ if (!r.confirm) return;
|
|
|
+ setResignStatus(staff.value.id, "cancelled");
|
|
|
+ refreshLocal(staff.value.id);
|
|
|
+ uni.showToast({ title: "已驳回(演示版)", icon: "none" });
|
|
|
+ },
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+function onForceComplete() {
|
|
|
+ uni.showModal({
|
|
|
+ title: "强制归档",
|
|
|
+ content: "演示版:把该离职流程标记为已完成。",
|
|
|
+ success: (r) => {
|
|
|
+ if (!r.confirm) return;
|
|
|
+ setResignStatus(staff.value.id, "done");
|
|
|
+ refreshLocal(staff.value.id);
|
|
|
+ uni.showToast({ title: "已归档", icon: "success" });
|
|
|
+ },
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+function onConfirm() {
|
|
|
+ uni.showModal({
|
|
|
+ title: "确认离职",
|
|
|
+ content: "演示版:将该员工标记为已离职(实际不会修改 mock 数据)。",
|
|
|
+ success: (r) => {
|
|
|
+ if (r.confirm) {
|
|
|
+ setResignStatus(staff.value.id, "done");
|
|
|
+ refreshLocal(staff.value.id);
|
|
|
+ uni.showToast({ title: "操作成功", icon: "success" });
|
|
|
+ setTimeout(() => go("/pages/manage/index?type=resign"), 600);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+onLoad((options) => {
|
|
|
+ id.value = options?.id || "";
|
|
|
+ uni.setNavigationBarTitle({ title: "离职详情" });
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.detail-head {
|
|
|
+ padding: 32rpx 28rpx 28rpx;
|
|
|
+ color: #fff;
|
|
|
+}
|
|
|
+.head-base {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+.head-avatar {
|
|
|
+ width: 96rpx;
|
|
|
+ height: 96rpx;
|
|
|
+ font-size: 36rpx;
|
|
|
+ border-radius: 24rpx;
|
|
|
+ background: rgba(255, 255, 255, 0.22);
|
|
|
+}
|
|
|
+.head-main {
|
|
|
+ flex: 1;
|
|
|
+ margin-left: 20rpx;
|
|
|
+}
|
|
|
+.head-name {
|
|
|
+ display: block;
|
|
|
+ font-size: 36rpx;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+.head-sub {
|
|
|
+ display: block;
|
|
|
+ margin-top: 8rpx;
|
|
|
+ font-size: 24rpx;
|
|
|
+ opacity: 0.86;
|
|
|
+}
|
|
|
+.head-meta {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: 1fr 1fr;
|
|
|
+ gap: 18rpx 28rpx;
|
|
|
+ margin-top: 28rpx;
|
|
|
+ padding-top: 24rpx;
|
|
|
+ border-top: 1rpx solid rgba(255, 255, 255, 0.2);
|
|
|
+}
|
|
|
+.head-meta > view text {
|
|
|
+ display: block;
|
|
|
+ font-size: 22rpx;
|
|
|
+ opacity: 0.78;
|
|
|
+}
|
|
|
+.head-meta > view text + text {
|
|
|
+ margin-top: 6rpx;
|
|
|
+ font-size: 26rpx;
|
|
|
+ font-weight: 600;
|
|
|
+ opacity: 1;
|
|
|
+}
|
|
|
+.progress-card {
|
|
|
+ padding: 28rpx;
|
|
|
+}
|
|
|
+.progress-head {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+}
|
|
|
+.progress-title {
|
|
|
+ font-size: 28rpx;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+.progress-value {
|
|
|
+ font-size: 32rpx;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #ee6666;
|
|
|
+}
|
|
|
+.progress-bar {
|
|
|
+ margin-top: 18rpx;
|
|
|
+ height: 12rpx;
|
|
|
+ border-radius: 6rpx;
|
|
|
+ background: #edf0f2;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+.progress-fill {
|
|
|
+ height: 100%;
|
|
|
+ background: linear-gradient(90deg, #f08a8a, #ee6666);
|
|
|
+ border-radius: 6rpx;
|
|
|
+}
|
|
|
+.progress-tip {
|
|
|
+ display: block;
|
|
|
+ margin-top: 14rpx;
|
|
|
+ color: #8b939f;
|
|
|
+ font-size: 23rpx;
|
|
|
+}
|
|
|
+.section-title {
|
|
|
+ padding: 28rpx 28rpx 12rpx;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ font-size: 26rpx;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+.section-more {
|
|
|
+ font-weight: 400;
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: #8b939f;
|
|
|
+}
|
|
|
+.timeline {
|
|
|
+ padding: 8rpx 0;
|
|
|
+}
|
|
|
+.timeline-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-start;
|
|
|
+ padding: 18rpx 28rpx;
|
|
|
+}
|
|
|
+.timeline-row.is-done .node-dot {
|
|
|
+ background: #52c41a;
|
|
|
+}
|
|
|
+.timeline-row.is-done .timeline-label {
|
|
|
+ color: #52c41a;
|
|
|
+}
|
|
|
+.timeline-row.is-current .node-dot {
|
|
|
+ background: #1677ff;
|
|
|
+ box-shadow: 0 0 0 6rpx rgba(22, 119, 255, 0.16);
|
|
|
+}
|
|
|
+.timeline-row.is-current .timeline-label {
|
|
|
+ color: #1677ff;
|
|
|
+}
|
|
|
+.timeline-node {
|
|
|
+ width: 28rpx;
|
|
|
+ position: relative;
|
|
|
+ padding-top: 12rpx;
|
|
|
+}
|
|
|
+.node-dot {
|
|
|
+ width: 16rpx;
|
|
|
+ height: 16rpx;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: #c4c8ce;
|
|
|
+ margin: 0 auto;
|
|
|
+}
|
|
|
+.node-line {
|
|
|
+ position: absolute;
|
|
|
+ top: 32rpx;
|
|
|
+ left: 50%;
|
|
|
+ bottom: -32rpx;
|
|
|
+ width: 2rpx;
|
|
|
+ background: #edf0f2;
|
|
|
+ transform: translateX(-50%);
|
|
|
+}
|
|
|
+.timeline-main {
|
|
|
+ flex: 1;
|
|
|
+ margin-left: 18rpx;
|
|
|
+}
|
|
|
+.timeline-label {
|
|
|
+ display: block;
|
|
|
+ font-size: 28rpx;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #4b5563;
|
|
|
+}
|
|
|
+.timeline-desc {
|
|
|
+ display: block;
|
|
|
+ margin-top: 4rpx;
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: #8b939f;
|
|
|
+}
|
|
|
+.timeline-row > .status-tag {
|
|
|
+ margin-top: 6rpx;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+.materials {
|
|
|
+ padding: 8rpx 0;
|
|
|
+}
|
|
|
+.material-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-start;
|
|
|
+ padding: 24rpx 28rpx;
|
|
|
+}
|
|
|
+.material-row + .material-row {
|
|
|
+ border-top: 1rpx solid #edf0f2;
|
|
|
+}
|
|
|
+.material-icon {
|
|
|
+ width: 56rpx;
|
|
|
+ height: 56rpx;
|
|
|
+ border-radius: 16rpx;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ font-size: 32rpx;
|
|
|
+ font-weight: 700;
|
|
|
+ margin-top: 4rpx;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+.material-icon.is-on {
|
|
|
+ background: #eaf8ef;
|
|
|
+ color: #07893f;
|
|
|
+}
|
|
|
+.material-icon.is-off {
|
|
|
+ background: #fff2df;
|
|
|
+ color: #d9780b;
|
|
|
+}
|
|
|
+.material-main {
|
|
|
+ flex: 1;
|
|
|
+ margin-left: 18rpx;
|
|
|
+ min-width: 0;
|
|
|
+}
|
|
|
+.material-name {
|
|
|
+ display: block;
|
|
|
+ font-size: 28rpx;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #1f2329;
|
|
|
+}
|
|
|
+.material-desc {
|
|
|
+ display: block;
|
|
|
+ margin-top: 6rpx;
|
|
|
+ font-size: 22rpx;
|
|
|
+}
|
|
|
+.material-row > .status-tag {
|
|
|
+ flex-shrink: 0;
|
|
|
+ margin-top: 8rpx;
|
|
|
+ margin-left: 12rpx;
|
|
|
+}
|
|
|
+.empty-tip {
|
|
|
+ padding: 32rpx 28rpx;
|
|
|
+ font-size: 22rpx;
|
|
|
+}
|
|
|
+.info-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: 1fr 1fr;
|
|
|
+ gap: 18rpx;
|
|
|
+ margin: 18rpx 24rpx 0;
|
|
|
+}
|
|
|
+.info-card {
|
|
|
+ padding: 24rpx;
|
|
|
+}
|
|
|
+.info-card-wide {
|
|
|
+ grid-column: span 2;
|
|
|
+}
|
|
|
+.info-head {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ margin-bottom: 14rpx;
|
|
|
+}
|
|
|
+.info-title {
|
|
|
+ font-size: 28rpx;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+.info-row {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ font-size: 24rpx;
|
|
|
+ padding: 8rpx 0;
|
|
|
+ color: #4b5563;
|
|
|
+}
|
|
|
+.info-row text:first-child {
|
|
|
+ color: #8b939f;
|
|
|
+}
|
|
|
+.info-row.info-items {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 12rpx;
|
|
|
+ justify-content: flex-end;
|
|
|
+ padding-top: 12rpx;
|
|
|
+}
|
|
|
+.info-chip {
|
|
|
+ padding: 6rpx 16rpx;
|
|
|
+ border-radius: 999rpx;
|
|
|
+ background: #e7f0ff;
|
|
|
+ color: #1f6feb;
|
|
|
+ font-size: 22rpx;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+.asset-chip.is-returned {
|
|
|
+ background: #eaf8ef;
|
|
|
+ color: #07893f;
|
|
|
+}
|
|
|
+.asset-chip.is-pending {
|
|
|
+ background: #fff2df;
|
|
|
+ color: #d9780b;
|
|
|
+}
|
|
|
+.footer-tip {
|
|
|
+ padding: 32rpx 28rpx 8rpx;
|
|
|
+ font-size: 22rpx;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+.footer-actions {
|
|
|
+ display: flex;
|
|
|
+ gap: 18rpx;
|
|
|
+ padding: 24rpx 24rpx calc(40rpx + env(safe-area-inset-bottom));
|
|
|
+}
|
|
|
+.footer-actions > view {
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+.ghost-btn {
|
|
|
+ height: 88rpx;
|
|
|
+ border-radius: 12rpx;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ font-size: 28rpx;
|
|
|
+ background: #fff;
|
|
|
+ color: #4b5563;
|
|
|
+ border: 1rpx solid #d7dce2;
|
|
|
+}
|
|
|
+.primary-btn {
|
|
|
+ height: 88rpx;
|
|
|
+ border-radius: 12rpx;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ font-size: 28rpx;
|
|
|
+ background: #ee6666;
|
|
|
+ color: #fff;
|
|
|
+}
|
|
|
+.primary-btn.full {
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+</style>
|