| | |
| | | <template> |
| | | <view class="approve-page"> |
| | | |
| | | <PageHeader title="审核" @back="goBack" /> |
| | | |
| | | <PageHeader title="审核" |
| | | @back="goBack" /> |
| | | <!-- 申请信息 --> |
| | | <view class="application-info"> |
| | | <view class="info-header"> |
| | |
| | | <text class="info-label">申请日期</text> |
| | | <text class="info-value">{{ approvalData.approveTime }}</text> |
| | | </view> |
| | | |
| | | <!-- approveType=2 请假相关字段 --> |
| | | <template v-if="approvalData.approveType === 2"> |
| | | <view class="info-row"> |
| | |
| | | <text class="info-value">{{ approvalData.endDate || '-' }}</text> |
| | | </view> |
| | | </template> |
| | | |
| | | <!-- approveType=3 出差相关字段 --> |
| | | <view v-if="approvalData.approveType === 3" class="info-row"> |
| | | <view v-if="approvalData.approveType === 3" |
| | | class="info-row"> |
| | | <text class="info-label">出差地点</text> |
| | | <text class="info-value">{{ approvalData.location || '-' }}</text> |
| | | </view> |
| | | |
| | | <!-- approveType=4 报销相关字段 --> |
| | | <view v-if="approvalData.approveType === 4" class="info-row"> |
| | | <view v-if="approvalData.approveType === 4" |
| | | class="info-row"> |
| | | <text class="info-label">报销金额</text> |
| | | <text class="info-value">{{ approvalData.price ? `¥${approvalData.price}` : '-' }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 审批流程 --> |
| | | <view class="approval-process"> |
| | | <view class="process-header"> |
| | | <text class="process-title">审批流程</text> |
| | | </view> |
| | | |
| | | <view class="process-steps"> |
| | | <view |
| | | v-for="(step, index) in approvalSteps" |
| | | :key="index" |
| | | class="process-step" |
| | | :class="{ |
| | | <view v-for="(step, index) in approvalSteps" |
| | | :key="index" |
| | | class="process-step" |
| | | :class="{ |
| | | 'completed': step.status === 'completed', |
| | | 'current': step.status === 'current', |
| | | 'pending': step.status === 'pending', |
| | | 'rejected': step.status === 'rejected' |
| | | }" |
| | | > |
| | | }"> |
| | | <view class="step-indicator"> |
| | | <view class="step-dot"> |
| | | <text v-if="step.status === 'completed'" class="step-icon">✓</text> |
| | | <text v-else-if="step.status === 'rejected'" class="step-icon">✗</text> |
| | | <text v-else class="step-number">{{ index + 1 }}</text> |
| | | <text v-if="step.status === 'completed'" |
| | | class="step-icon">✓</text> |
| | | <text v-else-if="step.status === 'rejected'" |
| | | class="step-icon">✗</text> |
| | | <text v-else |
| | | class="step-number">{{ index + 1 }}</text> |
| | | </view> |
| | | <view v-if="index < approvalSteps.length - 1" class="step-line"></view> |
| | | <view v-if="index < approvalSteps.length - 1" |
| | | class="step-line"></view> |
| | | </view> |
| | | |
| | | <view class="step-content"> |
| | | <view class="step-info"> |
| | | <text class="step-title">{{ step.title }}</text> |
| | | <text class="step-approver">{{ step.approverName }}</text> |
| | | <text v-if="step.approveTime" class="step-time">{{ step.approveTime }}</text> |
| | | <text v-if="step.approveTime" |
| | | class="step-time">{{ step.approveTime }}</text> |
| | | </view> |
| | | |
| | | <view v-if="step.opinion" class="step-opinion"> |
| | | <view v-if="step.opinion" |
| | | class="step-opinion"> |
| | | <text class="opinion-label">审批意见:</text> |
| | | <text class="opinion-content">{{ step.opinion }}</text> |
| | | </view> |
| | | <!-- 签名展示 --> |
| | | <view v-if="step.urlTem" class="step-opinion" style="margin-top:8px;"> |
| | | <view v-if="step.urlTem" |
| | | class="step-opinion" |
| | | style="margin-top:8px;"> |
| | | <text class="opinion-label">签名:</text> |
| | | <image :src="step.urlTem" mode="widthFix" style="width:180px;border-radius:6px;border:1px solid #eee;" /> |
| | | <image :src="step.urlTem" |
| | | mode="widthFix" |
| | | style="width:180px;border-radius:6px;border:1px solid #eee;" /> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 审核意见输入 --> |
| | | <view v-if="canApprove" class="approval-input"> |
| | | <view v-if="canApprove" |
| | | class="approval-input"> |
| | | <view class="input-header"> |
| | | <text class="input-title">审核意见</text> |
| | | </view> |
| | | |
| | | <view class="input-content"> |
| | | <u-textarea |
| | | v-model="approvalOpinion" |
| | | rows="4" |
| | | placeholder="请输入审核意见" |
| | | maxlength="200" |
| | | count |
| | | /> |
| | | <u-textarea v-model="approvalOpinion" |
| | | rows="4" |
| | | placeholder="请输入审核意见" |
| | | maxlength="200" |
| | | count /> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 底部操作按钮 --> |
| | | <view v-if="canApprove" class="footer-actions"> |
| | | <u-button class="reject-btn" @click="handleReject">驳回</u-button> |
| | | <u-button class="approve-btn" @click="handleApprove">通过</u-button> |
| | | <view v-if="canApprove" |
| | | class="footer-actions"> |
| | | <u-button class="reject-btn" |
| | | @click="handleReject">驳回</u-button> |
| | | <u-button class="approve-btn" |
| | | @click="handleApprove">通过</u-button> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, computed } from 'vue' |
| | | import { approveProcessGetInfo, approveProcessDetails, updateApproveNode } from '@/api/collaborativeApproval/approvalProcess' |
| | | import useUserStore from '@/store/modules/user' |
| | | const showToast = (message) => { |
| | | uni.showToast({ |
| | | title: message, |
| | | icon: 'none' |
| | | }) |
| | | } |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import { ref, onMounted, computed } from "vue"; |
| | | import { |
| | | approveProcessGetInfo, |
| | | approveProcessDetails, |
| | | updateApproveNode, |
| | | } from "@/api/collaborativeApproval/approvalProcess"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | const showToast = message => { |
| | | uni.showToast({ |
| | | title: message, |
| | | icon: "none", |
| | | }); |
| | | }; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | |
| | | const userStore = useUserStore() |
| | | const approvalData = ref({}) |
| | | const approvalSteps = ref([]) |
| | | const approvalOpinion = ref('') |
| | | const approveId = ref('') |
| | | const userStore = useUserStore(); |
| | | const approvalData = ref({}); |
| | | const approvalSteps = ref([]); |
| | | const approvalOpinion = ref(""); |
| | | const approveId = ref(""); |
| | | |
| | | // 从详情接口字段对齐 canApprove:仅当有 isShen 的节点时可审批 |
| | | const canApprove = computed(() => { |
| | | return approvalSteps.value.some(step => step.isShen === true) |
| | | }) |
| | | // 从详情接口字段对齐 canApprove:仅当有 isShen 的节点时可审批 |
| | | const canApprove = computed(() => { |
| | | return approvalSteps.value.some(step => step.isShen === true); |
| | | }); |
| | | |
| | | onMounted(() => { |
| | | approveId.value = uni.getStorageSync('approveId') |
| | | if (approveId.value) { |
| | | loadApprovalData() |
| | | } |
| | | }) |
| | | |
| | | const loadApprovalData = () => { |
| | | // 基本申请信息 |
| | | approveProcessGetInfo({ id: approveId.value }).then(res => { |
| | | approvalData.value = res.data || {} |
| | | }) |
| | | // 审批节点详情 |
| | | approveProcessDetails(approveId.value).then(res => { |
| | | const list = Array.isArray(res.data) ? res.data : [] |
| | | // 保存原始节点数据供提交使用 |
| | | activities.value = list |
| | | |
| | | approvalSteps.value = list.map((it, idx) => { |
| | | // 节点状态映射:1=通过,2=不通过,否则看是否当前(isShen),再默认为待处理 |
| | | let status = 'pending' |
| | | if (it.approveNodeStatus === 1) status = 'completed' |
| | | else if (it.approveNodeStatus === 2) status = 'rejected' |
| | | else if (it.isShen) status = 'current' |
| | | return { |
| | | title: `第${idx + 1}步审批`, |
| | | approverName: it.approveNodeUser || '未知用户', |
| | | status, |
| | | approveTime: it.approveTime || null, |
| | | opinion: it.approveNodeReason || '', |
| | | urlTem: it.urlTem || '', |
| | | isShen: !!it.isShen |
| | | } |
| | | }) |
| | | }) |
| | | } |
| | | |
| | | const goBack = () => { |
| | | uni.removeStorageSync('approveId'); |
| | | uni.navigateBack() |
| | | } |
| | | |
| | | const submitForm = (status) => { |
| | | // 可选:校验审核意见 |
| | | if (!approvalOpinion.value?.trim()) { |
| | | showToast('请输入审核意见') |
| | | return |
| | | } |
| | | // 找到当前可审批节点 |
| | | const filteredActivities = activities.value.filter(activity => activity.isShen) |
| | | if (!filteredActivities.length) { |
| | | showToast('当前无可审批节点') |
| | | return |
| | | } |
| | | // 写入状态和意见 |
| | | filteredActivities[0].approveNodeStatus = status |
| | | filteredActivities[0].approveNodeReason = approvalOpinion.value || '' |
| | | // 计算是否为最后一步 |
| | | const isLast = activities.value.findIndex(a => a.isShen) === activities.value.length - 1 |
| | | // 调用后端 |
| | | updateApproveNode({ ...filteredActivities[0], isLast }).then(() => { |
| | | const msg = status === 1 ? '审批通过' : '审批已驳回' |
| | | showToast(msg) |
| | | // 提示后返回上一个页面 |
| | | setTimeout(() => { |
| | | goBack() // 内部是 uni.navigateBack() |
| | | }, 800) |
| | | }) |
| | | } |
| | | |
| | | const handleApprove = () => { |
| | | uni.showModal({ |
| | | title: '确认操作', |
| | | content: '确定要通过此审批吗?', |
| | | success: (res) => { |
| | | if (res.confirm) submitForm(1) |
| | | onMounted(() => { |
| | | approveId.value = uni.getStorageSync("approveId"); |
| | | if (approveId.value) { |
| | | loadApprovalData(); |
| | | } |
| | | }) |
| | | } |
| | | }); |
| | | |
| | | const handleReject = () => { |
| | | uni.showModal({ |
| | | title: '确认操作', |
| | | content: '确定要驳回此审批吗?', |
| | | success: (res) => { |
| | | if (res.confirm) submitForm(2) |
| | | const loadApprovalData = () => { |
| | | // 基本申请信息 |
| | | approveProcessGetInfo({ id: approveId.value }).then(res => { |
| | | approvalData.value = res.data || {}; |
| | | }); |
| | | // 审批节点详情 |
| | | approveProcessDetails(approveId.value).then(res => { |
| | | const list = Array.isArray(res.data) ? res.data : []; |
| | | // 保存原始节点数据供提交使用 |
| | | activities.value = list; |
| | | |
| | | approvalSteps.value = list.map((it, idx) => { |
| | | // 节点状态映射:1=通过,2=不通过,否则看是否当前(isShen),再默认为待处理 |
| | | let status = "pending"; |
| | | if (it.approveNodeStatus === 1) status = "completed"; |
| | | else if (it.approveNodeStatus === 2) status = "rejected"; |
| | | else if (it.isShen) status = "current"; |
| | | return { |
| | | title: `第${idx + 1}步审批`, |
| | | approverName: it.approveNodeUser || "未知用户", |
| | | status, |
| | | approveTime: it.approveTime || null, |
| | | opinion: it.approveNodeReason || "", |
| | | urlTem: it.urlTem || "", |
| | | isShen: !!it.isShen, |
| | | }; |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | const goBack = () => { |
| | | uni.removeStorageSync("approveId"); |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | const submitForm = status => { |
| | | // 可选:校验审核意见 |
| | | if (!approvalOpinion.value?.trim()) { |
| | | showToast("请输入审核意见"); |
| | | return; |
| | | } |
| | | }) |
| | | } |
| | | // 原始节点数据(用于提交逻辑) |
| | | const activities = ref([]) |
| | | // 找到当前可审批节点 |
| | | const filteredActivities = activities.value.filter( |
| | | activity => activity.isShen |
| | | ); |
| | | if (!filteredActivities.length) { |
| | | showToast("当前无可审批节点"); |
| | | return; |
| | | } |
| | | // 写入状态和意见 |
| | | filteredActivities[0].approveNodeStatus = status; |
| | | filteredActivities[0].approveNodeReason = approvalOpinion.value || ""; |
| | | // 计算是否为最后一步 |
| | | const isLast = |
| | | activities.value.findIndex(a => a.isShen) === activities.value.length - 1; |
| | | // 调用后端 |
| | | updateApproveNode({ ...filteredActivities[0], isLast }).then(() => { |
| | | const msg = status === 1 ? "审批通过" : "审批已驳回"; |
| | | showToast(msg); |
| | | // 提示后返回上一个页面 |
| | | setTimeout(() => { |
| | | goBack(); // 内部是 uni.navigateBack() |
| | | }, 800); |
| | | }); |
| | | }; |
| | | |
| | | const handleApprove = () => { |
| | | uni.showModal({ |
| | | title: "确认操作", |
| | | content: "确定要通过此审批吗?", |
| | | success: res => { |
| | | if (res.confirm) submitForm(1); |
| | | }, |
| | | }); |
| | | }; |
| | | |
| | | const handleReject = () => { |
| | | uni.showModal({ |
| | | title: "确认操作", |
| | | content: "确定要驳回此审批吗?", |
| | | success: res => { |
| | | if (res.confirm) submitForm(2); |
| | | }, |
| | | }); |
| | | }; |
| | | // 原始节点数据(用于提交逻辑) |
| | | const activities = ref([]); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .approve-page { |
| | | min-height: 100vh; |
| | | background: #f8f9fa; |
| | | padding-bottom: 80px; |
| | | } |
| | | |
| | | .header { |
| | | display: flex; |
| | | align-items: center; |
| | | background: #fff; |
| | | padding: 16px 20px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | position: sticky; |
| | | top: 0; |
| | | z-index: 100; |
| | | } |
| | | |
| | | .title { |
| | | flex: 1; |
| | | text-align: center; |
| | | font-size: 18px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | |
| | | .application-info { |
| | | background: #fff; |
| | | margin: 16px; |
| | | border-radius: 12px; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .info-header { |
| | | padding: 16px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | background: #f8f9fa; |
| | | } |
| | | |
| | | .info-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | |
| | | .info-content { |
| | | padding: 16px; |
| | | } |
| | | |
| | | .info-row { |
| | | display: flex; |
| | | align-items: center; |
| | | margin-bottom: 12px; |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | .approve-page { |
| | | min-height: 100vh; |
| | | background: #f8f9fa; |
| | | padding-bottom: 80px; |
| | | } |
| | | } |
| | | |
| | | .info-label { |
| | | font-size: 14px; |
| | | color: #666; |
| | | width: 80px; |
| | | flex-shrink: 0; |
| | | } |
| | | .header { |
| | | display: flex; |
| | | align-items: center; |
| | | background: #fff; |
| | | padding: 16px 20px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | position: sticky; |
| | | top: 0; |
| | | z-index: 100; |
| | | } |
| | | |
| | | .info-value { |
| | | font-size: 14px; |
| | | color: #333; |
| | | flex: 1; |
| | | } |
| | | .title { |
| | | flex: 1; |
| | | text-align: center; |
| | | font-size: 18px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | |
| | | .approval-process { |
| | | background: #fff; |
| | | margin: 16px; |
| | | border-radius: 12px; |
| | | overflow: hidden; |
| | | } |
| | | .application-info { |
| | | background: #fff; |
| | | margin: 16px; |
| | | border-radius: 12px; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .process-header { |
| | | padding: 16px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | background: #f8f9fa; |
| | | } |
| | | .info-header { |
| | | padding: 16px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | background: #f8f9fa; |
| | | } |
| | | |
| | | .process-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | .info-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | |
| | | .process-steps { |
| | | padding: 20px; |
| | | } |
| | | .info-content { |
| | | padding: 16px; |
| | | } |
| | | |
| | | .process-step { |
| | | display: flex; |
| | | position: relative; |
| | | margin-bottom: 24px; |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | |
| | | .step-line { |
| | | display: none; |
| | | .info-row { |
| | | display: flex; |
| | | align-items: center; |
| | | margin-bottom: 12px; |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .step-indicator { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | margin-right: 16px; |
| | | } |
| | | .info-label { |
| | | font-size: 14px; |
| | | color: #666; |
| | | width: 80px; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .step-dot { |
| | | width: 32px; |
| | | height: 32px; |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | position: relative; |
| | | z-index: 2; |
| | | } |
| | | .info-value { |
| | | font-size: 14px; |
| | | color: #333; |
| | | flex: 1; |
| | | } |
| | | |
| | | .process-step.completed .step-dot { |
| | | background: #52c41a; |
| | | color: #fff; |
| | | } |
| | | .approval-process { |
| | | background: #fff; |
| | | margin: 16px; |
| | | border-radius: 12px; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .process-step.current .step-dot { |
| | | background: #1890ff; |
| | | color: #fff; |
| | | animation: pulse 2s infinite; |
| | | } |
| | | .process-header { |
| | | padding: 16px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | background: #f8f9fa; |
| | | } |
| | | |
| | | .process-step.pending .step-dot { |
| | | background: #d9d9d9; |
| | | color: #999; |
| | | } |
| | | .process-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | |
| | | .step-line { |
| | | width: 2px; |
| | | height: 40px; |
| | | background: #d9d9d9; |
| | | margin-top: 8px; |
| | | } |
| | | .process-steps { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .process-step.completed .step-line { |
| | | background: #52c41a; |
| | | } |
| | | .process-step { |
| | | display: flex; |
| | | position: relative; |
| | | margin-bottom: 24px; |
| | | |
| | | .process-step.rejected .step-dot { |
| | | background: #ff4d4f; |
| | | color: #fff; |
| | | } |
| | | .process-step.rejected .step-line { |
| | | background: #ff4d4f; |
| | | } |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | |
| | | .step-content { |
| | | flex: 1; |
| | | padding-top: 4px; |
| | | } |
| | | .step-line { |
| | | display: none; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .step-info { |
| | | margin-bottom: 8px; |
| | | } |
| | | .step-indicator { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | margin-right: 16px; |
| | | } |
| | | |
| | | .step-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | display: block; |
| | | margin-bottom: 4px; |
| | | } |
| | | .step-dot { |
| | | width: 32px; |
| | | height: 32px; |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | position: relative; |
| | | z-index: 2; |
| | | } |
| | | |
| | | .step-approver { |
| | | font-size: 14px; |
| | | color: #666; |
| | | display: block; |
| | | margin-bottom: 4px; |
| | | } |
| | | .process-step.completed .step-dot { |
| | | background: #52c41a; |
| | | color: #fff; |
| | | } |
| | | |
| | | .step-time { |
| | | font-size: 12px; |
| | | color: #999; |
| | | display: block; |
| | | } |
| | | .process-step.current .step-dot { |
| | | background: #1890ff; |
| | | color: #fff; |
| | | animation: pulse 2s infinite; |
| | | } |
| | | |
| | | .step-opinion { |
| | | background: #f8f9fa; |
| | | padding: 12px; |
| | | border-radius: 8px; |
| | | border-left: 4px solid #52c41a; |
| | | } |
| | | .process-step.pending .step-dot { |
| | | background: #d9d9d9; |
| | | color: #999; |
| | | } |
| | | |
| | | .opinion-label { |
| | | font-size: 12px; |
| | | color: #666; |
| | | display: block; |
| | | margin-bottom: 4px; |
| | | } |
| | | .step-line { |
| | | width: 2px; |
| | | height: 40px; |
| | | background: #d9d9d9; |
| | | margin-top: 8px; |
| | | } |
| | | |
| | | .opinion-content { |
| | | font-size: 14px; |
| | | color: #333; |
| | | line-height: 1.5; |
| | | } |
| | | .process-step.completed .step-line { |
| | | background: #52c41a; |
| | | } |
| | | |
| | | .approval-input { |
| | | background: #fff; |
| | | margin: 16px; |
| | | border-radius: 12px; |
| | | overflow: hidden; |
| | | } |
| | | .process-step.rejected .step-dot { |
| | | background: #ff4d4f; |
| | | color: #fff; |
| | | } |
| | | .process-step.rejected .step-line { |
| | | background: #ff4d4f; |
| | | } |
| | | |
| | | .input-header { |
| | | padding: 16px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | background: #f8f9fa; |
| | | } |
| | | .step-content { |
| | | flex: 1; |
| | | padding-top: 4px; |
| | | } |
| | | |
| | | .input-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | .step-info { |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .input-content { |
| | | padding: 16px; |
| | | } |
| | | .step-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | display: block; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .footer-actions { |
| | | position: fixed; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background: #fff; |
| | | display: flex; |
| | | justify-content: space-around; |
| | | align-items: center; |
| | | padding: 16px; |
| | | box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1); |
| | | z-index: 1000; |
| | | } |
| | | .step-approver { |
| | | font-size: 14px; |
| | | color: #666; |
| | | display: block; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .reject-btn { |
| | | .step-time { |
| | | font-size: 12px; |
| | | color: #999; |
| | | display: block; |
| | | } |
| | | |
| | | .step-opinion { |
| | | background: #f8f9fa; |
| | | padding: 12px; |
| | | border-radius: 8px; |
| | | border-left: 4px solid #52c41a; |
| | | } |
| | | |
| | | .opinion-label { |
| | | font-size: 12px; |
| | | color: #666; |
| | | display: block; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .opinion-content { |
| | | font-size: 14px; |
| | | color: #333; |
| | | line-height: 1.5; |
| | | } |
| | | |
| | | .approval-input { |
| | | background: #fff; |
| | | margin: 16px; |
| | | border-radius: 12px; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .input-header { |
| | | padding: 16px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | background: #f8f9fa; |
| | | } |
| | | |
| | | .input-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | |
| | | .input-content { |
| | | padding: 16px; |
| | | } |
| | | |
| | | .footer-actions { |
| | | position: fixed; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background: #fff; |
| | | display: flex; |
| | | justify-content: space-around; |
| | | align-items: center; |
| | | padding: 16px; |
| | | box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1); |
| | | z-index: 1000; |
| | | } |
| | | |
| | | .reject-btn { |
| | | width: 120px; |
| | | background: #ff4d4f; |
| | | color: #fff; |
| | |
| | | background: #52c41a; |
| | | color: #fff; |
| | | } |
| | | |
| | | |
| | | /* 适配u-button样式 */ |
| | | :deep(.u-button) { |
| | | border-radius: 6px; |
| | | } |
| | | |
| | | @keyframes pulse { |
| | | 0% { |
| | | box-shadow: 0 0 0 0 rgba(24, 144, 255, 0.7); |
| | | @keyframes pulse { |
| | | 0% { |
| | | box-shadow: 0 0 0 0 rgba(24, 144, 255, 0.7); |
| | | } |
| | | 70% { |
| | | box-shadow: 0 0 0 10px rgba(24, 144, 255, 0); |
| | | } |
| | | 100% { |
| | | box-shadow: 0 0 0 0 rgba(24, 144, 255, 0); |
| | | } |
| | | } |
| | | 70% { |
| | | box-shadow: 0 0 0 10px rgba(24, 144, 255, 0); |
| | | .signature-section { |
| | | background: #fff; |
| | | padding: 12px 16px 16px; |
| | | border-top: 1px solid #f0f0f0; |
| | | } |
| | | 100% { |
| | | box-shadow: 0 0 0 0 rgba(24, 144, 255, 0); |
| | | .signature-header { |
| | | margin-bottom: 8px; |
| | | } |
| | | } |
| | | .signature-section { |
| | | background: #fff; |
| | | padding: 12px 16px 16px; |
| | | border-top: 1px solid #f0f0f0; |
| | | } |
| | | .signature-header { |
| | | margin-bottom: 8px; |
| | | } |
| | | .signature-title { |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | .signature-box { |
| | | width: 100%; |
| | | height: 180px; |
| | | background: #fff; |
| | | border: 1px dashed #d9d9d9; |
| | | border-radius: 8px; |
| | | overflow: hidden; |
| | | } |
| | | .signature-actions { |
| | | margin-top: 8px; |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | } |
| | | .signature-title { |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | .signature-box { |
| | | width: 100%; |
| | | height: 180px; |
| | | background: #fff; |
| | | border: 1px dashed #d9d9d9; |
| | | border-radius: 8px; |
| | | overflow: hidden; |
| | | } |
| | | .signature-actions { |
| | | margin-top: 8px; |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | } |
| | | </style> |