| | |
| | | <template> |
| | | <view class="account-detail"> |
| | | <PageHeader title="审批流程" @back="goBack" /> |
| | | |
| | | <PageHeader title="审批流程" |
| | | @back="goBack" /> |
| | | <!-- 表单区域 --> |
| | | <u-form ref="formRef" @submit="submitForm" :rules="rules" :model="form" label-width="140rpx"> |
| | | <u-form-item prop="approveReason" label="申请事由" required> |
| | | <u-input |
| | | v-model="form.approveReason" |
| | | type="textarea" |
| | | rows="2" |
| | | auto-height |
| | | maxlength="200" |
| | | placeholder="请输入申请事由" |
| | | show-word-limit |
| | | /> |
| | | <u-form ref="formRef" |
| | | @submit="submitForm" |
| | | :rules="rules" |
| | | :model="form" |
| | | label-width="140rpx"> |
| | | <u-form-item prop="approveReason" |
| | | label="流程编号"> |
| | | <u-input v-model="form.approveId" |
| | | disabled |
| | | placeholder="自动编号" /> |
| | | </u-form-item> |
| | | <u-form-item prop="approveDeptName" label="申请部门" required> |
| | | <u-input |
| | | v-model="form.approveDeptName" |
| | | readonly |
| | | placeholder="请选择申请部门" |
| | | @click="showPicker = true" |
| | | /> |
| | | <u-form-item prop="approveReason" |
| | | :label="approveType === 5 ? '采购事由' : '申请事由'" |
| | | required> |
| | | <u-input v-model="form.approveReason" |
| | | type="textarea" |
| | | rows="2" |
| | | auto-height |
| | | maxlength="200" |
| | | :placeholder="approveType === 5 ? '请输入采购事由' : '请输入申请事由'" |
| | | show-word-limit /> |
| | | </u-form-item> |
| | | <u-form-item prop="approveDeptName" |
| | | label="申请部门" |
| | | required> |
| | | <u-input v-model="form.approveDeptName" |
| | | placeholder="请选择申请部门" /> |
| | | <!-- <u-input v-model="form.approveDeptName" |
| | | readonly |
| | | placeholder="请选择申请部门" |
| | | @click="showPicker = true" /> |
| | | <template #right> |
| | | <up-icon |
| | | name="arrow-right" |
| | | @click="showPicker = true" |
| | | ></up-icon> |
| | | </template> |
| | | <up-icon name="arrow-right" |
| | | @click="showPicker = true"></up-icon> |
| | | </template> --> |
| | | </u-form-item> |
| | | <u-form-item prop="approveUser" label="申请人" required> |
| | | <u-input |
| | | v-model="form.approveUserName" |
| | | placeholder="请输入申请人" |
| | | readonly |
| | | /> |
| | | <u-form-item prop="approveUser" |
| | | label="申请人" |
| | | required> |
| | | <u-input v-model="form.approveUserName" |
| | | placeholder="请输入申请人" |
| | | readonly /> |
| | | </u-form-item> |
| | | <u-form-item prop="approveTime" label="申请日期" required> |
| | | <u-input |
| | | v-model="form.approveTime" |
| | | readonly |
| | | placeholder="请选择" |
| | | @click="showDatePicker" |
| | | /> |
| | | <u-form-item prop="approveTime" |
| | | label="申请日期" |
| | | required> |
| | | <u-input v-model="form.approveTime" |
| | | readonly |
| | | placeholder="请选择" |
| | | @click="showDatePicker" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" |
| | | @click="showDatePicker"></up-icon> |
| | | </template> |
| | | </u-form-item> |
| | | |
| | | <!-- approveType=2 请假相关字段 --> |
| | | <template v-if="approveType === 2"> |
| | | <u-form-item prop="startDate" label="请假开始时间" required> |
| | | <u-input |
| | | v-model="form.startDate" |
| | | readonly |
| | | placeholder="请选择开始时间" |
| | | @click="showStartDatePicker" |
| | | /> |
| | | <u-form-item prop="startDate" |
| | | label="开始时间" |
| | | required> |
| | | <u-input v-model="form.startDate" |
| | | readonly |
| | | placeholder="请假开始时间" |
| | | @click="showStartDatePicker" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" |
| | | @click="showStartDatePicker"></up-icon> |
| | | </template> |
| | | </u-form-item> |
| | | <u-form-item prop="endDate" label="请假结束时间" required> |
| | | <u-input |
| | | v-model="form.endDate" |
| | | readonly |
| | | placeholder="请选择结束时间" |
| | | @click="showEndDatePicker" |
| | | /> |
| | | <u-form-item prop="endDate" |
| | | label="结束时间" |
| | | required> |
| | | <u-input v-model="form.endDate" |
| | | readonly |
| | | placeholder="请假结束时间" |
| | | @click="showEndDatePicker" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" |
| | | @click="showEndDatePicker"></up-icon> |
| | | </template> |
| | | </u-form-item> |
| | | </template> |
| | | |
| | | <!-- approveType=3 出差相关字段 --> |
| | | <u-form-item v-if="approveType === 3" prop="location" label="出差地点" required> |
| | | <u-input |
| | | v-model="form.location" |
| | | placeholder="请输入出差地点" |
| | | clearable |
| | | /> |
| | | <u-form-item v-if="approveType === 3" |
| | | prop="location" |
| | | label="出差地点" |
| | | required> |
| | | <u-input v-model="form.location" |
| | | placeholder="请输入出差地点" |
| | | clearable /> |
| | | </u-form-item> |
| | | |
| | | <!-- approveType=4 报销相关字段 --> |
| | | <u-form-item v-if="approveType === 4" prop="price" label="报销金额" required> |
| | | <u-input |
| | | v-model="form.price" |
| | | type="number" |
| | | placeholder="请输入报销金额" |
| | | clearable |
| | | /> |
| | | <u-form-item v-if="approveType === 4" |
| | | prop="price" |
| | | label="报销金额" |
| | | required> |
| | | <u-input v-model="form.price" |
| | | type="number" |
| | | placeholder="请输入报销金额" |
| | | clearable /> |
| | | </u-form-item> |
| | | </u-form> |
| | | |
| | | <!-- 选择器弹窗 --> |
| | | <up-action-sheet |
| | | :show="showPicker" |
| | | :actions="productOptions" |
| | | title="选择部门" |
| | | @select="onConfirm" |
| | | @close="showPicker = false" |
| | | /> |
| | | |
| | | <up-action-sheet :show="showPicker" |
| | | :actions="productOptions" |
| | | title="选择部门" |
| | | @select="onConfirm" |
| | | @close="showPicker = false" /> |
| | | <!-- 日期选择器 --> |
| | | <up-popup :show="showDate" mode="bottom" @close="showDate = false"> |
| | | <up-datetime-picker |
| | | :show="true" |
| | | v-model="currentDate" |
| | | @confirm="onDateConfirm" |
| | | @cancel="showDate = false" |
| | | mode="date" |
| | | /> |
| | | <up-popup :show="showDate" |
| | | mode="bottom" |
| | | @close="showDate = false"> |
| | | <up-datetime-picker :show="true" |
| | | v-model="currentDate" |
| | | @confirm="onDateConfirm" |
| | | @cancel="showDate = false" |
| | | mode="date" /> |
| | | </up-popup> |
| | | |
| | | <!-- 请假开始时间选择器 --> |
| | | <up-popup :show="showStartDate" mode="bottom" @close="showStartDate = false"> |
| | | <up-datetime-picker |
| | | :show="true" |
| | | v-model="startDateValue" |
| | | @confirm="onStartDateConfirm" |
| | | @cancel="showStartDate = false" |
| | | mode="date" |
| | | /> |
| | | <up-popup :show="showStartDate" |
| | | mode="bottom" |
| | | @close="showStartDate = false"> |
| | | <up-datetime-picker :show="true" |
| | | v-model="startDateValue" |
| | | @confirm="onStartDateConfirm" |
| | | @cancel="showStartDate = false" |
| | | mode="date" /> |
| | | </up-popup> |
| | | |
| | | <!-- 请假结束时间选择器 --> |
| | | <up-popup :show="showEndDate" mode="bottom" @close="showEndDate = false"> |
| | | <up-datetime-picker |
| | | :show="true" |
| | | v-model="endDateValue" |
| | | @confirm="onEndDateConfirm" |
| | | @cancel="showEndDate = false" |
| | | mode="date" |
| | | /> |
| | | <up-popup :show="showEndDate" |
| | | mode="bottom" |
| | | @close="showEndDate = false"> |
| | | <up-datetime-picker :show="true" |
| | | v-model="endDateValue" |
| | | @confirm="onEndDateConfirm" |
| | | @cancel="showEndDate = false" |
| | | mode="date" /> |
| | | </up-popup> |
| | | <!-- 审核流程区域 --> |
| | | <view class="approval-process"> |
| | |
| | | <text class="approval-title">审核流程</text> |
| | | <text class="approval-desc">每个步骤只能选择一个审批人</text> |
| | | </view> |
| | | |
| | | <view class="approval-steps"> |
| | | <view v-for="(step, stepIndex) in approverNodes" :key="stepIndex" class="approval-step"> |
| | | <view v-for="(step, stepIndex) in approverNodes" |
| | | :key="stepIndex" |
| | | class="approval-step"> |
| | | <view class="step-dot"></view> |
| | | <view class="step-title"> |
| | | <text>审批人</text> |
| | | </view> |
| | | <view class="approver-container"> |
| | | <view v-if="step.nickName" class="approver-item"> |
| | | <view v-if="step.nickName" |
| | | class="approver-item"> |
| | | <view class="approver-avatar"> |
| | | <text class="avatar-text">{{ step.nickName.charAt(0) }}</text> |
| | | <view class="status-dot"></view> |
| | |
| | | <view class="approver-info"> |
| | | <text class="approver-name">{{ step.nickName }}</text> |
| | | </view> |
| | | <view class="delete-approver-btn" @click="removeApprover(stepIndex)">×</view> |
| | | <view class="delete-approver-btn" |
| | | @click="removeApprover(stepIndex)">×</view> |
| | | </view> |
| | | <view v-else class="add-approver-btn" @click="addApprover(stepIndex)"> |
| | | <view v-else |
| | | class="add-approver-btn" |
| | | @click="addApprover(stepIndex)"> |
| | | <view class="add-circle">+</view> |
| | | <text class="add-label">选择审批人</text> |
| | | </view> |
| | | </view> |
| | | <view class="step-line" v-if="stepIndex < approverNodes.length - 1"></view> |
| | | <view class="delete-step-btn" v-if="approverNodes.length > 1" @click="removeApprovalStep(stepIndex)">删除节点</view> |
| | | <view class="step-line" |
| | | v-if="stepIndex < approverNodes.length - 1"></view> |
| | | <view class="delete-step-btn" |
| | | v-if="approverNodes.length > 1" |
| | | @click="removeApprovalStep(stepIndex)">删除节点</view> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="add-step-btn"> |
| | | <u-button icon="plus" plain type="primary" style="width: 100%" @click="addApprovalStep">新增节点</u-button> |
| | | <u-button icon="plus" |
| | | plain |
| | | type="primary" |
| | | style="width: 100%" |
| | | @click="addApprovalStep">新增节点</u-button> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 底部按钮 --> |
| | | <view class="footer-btns"> |
| | | <u-button class="cancel-btn" @click="goBack">取消</u-button> |
| | | <u-button class="save-btn" @click="submitForm">保存</u-button> |
| | | <u-button class="cancel-btn" |
| | | @click="goBack">取消</u-button> |
| | | <u-button class="save-btn" |
| | | @click="submitForm">保存</u-button> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { formatDateToYMD } from '@/utils/ruoyi' |
| | | import {getDept, approveProcessGetInfo, approveProcessAdd, approveProcessUpdate} from "@/api/collaborativeApproval/approvalProcess"; |
| | | const showToast = (message) => { |
| | | uni.showToast({ |
| | | title: message, |
| | | icon: 'none' |
| | | }) |
| | | } |
| | | import {userListNoPageByTenantId} from "@/api/system/user"; |
| | | import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { formatDateToYMD } from "@/utils/ruoyi"; |
| | | import { |
| | | getDept, |
| | | approveProcessGetInfo, |
| | | approveProcessAdd, |
| | | approveProcessUpdate, |
| | | } from "@/api/collaborativeApproval/approvalProcess"; |
| | | const showToast = message => { |
| | | uni.showToast({ |
| | | title: message, |
| | | icon: "none", |
| | | }); |
| | | }; |
| | | import { userListNoPageByTenantId } from "@/api/system/user"; |
| | | |
| | | const data = reactive({ |
| | | form: { |
| | | approveTime: "", |
| | | approveId: "", |
| | | approveUser: "", |
| | | approveUserName: "", |
| | | approveDeptName: "", |
| | | approveDeptId: "", |
| | | approveReason: "", |
| | | checkResult: "", |
| | | tempFileIds: [], |
| | | approverList: [], // 新增字段,存储所有节点的审批人id |
| | | startDate: "", |
| | | endDate: "", |
| | | location: "", |
| | | price: "" |
| | | }, |
| | | rules: { |
| | | approveTime: [{ required: false, message: "请输入", trigger: "change" },], |
| | | approveId: [{ required: false, message: "请输入", trigger: "blur" }], |
| | | approveDeptId: [{ required: true, message: "请输入", trigger: "blur" }], |
| | | approveReason: [{ required: true, message: "请输入", trigger: "blur" }], |
| | | checkResult: [{ required: false, message: "请输入", trigger: "blur" }], |
| | | startDate: [{ required: false, message: "请选择开始时间", trigger: "change" }], |
| | | endDate: [{ required: false, message: "请选择结束时间", trigger: "change" }], |
| | | location: [{ required: false, message: "请输入出差地点", trigger: "blur" }], |
| | | price: [{ required: false, message: "请输入报销金额", trigger: "blur" }], |
| | | }, |
| | | }); |
| | | const { form, rules } = toRefs(data); |
| | | const result = ref(""); |
| | | const showPicker = ref(false); |
| | | const productOptions = ref([]); |
| | | const operationType = ref(""); |
| | | const currentApproveStatus = ref(""); |
| | | const approverNodes = ref([]); |
| | | const userList = ref([]); |
| | | const formRef = ref(null); |
| | | const message = ref(""); |
| | | const showDate = ref(false) |
| | | const currentDate = ref(Date.now()) |
| | | const showStartDate = ref(false) |
| | | const startDateValue = ref(Date.now()) |
| | | const showEndDate = ref(false) |
| | | const endDateValue = ref(Date.now()) |
| | | const userStore = useUserStore() |
| | | const approveType = ref(0) |
| | | |
| | | const getProductOptions = () => { |
| | | getDept().then((res) => { |
| | | productOptions.value = res.data.map(item => ({ |
| | | value: item.deptId, |
| | | name: item.deptName |
| | | })) |
| | | }); |
| | | }; |
| | | const fileList = ref([]); |
| | | let nextApproverId = 2; |
| | | |
| | | onMounted(async () => { |
| | | try { |
| | | getProductOptions() |
| | | userListNoPageByTenantId().then((res) => { |
| | | userList.value = res.data |
| | | }) |
| | | form.value.approveUser = userStore.id |
| | | form.value.approveUserName = userStore.nickName |
| | | form.value.approveTime = getCurrentDate(); |
| | | |
| | | // 从本地存储获取参数 |
| | | operationType.value = uni.getStorageSync('operationType') || 'add'; |
| | | approveType.value = uni.getStorageSync('approveType') || 0; |
| | | |
| | | // 如果是编辑模式,从本地存储获取数据 |
| | | if (operationType.value === 'edit') { |
| | | const storedData = uni.getStorageSync('invoiceLedgerEditRow'); |
| | | if (storedData) { |
| | | const row = JSON.parse(storedData); |
| | | fileList.value = row.commonFileList || []; |
| | | form.value.tempFileIds = fileList.value.map(file => file.id); |
| | | currentApproveStatus.value = row.approveStatus; |
| | | |
| | | approveProcessGetInfo({id: row.approveId, approveReason: '1'}).then(res => { |
| | | form.value = {...res.data}; |
| | | // 反显审批人 |
| | | if (res.data && res.data.approveUserIds) { |
| | | const userIds = res.data.approveUserIds.split(','); |
| | | approverNodes.value = userIds.map((userId, idx) => { |
| | | const userIdNum = parseInt(userId.trim()); |
| | | // 从userList中找到对应的用户信息 |
| | | const userInfo = userList.value.find(user => user.userId === userIdNum); |
| | | return { |
| | | id: idx + 1, |
| | | userId: userIdNum, |
| | | nickName: userInfo ? userInfo.nickName : null |
| | | }; |
| | | }); |
| | | nextApproverId = userIds.length + 1; |
| | | } else { |
| | | // 新增模式,初始化一个空的审批节点 |
| | | approverNodes.value = [{ id: 1, userId: null, nickName: null }]; |
| | | nextApproverId = 2; |
| | | } |
| | | }); |
| | | } |
| | | } else { |
| | | // 新增模式,初始化一个空的审批节点 |
| | | approverNodes.value = [{ id: 1, userId: null }]; |
| | | } |
| | | |
| | | // 监听联系人选择事件 |
| | | uni.$on('selectContact', handleSelectContact); |
| | | } catch (error) { |
| | | console.error("获取部门数据失败:", error); |
| | | } |
| | | }); |
| | | |
| | | onUnmounted(() => { |
| | | // 移除事件监听 |
| | | uni.$off('selectContact', handleSelectContact); |
| | | }); |
| | | |
| | | const onConfirm = (item) => { |
| | | // 设置选中的部门 |
| | | form.value.approveDeptName = item.name; |
| | | // 确保设置的是字符串类型的部门ID |
| | | form.value.approveDeptId = String(item.value || ''); |
| | | console.log('部门选择后的值:', { |
| | | approveDeptId: form.value.approveDeptId, |
| | | approveDeptName: form.value.approveDeptName |
| | | const data = reactive({ |
| | | form: { |
| | | approveTime: "", |
| | | approveId: "", |
| | | approveUser: "", |
| | | approveUserName: "", |
| | | approveDeptName: "", |
| | | approveDeptId: "", |
| | | approveReason: "", |
| | | checkResult: "", |
| | | tempFileIds: [], |
| | | approverList: [], // 新增字段,存储所有节点的审批人id |
| | | startDate: "", |
| | | endDate: "", |
| | | location: "", |
| | | price: "", |
| | | }, |
| | | rules: { |
| | | approveTime: [{ required: false, message: "请输入", trigger: "change" }], |
| | | approveId: [{ required: false, message: "请输入", trigger: "blur" }], |
| | | approveDeptId: [{ required: true, message: "请输入", trigger: "blur" }], |
| | | approveReason: [{ required: true, message: "请输入", trigger: "blur" }], |
| | | checkResult: [{ required: false, message: "请输入", trigger: "blur" }], |
| | | startDate: [ |
| | | { required: false, message: "请选择开始时间", trigger: "change" }, |
| | | ], |
| | | endDate: [ |
| | | { required: false, message: "请选择结束时间", trigger: "change" }, |
| | | ], |
| | | location: [{ required: false, message: "请输入出差地点", trigger: "blur" }], |
| | | price: [{ required: false, message: "请输入报销金额", trigger: "blur" }], |
| | | }, |
| | | }); |
| | | showPicker.value = false; |
| | | }; |
| | | const { form, rules } = toRefs(data); |
| | | const result = ref(""); |
| | | const showPicker = ref(false); |
| | | const productOptions = ref([]); |
| | | const operationType = ref(""); |
| | | const currentApproveStatus = ref(""); |
| | | const approverNodes = ref([]); |
| | | const userList = ref([]); |
| | | const formRef = ref(null); |
| | | const message = ref(""); |
| | | const showDate = ref(false); |
| | | const currentDate = ref(Date.now()); |
| | | const showStartDate = ref(false); |
| | | const startDateValue = ref(Date.now()); |
| | | const showEndDate = ref(false); |
| | | const endDateValue = ref(Date.now()); |
| | | const userStore = useUserStore(); |
| | | const approveType = ref(0); |
| | | |
| | | const goBack = () => { |
| | | // 清除本地存储的数据 |
| | | uni.removeStorageSync('operationType'); |
| | | uni.removeStorageSync('invoiceLedgerEditRow'); |
| | | uni.removeStorageSync('approveType'); |
| | | uni.navigateBack(); |
| | | }; |
| | | const getProductOptions = () => { |
| | | getDept().then(res => { |
| | | productOptions.value = res.data.map(item => ({ |
| | | value: item.deptId, |
| | | name: item.deptName, |
| | | })); |
| | | }); |
| | | }; |
| | | const fileList = ref([]); |
| | | let nextApproverId = 2; |
| | | const getCurrentinfo = () => { |
| | | userStore.getInfo().then(res => { |
| | | form.value.approveDeptId = res.user.tenantId; |
| | | console.log(res.user.tenantId, "res.user.tenantId"); |
| | | }); |
| | | }; |
| | | onMounted(async () => { |
| | | try { |
| | | getProductOptions(); |
| | | userListNoPageByTenantId().then(res => { |
| | | userList.value = res.data; |
| | | }); |
| | | form.value.approveUser = userStore.id; |
| | | form.value.approveUserName = userStore.nickName; |
| | | form.value.approveTime = getCurrentDate(); |
| | | getCurrentinfo(); |
| | | // 从本地存储获取参数 |
| | | operationType.value = uni.getStorageSync("operationType") || "add"; |
| | | approveType.value = uni.getStorageSync("approveType") || 0; |
| | | |
| | | const submitForm = () => { |
| | | // 检查每个审批步骤是否都有审批人 |
| | | const hasEmptyStep = approverNodes.value.some(step => !step.nickName); |
| | | if (hasEmptyStep) { |
| | | showToast('请为每个审批步骤选择审批人'); |
| | | return; |
| | | } |
| | | |
| | | // 手动检查必填字段,防止因数据类型问题导致的校验失败 |
| | | if (!form.value.approveReason || !form.value.approveReason.trim()) { |
| | | showToast('请输入申请事由'); |
| | | return; |
| | | } |
| | | |
| | | if (!form.value.approveDeptId || String(form.value.approveDeptId).trim() === '') { |
| | | showToast('请选择申请部门'); |
| | | return; |
| | | } |
| | | |
| | | if (!form.value.approveTime) { |
| | | showToast('请选择申请日期'); |
| | | return; |
| | | } |
| | | |
| | | formRef.value.validate().then((valid) => { |
| | | if (valid) { |
| | | // 表单校验通过,可以提交数据 |
| | | // 收集所有节点的审批人id |
| | | console.log('approverNodes---', approverNodes.value) |
| | | form.value.approveUserIds = approverNodes.value.map(node => node.userId).join(',') |
| | | form.value.approveType = approveType.value |
| | | if (operationType.value === "add" || currentApproveStatus.value == 3) { |
| | | approveProcessAdd(form.value).then(res => { |
| | | showToast("提交成功"); |
| | | goBack() |
| | | }) |
| | | } else { |
| | | approveProcessUpdate(form.value).then(res => { |
| | | showToast("提交成功"); |
| | | goBack() |
| | | }) |
| | | } |
| | | } |
| | | }).catch((error) => { |
| | | console.error("表单校验失败:", error); |
| | | // 尝试获取具体的错误字段 |
| | | if (error && error.errors) { |
| | | const firstError = error.errors[0]; |
| | | if (firstError) { |
| | | uni.showToast({ |
| | | title: firstError.message || '表单校验失败,请检查必填项', |
| | | icon: 'none' |
| | | }); |
| | | return; |
| | | // 如果是编辑模式,从本地存储获取数据 |
| | | if (operationType.value === "edit") { |
| | | const storedData = uni.getStorageSync("invoiceLedgerEditRow"); |
| | | if (storedData) { |
| | | const row = JSON.parse(storedData); |
| | | fileList.value = row.commonFileList || []; |
| | | form.value.tempFileIds = fileList.value.map(file => file.id); |
| | | currentApproveStatus.value = row.approveStatus; |
| | | |
| | | approveProcessGetInfo({ id: row.approveId, approveReason: "1" }).then( |
| | | res => { |
| | | form.value = { ...res.data }; |
| | | // 反显审批人 |
| | | if (res.data && res.data.approveUserIds) { |
| | | const userIds = res.data.approveUserIds.split(","); |
| | | approverNodes.value = userIds.map((userId, idx) => { |
| | | const userIdNum = parseInt(userId.trim()); |
| | | // 从userList中找到对应的用户信息 |
| | | const userInfo = userList.value.find( |
| | | user => user.userId === userIdNum |
| | | ); |
| | | return { |
| | | id: idx + 1, |
| | | userId: userIdNum, |
| | | nickName: userInfo ? userInfo.nickName : null, |
| | | }; |
| | | }); |
| | | nextApproverId = userIds.length + 1; |
| | | } else { |
| | | // 新增模式,初始化一个空的审批节点 |
| | | approverNodes.value = [{ id: 1, userId: null, nickName: null }]; |
| | | nextApproverId = 2; |
| | | } |
| | | } |
| | | ); |
| | | } |
| | | } else { |
| | | // 新增模式,初始化一个空的审批节点 |
| | | approverNodes.value = [{ id: 1, userId: null }]; |
| | | } |
| | | |
| | | // 监听联系人选择事件 |
| | | uni.$on("selectContact", handleSelectContact); |
| | | } catch (error) { |
| | | console.error("获取部门数据失败:", error); |
| | | } |
| | | // 显示通用错误信息 |
| | | uni.showToast({ |
| | | title: '表单校验失败,请检查必填项', |
| | | icon: 'none' |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | // 处理联系人选择结果 |
| | | const handleSelectContact = (data) => { |
| | | const { stepIndex, contact } = data; |
| | | // 将选中的联系人设置为对应审批步骤的审批人 |
| | | approverNodes.value[stepIndex].userId = contact.userId; |
| | | approverNodes.value[stepIndex].nickName = contact.nickName; |
| | | }; |
| | | |
| | | const addApprover = (stepIndex) => { |
| | | // 跳转到联系人选择页面 |
| | | uni.setStorageSync('stepIndex', stepIndex); |
| | | uni.navigateTo({ |
| | | url: "/pages/cooperativeOffice/collaborativeApproval/contactSelect" |
| | | onUnmounted(() => { |
| | | // 移除事件监听 |
| | | uni.$off("selectContact", handleSelectContact); |
| | | }); |
| | | }; |
| | | |
| | | const addApprovalStep = () => { |
| | | // 添加新的审批步骤 |
| | | approverNodes.value.push({ userId: null, nickName: null }); |
| | | }; |
| | | |
| | | const removeApprover = (stepIndex) => { |
| | | // 移除审批人 |
| | | approverNodes.value[stepIndex].userId = null; |
| | | approverNodes.value[stepIndex].nickName = null; |
| | | }; |
| | | |
| | | const removeApprovalStep = (stepIndex) => { |
| | | // 确保至少保留一个审批步骤 |
| | | if (approverNodes.value.length > 1) { |
| | | approverNodes.value.splice(stepIndex, 1); |
| | | } else { |
| | | uni.showToast({ |
| | | title: '至少需要一个审批步骤', |
| | | icon: 'none' |
| | | const onConfirm = item => { |
| | | // 设置选中的部门 |
| | | form.value.approveDeptName = item.name; |
| | | // 确保设置的是字符串类型的部门ID |
| | | form.value.approveDeptId = String(item.value || ""); |
| | | console.log("部门选择后的值:", { |
| | | approveDeptId: form.value.approveDeptId, |
| | | approveDeptName: form.value.approveDeptName, |
| | | }); |
| | | showPicker.value = false; |
| | | }; |
| | | |
| | | const goBack = () => { |
| | | // 清除本地存储的数据 |
| | | uni.removeStorageSync("operationType"); |
| | | uni.removeStorageSync("invoiceLedgerEditRow"); |
| | | uni.removeStorageSync("approveType"); |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | const submitForm = () => { |
| | | // 检查每个审批步骤是否都有审批人 |
| | | const hasEmptyStep = approverNodes.value.some(step => !step.nickName); |
| | | if (hasEmptyStep) { |
| | | showToast("请为每个审批步骤选择审批人"); |
| | | return; |
| | | } |
| | | |
| | | // 手动检查必填字段,防止因数据类型问题导致的校验失败 |
| | | if (!form.value.approveReason || !form.value.approveReason.trim()) { |
| | | showToast("请输入申请事由"); |
| | | return; |
| | | } |
| | | |
| | | if ( |
| | | !form.value.approveDeptId || |
| | | String(form.value.approveDeptId).trim() === "" |
| | | ) { |
| | | showToast("请选择申请部门"); |
| | | return; |
| | | } |
| | | |
| | | if (!form.value.approveTime) { |
| | | showToast("请选择申请日期"); |
| | | return; |
| | | } |
| | | |
| | | formRef.value |
| | | .validate() |
| | | .then(valid => { |
| | | if (valid) { |
| | | // 表单校验通过,可以提交数据 |
| | | // 收集所有节点的审批人id |
| | | console.log("approverNodes---", approverNodes.value); |
| | | form.value.approveUserIds = approverNodes.value |
| | | .map(node => node.userId) |
| | | .join(","); |
| | | form.value.approveType = approveType.value; |
| | | form.value.approveDeptId = Number(form.value.approveDeptId); |
| | | // const submitForm = { |
| | | // approveDeptId: form.value.approveDeptId, |
| | | // approveDeptName: form.value.approveDeptName, |
| | | // approveReason: form.value.approveReason, |
| | | // approveTime: form.value.approveTime, |
| | | // approveType: form.value.approveType, |
| | | // approveUser: form.value.approveUser, |
| | | // approveUserIds: form.value.approveUserIds, |
| | | // endDate: form.value.endDate, |
| | | // startDate: form.value.startDate, |
| | | // }; |
| | | // console.log("form.value---", form.value); |
| | | // console.log("submitForm", submitForm); |
| | | |
| | | if (operationType.value === "add" || currentApproveStatus.value == 3) { |
| | | approveProcessAdd(form.value).then(res => { |
| | | showToast("提交成功"); |
| | | goBack(); |
| | | }); |
| | | } else { |
| | | approveProcessUpdate(form.value).then(res => { |
| | | showToast("提交成功"); |
| | | goBack(); |
| | | }); |
| | | } |
| | | } |
| | | }) |
| | | .catch(error => { |
| | | console.error("表单校验失败:", error); |
| | | // 尝试获取具体的错误字段 |
| | | if (error && error.errors) { |
| | | const firstError = error.errors[0]; |
| | | if (firstError) { |
| | | uni.showToast({ |
| | | title: firstError.message || "表单校验失败,请检查必填项", |
| | | icon: "none", |
| | | }); |
| | | return; |
| | | } |
| | | } |
| | | // 显示通用错误信息 |
| | | uni.showToast({ |
| | | title: "表单校验失败,请检查必填项", |
| | | icon: "none", |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | // 处理联系人选择结果 |
| | | const handleSelectContact = data => { |
| | | const { stepIndex, contact } = data; |
| | | // 将选中的联系人设置为对应审批步骤的审批人 |
| | | approverNodes.value[stepIndex].userId = contact.userId; |
| | | approverNodes.value[stepIndex].nickName = contact.nickName; |
| | | }; |
| | | |
| | | const addApprover = stepIndex => { |
| | | // 跳转到联系人选择页面 |
| | | uni.setStorageSync("stepIndex", stepIndex); |
| | | uni.navigateTo({ |
| | | url: "/pages/cooperativeOffice/collaborativeApproval/contactSelect", |
| | | }); |
| | | }; |
| | | |
| | | const addApprovalStep = () => { |
| | | // 添加新的审批步骤 |
| | | approverNodes.value.push({ userId: null, nickName: null }); |
| | | }; |
| | | |
| | | const removeApprover = stepIndex => { |
| | | // 移除审批人 |
| | | approverNodes.value[stepIndex].userId = null; |
| | | approverNodes.value[stepIndex].nickName = null; |
| | | }; |
| | | |
| | | const removeApprovalStep = stepIndex => { |
| | | // 确保至少保留一个审批步骤 |
| | | if (approverNodes.value.length > 1) { |
| | | approverNodes.value.splice(stepIndex, 1); |
| | | } else { |
| | | uni.showToast({ |
| | | title: "至少需要一个审批步骤", |
| | | icon: "none", |
| | | }); |
| | | } |
| | | }; |
| | | // 显示日期选择器 |
| | | const showDatePicker = () => { |
| | | showDate.value = true; |
| | | }; |
| | | |
| | | // 确认日期选择 |
| | | const onDateConfirm = e => { |
| | | form.value.approveTime = formatDateToYMD(e.value); |
| | | currentDate.value = formatDateToYMD(e.value); |
| | | showDate.value = false; |
| | | }; |
| | | |
| | | // 显示请假开始时间选择器 |
| | | const showStartDatePicker = () => { |
| | | showStartDate.value = true; |
| | | }; |
| | | |
| | | // 确认请假开始时间选择 |
| | | const onStartDateConfirm = e => { |
| | | form.value.startDate = formatDateToYMD(e.value); |
| | | showStartDate.value = false; |
| | | }; |
| | | |
| | | const showEndDatePicker = () => { |
| | | showEndDate.value = true; |
| | | }; |
| | | |
| | | // 确认请假结束时间选择 |
| | | const onEndDateConfirm = e => { |
| | | form.value.endDate = formatDateToYMD(e.value); |
| | | showEndDate.value = false; |
| | | }; |
| | | |
| | | // 获取当前日期并格式化为 YYYY-MM-DD |
| | | function getCurrentDate() { |
| | | const today = new Date(); |
| | | const year = today.getFullYear(); |
| | | const month = String(today.getMonth() + 1).padStart(2, "0"); // 月份从0开始 |
| | | const day = String(today.getDate()).padStart(2, "0"); |
| | | return `${year}-${month}-${day}`; |
| | | } |
| | | }; |
| | | // 显示日期选择器 |
| | | const showDatePicker = () => { |
| | | showDate.value = true |
| | | } |
| | | |
| | | // 确认日期选择 |
| | | const onDateConfirm = (e) => { |
| | | form.value.approveTime = formatDateToYMD(e.value) |
| | | currentDate.value = formatDateToYMD(e.value) |
| | | showDate.value = false; |
| | | } |
| | | |
| | | // 显示请假开始时间选择器 |
| | | const showStartDatePicker = () => { |
| | | showStartDate.value = true |
| | | } |
| | | |
| | | // 确认请假开始时间选择 |
| | | const onStartDateConfirm = (e) => { |
| | | form.value.startDate = formatDateToYMD(e.value) |
| | | showStartDate.value = false |
| | | } |
| | | |
| | | const showEndDatePicker = () => { |
| | | showEndDate.value = true |
| | | } |
| | | |
| | | // 确认请假结束时间选择 |
| | | const onEndDateConfirm = (e) => { |
| | | form.value.endDate = formatDateToYMD(e.value) |
| | | showEndDate.value = false |
| | | } |
| | | |
| | | // 获取当前日期并格式化为 YYYY-MM-DD |
| | | function getCurrentDate() { |
| | | const today = new Date(); |
| | | const year = today.getFullYear(); |
| | | const month = String(today.getMonth() + 1).padStart(2, "0"); // 月份从0开始 |
| | | const day = String(today.getDate()).padStart(2, "0"); |
| | | return `${year}-${month}-${day}`; |
| | | } |
| | | |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import '@/static/scss/form-common.scss'; |
| | | @import "@/static/scss/form-common.scss"; |
| | | |
| | | .approval-process { |
| | | background: #fff; |
| | | margin: 16px; |
| | | border-radius: 16px; |
| | | padding: 16px; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); |
| | | } |
| | | |
| | | .approval-header { |
| | | margin-bottom: 16px; |
| | | } |
| | | |
| | | .approval-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | display: block; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .approval-desc { |
| | | font-size: 12px; |
| | | color: #999; |
| | | } |
| | | |
| | | /* 样式增强为“简洁小圆圈风格” */ |
| | | .approval-steps { |
| | | padding-left: 22px; |
| | | position: relative; |
| | | |
| | | &::before { |
| | | content: ''; |
| | | position: absolute; |
| | | left: 11px; |
| | | top: 40px; |
| | | bottom: 40px; |
| | | width: 2px; |
| | | background: linear-gradient(to bottom, #e6f7ff 0%, #bae7ff 50%, #91d5ff 100%); |
| | | border-radius: 1px; |
| | | } |
| | | } |
| | | |
| | | .approval-step { |
| | | position: relative; |
| | | margin-bottom: 24px; |
| | | |
| | | &::before { |
| | | content: ''; |
| | | position: absolute; |
| | | left: -18px; |
| | | top: 14px; // 从 8px 调整为 14px,与文字中心对齐 |
| | | width: 12px; |
| | | height: 12px; |
| | | .approval-process { |
| | | background: #fff; |
| | | border: 3px solid #006cfb; |
| | | border-radius: 50%; |
| | | z-index: 2; |
| | | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
| | | margin: 16px; |
| | | border-radius: 16px; |
| | | padding: 16px; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); |
| | | } |
| | | } |
| | | |
| | | .step-title { |
| | | top: 12px; |
| | | margin-bottom: 12px; |
| | | position: relative; |
| | | margin-left: 6px; |
| | | } |
| | | |
| | | .step-title text { |
| | | font-size: 14px; |
| | | color: #666; |
| | | background: #f0f0f0; |
| | | padding: 4px 12px; |
| | | border-radius: 12px; |
| | | position: relative; |
| | | line-height: 1.4; // 确保文字行高一致 |
| | | } |
| | | |
| | | .approver-item { |
| | | display: flex; |
| | | align-items: center; |
| | | background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%); |
| | | border-radius: 16px; |
| | | padding: 16px; |
| | | gap: 12px; |
| | | position: relative; |
| | | border: 1px solid #e6f7ff; |
| | | box-shadow: 0 4px 12px rgba(0, 108, 251, 0.08); |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | .approver-avatar { |
| | | width: 48px; |
| | | height: 48px; |
| | | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | position: relative; |
| | | box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3); |
| | | } |
| | | |
| | | .avatar-text { |
| | | color: #fff; |
| | | font-size: 18px; |
| | | font-weight: 600; |
| | | text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .approver-info { |
| | | flex: 1; |
| | | position: relative; |
| | | } |
| | | |
| | | .approver-name { |
| | | display: block; |
| | | font-size: 16px; |
| | | color: #333; |
| | | font-weight: 500; |
| | | position: relative; |
| | | } |
| | | |
| | | .approver-dept { |
| | | font-size: 12px; |
| | | color: #999; |
| | | background: rgba(0, 108, 251, 0.05); |
| | | padding: 2px 8px; |
| | | border-radius: 8px; |
| | | display: inline-block; |
| | | position: relative; |
| | | |
| | | &::before { |
| | | content: ''; |
| | | position: absolute; |
| | | left: 4px; |
| | | top: 50%; |
| | | transform: translateY(-50%); |
| | | width: 2px; |
| | | height: 2px; |
| | | background: #006cfb; |
| | | border-radius: 50%; |
| | | .approval-header { |
| | | margin-bottom: 16px; |
| | | } |
| | | } |
| | | |
| | | .delete-approver-btn { |
| | | font-size: 16px; |
| | | color: #ff4d4f; |
| | | background: linear-gradient(135deg, rgba(255, 77, 79, 0.1) 0%, rgba(255, 77, 79, 0.05) 100%); |
| | | width: 28px; |
| | | height: 28px; |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | transition: all 0.3s ease; |
| | | position: relative; |
| | | } |
| | | .approval-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | display: block; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .add-approver-btn { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | background: linear-gradient(135deg, #f0f8ff 0%, #e6f7ff 100%); |
| | | border: 2px dashed #006cfb; |
| | | border-radius: 16px; |
| | | padding: 20px; |
| | | color: #006cfb; |
| | | font-size: 14px; |
| | | position: relative; |
| | | transition: all 0.3s ease; |
| | | |
| | | &::before { |
| | | content: ''; |
| | | position: absolute; |
| | | left: 50%; |
| | | top: 50%; |
| | | transform: translate(-50%, -50%); |
| | | width: 32px; |
| | | height: 32px; |
| | | border: 2px solid #006cfb; |
| | | border-radius: 50%; |
| | | opacity: 0; |
| | | .approval-desc { |
| | | font-size: 12px; |
| | | color: #999; |
| | | } |
| | | |
| | | /* 样式增强为“简洁小圆圈风格” */ |
| | | .approval-steps { |
| | | padding-left: 22px; |
| | | position: relative; |
| | | |
| | | &::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 11px; |
| | | top: 40px; |
| | | bottom: 40px; |
| | | width: 2px; |
| | | background: linear-gradient( |
| | | to bottom, |
| | | #e6f7ff 0%, |
| | | #bae7ff 50%, |
| | | #91d5ff 100% |
| | | ); |
| | | border-radius: 1px; |
| | | } |
| | | } |
| | | |
| | | .approval-step { |
| | | position: relative; |
| | | margin-bottom: 24px; |
| | | |
| | | &::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: -18px; |
| | | top: 14px; // 从 8px 调整为 14px,与文字中心对齐 |
| | | width: 12px; |
| | | height: 12px; |
| | | background: #fff; |
| | | border: 3px solid #006cfb; |
| | | border-radius: 50%; |
| | | z-index: 2; |
| | | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
| | | } |
| | | } |
| | | |
| | | .step-title { |
| | | top: 12px; |
| | | margin-bottom: 12px; |
| | | position: relative; |
| | | margin-left: 6px; |
| | | } |
| | | |
| | | .step-title text { |
| | | font-size: 14px; |
| | | color: #666; |
| | | background: #f0f0f0; |
| | | padding: 4px 12px; |
| | | border-radius: 12px; |
| | | position: relative; |
| | | line-height: 1.4; // 确保文字行高一致 |
| | | } |
| | | |
| | | .approver-item { |
| | | display: flex; |
| | | align-items: center; |
| | | background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%); |
| | | border-radius: 16px; |
| | | padding: 16px; |
| | | gap: 12px; |
| | | position: relative; |
| | | border: 1px solid #e6f7ff; |
| | | box-shadow: 0 4px 12px rgba(0, 108, 251, 0.08); |
| | | transition: all 0.3s ease; |
| | | } |
| | | } |
| | | |
| | | .delete-step-btn { |
| | | color: #ff4d4f; |
| | | font-size: 12px; |
| | | background: linear-gradient(135deg, rgba(255, 77, 79, 0.1) 0%, rgba(255, 77, 79, 0.05) 100%); |
| | | padding: 6px 12px; |
| | | border-radius: 12px; |
| | | display: inline-block; |
| | | position: relative; |
| | | transition: all 0.3s ease; |
| | | |
| | | &::before { |
| | | content: ''; |
| | | position: absolute; |
| | | left: 6px; |
| | | top: 50%; |
| | | transform: translateY(-50%); |
| | | width: 4px; |
| | | height: 4px; |
| | | background: #ff4d4f; |
| | | .approver-avatar { |
| | | width: 48px; |
| | | height: 48px; |
| | | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | position: relative; |
| | | box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3); |
| | | } |
| | | } |
| | | |
| | | .step-line { |
| | | display: none; // 隐藏原来的线条,使用伪元素代替 |
| | | } |
| | | |
| | | .add-step-btn { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | .footer-btns { |
| | | position: fixed; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background: #fff; |
| | | display: flex; |
| | | justify-content: space-around; |
| | | align-items: center; |
| | | padding: 0.75rem 0; |
| | | box-shadow: 0 -0.125rem 0.5rem rgba(0,0,0,0.05); |
| | | z-index: 1000; |
| | | } |
| | | |
| | | .cancel-btn { |
| | | font-weight: 400; |
| | | font-size: 1rem; |
| | | color: #FFFFFF; |
| | | width: 6.375rem; |
| | | background: #C7C9CC; |
| | | box-shadow: 0 0.25rem 0.625rem 0 rgba(3,88,185,0.2); |
| | | border-radius: 2.5rem 2.5rem 2.5rem 2.5rem; |
| | | } |
| | | |
| | | .save-btn { |
| | | font-weight: 400; |
| | | font-size: 1rem; |
| | | color: #FFFFFF; |
| | | width: 14rem; |
| | | background: linear-gradient( 140deg, #00BAFF 0%, #006CFB 100%); |
| | | box-shadow: 0 0.25rem 0.625rem 0 rgba(3,88,185,0.2); |
| | | border-radius: 2.5rem 2.5rem 2.5rem 2.5rem; |
| | | } |
| | | |
| | | // 动画定义 |
| | | @keyframes pulse { |
| | | 0% { |
| | | transform: scale(1); |
| | | opacity: 1; |
| | | .avatar-text { |
| | | color: #fff; |
| | | font-size: 18px; |
| | | font-weight: 600; |
| | | text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); |
| | | } |
| | | 50% { |
| | | transform: scale(1.2); |
| | | opacity: 0.7; |
| | | |
| | | .approver-info { |
| | | flex: 1; |
| | | position: relative; |
| | | } |
| | | 100% { |
| | | transform: scale(1); |
| | | opacity: 1; |
| | | |
| | | .approver-name { |
| | | display: block; |
| | | font-size: 16px; |
| | | color: #333; |
| | | font-weight: 500; |
| | | position: relative; |
| | | } |
| | | } |
| | | |
| | | @keyframes rotate { |
| | | 0% { |
| | | transform: rotate(0deg); |
| | | .approver-dept { |
| | | font-size: 12px; |
| | | color: #999; |
| | | background: rgba(0, 108, 251, 0.05); |
| | | padding: 2px 8px; |
| | | border-radius: 8px; |
| | | display: inline-block; |
| | | position: relative; |
| | | |
| | | &::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 4px; |
| | | top: 50%; |
| | | transform: translateY(-50%); |
| | | width: 2px; |
| | | height: 2px; |
| | | background: #006cfb; |
| | | border-radius: 50%; |
| | | } |
| | | } |
| | | 100% { |
| | | transform: rotate(360deg); |
| | | |
| | | .delete-approver-btn { |
| | | font-size: 16px; |
| | | color: #ff4d4f; |
| | | background: linear-gradient( |
| | | 135deg, |
| | | rgba(255, 77, 79, 0.1) 0%, |
| | | rgba(255, 77, 79, 0.05) 100% |
| | | ); |
| | | width: 28px; |
| | | height: 28px; |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | transition: all 0.3s ease; |
| | | position: relative; |
| | | } |
| | | } |
| | | |
| | | @keyframes ripple { |
| | | 0% { |
| | | transform: translate(-50%, -50%) scale(0.8); |
| | | opacity: 1; |
| | | .add-approver-btn { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | background: linear-gradient(135deg, #f0f8ff 0%, #e6f7ff 100%); |
| | | border: 2px dashed #006cfb; |
| | | border-radius: 16px; |
| | | padding: 20px; |
| | | color: #006cfb; |
| | | font-size: 14px; |
| | | position: relative; |
| | | transition: all 0.3s ease; |
| | | |
| | | &::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 50%; |
| | | top: 50%; |
| | | transform: translate(-50%, -50%); |
| | | width: 32px; |
| | | height: 32px; |
| | | border: 2px solid #006cfb; |
| | | border-radius: 50%; |
| | | opacity: 0; |
| | | transition: all 0.3s ease; |
| | | } |
| | | } |
| | | 100% { |
| | | transform: translate(-50%, -50%) scale(1.6); |
| | | opacity: 0; |
| | | |
| | | .delete-step-btn { |
| | | color: #ff4d4f; |
| | | font-size: 12px; |
| | | background: linear-gradient( |
| | | 135deg, |
| | | rgba(255, 77, 79, 0.1) 0%, |
| | | rgba(255, 77, 79, 0.05) 100% |
| | | ); |
| | | padding: 6px 12px; |
| | | border-radius: 12px; |
| | | display: inline-block; |
| | | position: relative; |
| | | transition: all 0.3s ease; |
| | | |
| | | &::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 6px; |
| | | top: 50%; |
| | | transform: translateY(-50%); |
| | | width: 4px; |
| | | height: 4px; |
| | | background: #ff4d4f; |
| | | border-radius: 50%; |
| | | } |
| | | } |
| | | } |
| | | |
| | | /* 如果已有 .step-line,这里更精准定位到左侧与小圆点对齐 */ |
| | | .step-line { |
| | | position: absolute; |
| | | left: 4px; |
| | | top: 48px; |
| | | width: 2px; |
| | | height: calc(100% - 48px); |
| | | background: #E5E7EB; |
| | | } |
| | | .step-line { |
| | | display: none; // 隐藏原来的线条,使用伪元素代替 |
| | | } |
| | | |
| | | .approver-container { |
| | | display: flex; |
| | | align-items: center; |
| | | background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%); |
| | | border-radius: 16px; |
| | | gap: 12px; |
| | | padding: 10px 0; |
| | | background: transparent; |
| | | border: none; |
| | | box-shadow: none; |
| | | } |
| | | .add-step-btn { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | .footer-btns { |
| | | position: fixed; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background: #fff; |
| | | display: flex; |
| | | justify-content: space-around; |
| | | align-items: center; |
| | | padding: 0.75rem 0; |
| | | box-shadow: 0 -0.125rem 0.5rem rgba(0, 0, 0, 0.05); |
| | | z-index: 1000; |
| | | } |
| | | |
| | | .approver-item { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 12px; |
| | | padding: 8px 10px; |
| | | background: transparent; |
| | | border: none; |
| | | box-shadow: none; |
| | | border-radius: 0; |
| | | } |
| | | .cancel-btn { |
| | | font-weight: 400; |
| | | font-size: 1rem; |
| | | color: #ffffff; |
| | | width: 6.375rem; |
| | | background: #c7c9cc; |
| | | box-shadow: 0 0.25rem 0.625rem 0 rgba(3, 88, 185, 0.2); |
| | | border-radius: 2.5rem 2.5rem 2.5rem 2.5rem; |
| | | } |
| | | |
| | | .approver-avatar { |
| | | position: relative; |
| | | width: 40px; |
| | | height: 40px; |
| | | border-radius: 50%; |
| | | background: #F3F4F6; |
| | | border: 2px solid #E5E7EB; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | animation: none; /* 禁用旋转等动画,回归简洁 */ |
| | | } |
| | | .save-btn { |
| | | font-weight: 400; |
| | | font-size: 1rem; |
| | | color: #ffffff; |
| | | width: 14rem; |
| | | background: linear-gradient(140deg, #00baff 0%, #006cfb 100%); |
| | | box-shadow: 0 0.25rem 0.625rem 0 rgba(3, 88, 185, 0.2); |
| | | border-radius: 2.5rem 2.5rem 2.5rem 2.5rem; |
| | | } |
| | | |
| | | .avatar-text { |
| | | font-size: 14px; |
| | | color: #374151; |
| | | font-weight: 600; |
| | | } |
| | | // 动画定义 |
| | | @keyframes pulse { |
| | | 0% { |
| | | transform: scale(1); |
| | | opacity: 1; |
| | | } |
| | | 50% { |
| | | transform: scale(1.2); |
| | | opacity: 0.7; |
| | | } |
| | | 100% { |
| | | transform: scale(1); |
| | | opacity: 1; |
| | | } |
| | | } |
| | | |
| | | .add-approver-btn { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | background: transparent; |
| | | border: none; |
| | | box-shadow: none; |
| | | padding: 0; |
| | | } |
| | | @keyframes rotate { |
| | | 0% { |
| | | transform: rotate(0deg); |
| | | } |
| | | 100% { |
| | | transform: rotate(360deg); |
| | | } |
| | | } |
| | | |
| | | .add-approver-btn .add-circle { |
| | | width: 40px; |
| | | height: 40px; |
| | | border: 2px dashed #A0AEC0; |
| | | border-radius: 50%; |
| | | color: #6B7280; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | font-size: 22px; |
| | | line-height: 1; |
| | | } |
| | | @keyframes ripple { |
| | | 0% { |
| | | transform: translate(-50%, -50%) scale(0.8); |
| | | opacity: 1; |
| | | } |
| | | 100% { |
| | | transform: translate(-50%, -50%) scale(1.6); |
| | | opacity: 0; |
| | | } |
| | | } |
| | | |
| | | .add-approver-btn .add-label { |
| | | color: #3B82F6; |
| | | font-size: 14px; |
| | | } |
| | | /* 如果已有 .step-line,这里更精准定位到左侧与小圆点对齐 */ |
| | | .step-line { |
| | | position: absolute; |
| | | left: 4px; |
| | | top: 48px; |
| | | width: 2px; |
| | | height: calc(100% - 48px); |
| | | background: #e5e7eb; |
| | | } |
| | | |
| | | .approver-container { |
| | | display: flex; |
| | | align-items: center; |
| | | background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%); |
| | | border-radius: 16px; |
| | | gap: 12px; |
| | | padding: 10px 0; |
| | | background: transparent; |
| | | border: none; |
| | | box-shadow: none; |
| | | } |
| | | |
| | | .approver-item { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 12px; |
| | | padding: 8px 10px; |
| | | background: transparent; |
| | | border: none; |
| | | box-shadow: none; |
| | | border-radius: 0; |
| | | } |
| | | |
| | | .approver-avatar { |
| | | position: relative; |
| | | width: 40px; |
| | | height: 40px; |
| | | border-radius: 50%; |
| | | background: #f3f4f6; |
| | | border: 2px solid #e5e7eb; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | animation: none; /* 禁用旋转等动画,回归简洁 */ |
| | | } |
| | | |
| | | .avatar-text { |
| | | font-size: 14px; |
| | | color: #374151; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .add-approver-btn { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | background: transparent; |
| | | border: none; |
| | | box-shadow: none; |
| | | padding: 0; |
| | | } |
| | | |
| | | .add-approver-btn .add-circle { |
| | | width: 40px; |
| | | height: 40px; |
| | | border: 2px dashed #a0aec0; |
| | | border-radius: 50%; |
| | | color: #6b7280; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | font-size: 22px; |
| | | line-height: 1; |
| | | } |
| | | |
| | | .add-approver-btn .add-label { |
| | | color: #3b82f6; |
| | | font-size: 14px; |
| | | } |
| | | </style> |