From 69a5fcec5068f7f5d2e2cade4389651e73e26aef Mon Sep 17 00:00:00 2001 From: gaoluyang <2820782392@qq.com> Date: 星期三, 27 八月 2025 10:43:44 +0800 Subject: [PATCH] 1.协同审批开发联调 --- src/pages/cooperativeOffice/collaborativeApproval/approve.vue | 519 +++++++++++ src/api/system/user.js | 7 src/pages.json | 14 src/pages/cooperativeOffice/collaborativeApproval/index.vue | 260 ++++- src/pages/cooperativeOffice/collaborativeApproval/detail.vue | 931 +++++++++++++-------- src/api/equipmentManagement/upkeep.js | 72 + src/api/equipmentManagement/ledger.js | 44 + src/api/equipmentManagement/measurementEquipment.js | 35 src/api/collaborativeApproval/approvalProcess.js | 63 + src/api/equipmentManagement/repair.js | 72 + src/api/collaborativeApproval/noticeManagement.js | 69 + src/pages/cooperativeOffice/collaborativeApproval/contactSelect.vue | 391 ++++++++ src/api/collaborativeApproval/rpaManagement.js | 77 + src/api/equipmentManagement/calibration.js | 27 14 files changed, 2,169 insertions(+), 412 deletions(-) diff --git a/src/api/collaborativeApproval/approvalProcess.js b/src/api/collaborativeApproval/approvalProcess.js new file mode 100644 index 0000000..415bed8 --- /dev/null +++ b/src/api/collaborativeApproval/approvalProcess.js @@ -0,0 +1,63 @@ +// 鍗忓悓瀹℃壒 +import request from "@/utils/request"; + +export function approveProcessListPage(query) { + return request({ + url: '/approveProcess/list', + method: 'get', + params: query, + }) +} +export function getDept(query) { + return request({ + url: '/approveProcess/getDept', + method: 'get', + params: query, + }) +} +export function approveProcessGetInfo(query) { + return request({ + url: '/approveProcess/get', + method: 'get', + params: query, + }) +} +// 鏂板瀹℃壒娴佺▼ +export function approveProcessAdd(query) { + return request({ + url: '/approveProcess/add', + method: 'post', + data: query, + }) +} +// 淇敼瀹℃壒娴佺▼ +export function approveProcessUpdate(query) { + return request({ + url: '/approveProcess/update', + method: 'post', + data: query, + }) +} +// 鎻愪氦瀹℃壒 +export function updateApproveNode(query) { + return request({ + url: '/approveNode/updateApproveNode', + method: 'post', + data: query, + }) +} +// 鍒犻櫎瀹℃壒娴佺▼ +export function approveProcessDelete(query) { + return request({ + url: '/approveProcess/deleteIds', + method: 'delete', + data: query, + }) +} +// 鏌ヨ瀹℃壒娴佺▼ +export function approveProcessDetails(query) { + return request({ + url: '/approveNode/details/' + query, + method: 'get', + }) +} \ No newline at end of file diff --git a/src/api/collaborativeApproval/noticeManagement.js b/src/api/collaborativeApproval/noticeManagement.js new file mode 100644 index 0000000..fa1caec --- /dev/null +++ b/src/api/collaborativeApproval/noticeManagement.js @@ -0,0 +1,69 @@ +import request from '@/utils/request' + +// 鏌ヨ鍏憡鍒楄〃 +export function listNotice(query) { + return request({ + url: '/collaborativeApproval/notice/list', + method: 'get', + params: query + }) +} + +// 鏌ヨ鍏憡璇︾粏 +export function getNotice(noticeId) { + return request({ + url: '/collaborativeApproval/notice/' + noticeId, + method: 'get' + }) +} + +// 鏂板鍏憡 +export function addNotice(data) { + return request({ + url: '/collaborativeApproval/notice', + method: 'post', + data: data + }) +} + +// 淇敼鍏憡 +export function updateNotice(data) { + return request({ + url: '/collaborativeApproval/notice', + method: 'put', + data: data + }) +} + +// 鍒犻櫎鍏憡 +export function delNotice(noticeId) { + return request({ + url: '/collaborativeApproval/notice/' + noticeId, + method: 'delete' + }) +} + +// 鎵归噺鍒犻櫎鍏憡 +export function delNoticeBatch(noticeIds) { + return request({ + url: '/collaborativeApproval/notice/batch', + method: 'delete', + data: noticeIds + }) +} + +// 鍙戝竷鍏憡 +export function publishNotice(noticeId) { + return request({ + url: '/collaborativeApproval/notice/publish/' + noticeId, + method: 'put' + }) +} + +// 涓嬬嚎鍏憡 +export function offlineNotice(noticeId) { + return request({ + url: '/collaborativeApproval/notice/offline/' + noticeId, + method: 'put' + }) +} diff --git a/src/api/collaborativeApproval/rpaManagement.js b/src/api/collaborativeApproval/rpaManagement.js new file mode 100644 index 0000000..6fc3368 --- /dev/null +++ b/src/api/collaborativeApproval/rpaManagement.js @@ -0,0 +1,77 @@ +import request from "@/utils/request"; + +// 鏌ヨRPA鍒楄〃 +export function listRpa(query) { + return request({ + url: "/collaborativeApproval/rpa/list", + method: "get", + params: query, + }); +} + +// 鏌ヨRPA璇︾粏 +export function getRpa(rpaId) { + return request({ + url: "/collaborativeApproval/rpa/" + rpaId, + method: "get", + }); +} + +// 鏂板RPA +export function addRpa(data) { + return request({ + url: "/collaborativeApproval/rpa", + method: "post", + data: data, + }); +} + +// 淇敼RPA +export function updateRpa(data) { + return request({ + url: "/collaborativeApproval/rpa", + method: "put", + data: data, + }); +} + +// 鍒犻櫎RPA +export function delRpa(rpaId) { + return request({ + url: "/collaborativeApproval/rpa/" + rpaId, + method: "delete", + }); +} + +// 鎵归噺鍒犻櫎RPA +export function delRpaBatch(rpaIds) { + return request({ + url: "/collaborativeApproval/rpa/batch", + method: "delete", + data: rpaIds, + }); +} + +// 鍚姩RPA +export function startRpa(rpaId) { + return request({ + url: "/collaborativeApproval/rpa/start/" + rpaId, + method: "post", + }); +} + +// 鍋滄RPA +export function stopRpa(rpaId) { + return request({ + url: "/collaborativeApproval/rpa/stop/" + rpaId, + method: "post", + }); +} + +// 鑾峰彇RPA鐘舵�� +export function getRpaStatus(rpaId) { + return request({ + url: "/collaborativeApproval/rpa/status/" + rpaId, + method: "get", + }); +} diff --git a/src/api/equipmentManagement/calibration.js b/src/api/equipmentManagement/calibration.js new file mode 100644 index 0000000..54b99f9 --- /dev/null +++ b/src/api/equipmentManagement/calibration.js @@ -0,0 +1,27 @@ +// 妫�瀹氭牎鍑嗚褰� +import request from "@/utils/request"; + +// 鍒嗛〉鏌ヨ +export function ledgerRecordListPage(query) { + return request({ + url: "/measuringInstrumentLedgerRecord/listPage", + method: "get", + params: query, + }); +} +// 鏍″噯 +export function ledgerRecordVerifying(query) { + return request({ + url: "/measuringInstrumentLedger/verifying", + method: "post", + data: query, + }); +} +// 淇敼鏍″噯 +export function ledgerRecordUpdate(query) { + return request({ + url: "/measuringInstrumentLedgerRecord/update", + method: "post", + data: query, + }); +} \ No newline at end of file diff --git a/src/api/equipmentManagement/ledger.js b/src/api/equipmentManagement/ledger.js new file mode 100644 index 0000000..d1b65b0 --- /dev/null +++ b/src/api/equipmentManagement/ledger.js @@ -0,0 +1,44 @@ +import request from "@/utils/request"; + +export const getLedgerPage = (params) => { + return request({ + url: "/device/ledger/page", + method: "get", + params, + }); +}; +export const getLedgerById = (id) => { + return request({ + url: `/device/ledger/${id}`, + method: "get", + }); +}; + +export const addLedger = (data) => { + return request({ + url: "/device/ledger", + method: "post", + data, + }); +}; +export const editLedger = (data) => { + return request({ + url: "/device/ledger", + method: "put", + data, + }); +}; + +export const delLedger = (id) => { + return request({ + url: `/device/ledger/${id}`, + method: "delete", + }); +}; + +export const getDeviceLedger = () => { + return request({ + url: "/device/ledger/getDeviceLedger", + method: "get", + }); +}; diff --git a/src/api/equipmentManagement/measurementEquipment.js b/src/api/equipmentManagement/measurementEquipment.js new file mode 100644 index 0000000..a22c034 --- /dev/null +++ b/src/api/equipmentManagement/measurementEquipment.js @@ -0,0 +1,35 @@ +// 璁¢噺鍣ㄥ叿鍙拌处 +import request from "@/utils/request"; + +// 鍒嗛〉鏌ヨ +export function measuringInstrumentListPage(query) { + return request({ + url: "/measuringInstrumentLedger/listPage", + method: "get", + params: query, + }); +} +// 鍒犻櫎 +export function measuringInstrumentDelete(query) { + return request({ + url: "/measuringInstrumentLedger/delete", + method: "delete", + data: query, + }); +} +// 鏂板 +export function measuringInstrumentAdd(query) { + return request({ + url: "/measuringInstrumentLedger/add", + method: "post", + data: query, + }); +} +// 淇敼 +export function measuringInstrumentUpdate(query) { + return request({ + url: "/measuringInstrumentLedger/update", + method: "post", + data: query, + }); +} \ No newline at end of file diff --git a/src/api/equipmentManagement/repair.js b/src/api/equipmentManagement/repair.js new file mode 100644 index 0000000..0233ae6 --- /dev/null +++ b/src/api/equipmentManagement/repair.js @@ -0,0 +1,72 @@ +import request from "@/utils/request"; + +/** + * @desc 璁惧鎶ヤ慨鍒楄〃 + * @param {鍒嗛〉鏌ヨ} params + * @returns + */ +export const getRepairPage = (params) => { + return request({ + url: "/device/repair/page", + method: "get", + params, + }); +}; + +/** + * @desc 鏂板鎶ヤ慨 + * @param {鎶ヤ慨鍙傛暟} data + * @returns + */ +export const addRepair = (data) => { + return request({ + url: "/device/repair", + method: "post", + data, + }); +}; + +/** + * @desc 缂栬緫鎶ヤ慨 + * @param {鎶ヤ慨鍙傛暟} data + * @returns + */ +export const editRepair = (data) => { + return request({ + url: "/device/repair", + method: "put", + data, + }); +}; + +/** + * @desc 鏍规嵁id鏌ヨ涓�鏉℃姤淇� + * @param {鎶ヤ慨id} id + * @returns + */ +export const getRepairById = (id) => { + return request({ + url: `/device/repair/${id}`, + method: "get", + }); +}; + +/** + * @desc 鍒犻櫎鎶ヤ慨 + * @param {缂栧彿} ids + * @returns + */ +export const delRepair = (ids) => { + return request({ + url: `/device/repair/${ids}`, + method: "delete", + }); +}; + +export const addMaintain = (data) => { + return request({ + url: `/device/repair/repair`, + method: "post", + data, + }); +}; diff --git a/src/api/equipmentManagement/upkeep.js b/src/api/equipmentManagement/upkeep.js new file mode 100644 index 0000000..c091670 --- /dev/null +++ b/src/api/equipmentManagement/upkeep.js @@ -0,0 +1,72 @@ +import request from "@/utils/request"; + +/** + * @desc 璁惧淇濆吇鍒楄〃鍒嗛〉鏌ヨ + * @param {鍒嗛〉鏌ヨ鍏ュ弬} params + * @returns + */ +export const getUpkeepPage = (params) => { + return request({ + url: "/device/maintenance/page", + method: "get", + params, + }); +}; + +/** + * @desc 璁惧淇濆吇璇︽儏 + * @param {淇濆吇浣嗙紪鍙穧 id + * @returns + */ +export const getUpkeepById = (id) => { + return request({ + url: `/device/maintenance/${id}`, + method: "get", + }); +}; + +/** + * @desc 璁惧淇濆吇鏂板 + * @param {鏂板淇濆吇琛ㄥ崟} data + * @returns + */ +export const addUpkeep = (data) => { + return request({ + url: "/device/maintenance", + method: "post", + data, + }); +}; + +/** + * @desc 璁惧淇濆吇缂栬緫 + * @param {缂栬緫淇濆吇琛ㄥ崟} data + * @returns + */ +export const editUpkeep = (data) => { + return request({ + url: "/device/maintenance", + method: "put", + data, + }); +}; + +/** + * @desc 鏂板淇濆吇琛ㄥ崟 + * @param {鏂板淇濆吇琛ㄥ崟} data + * @returns + */ +export const addMaintenance = (data) => { + return request({ + url: "/device/maintenance/maintenance", + method: "post", + data, + }); +}; + +export const delUpkeep = (id) => { + return request({ + url: `/device/maintenance/${id}`, + method: "delete", + }); +}; diff --git a/src/api/system/user.js b/src/api/system/user.js index 21d60bc..c2553cb 100644 --- a/src/api/system/user.js +++ b/src/api/system/user.js @@ -45,4 +45,11 @@ url: '/system/user/userListNoPage', method: 'get' }) +} +// 鏌ヨ鐢ㄦ埛鍒楄〃 +export function userListNoPageByTenantId() { + return request({ + url: '/system/user/userListNoPageByTenantId', + method: 'get' + }) } \ No newline at end of file diff --git a/src/pages.json b/src/pages.json index 9e29df3..fc70bde 100644 --- a/src/pages.json +++ b/src/pages.json @@ -273,6 +273,20 @@ } }, { + "path": "pages/cooperativeOffice/collaborativeApproval/approve", + "style": { + "navigationBarTitleText": "瀹℃牳", + "navigationStyle": "custom" + } + }, + { + "path": "pages/cooperativeOffice/collaborativeApproval/contactSelect", + "style": { + "navigationBarTitleText": "閫夋嫨鑱旂郴浜�", + "navigationStyle": "custom" + } + }, + { "path": "pages/cooperativeOffice/clientVisit/index", "style": { "navigationBarTitleText": "瀹㈡埛鎷滆", diff --git a/src/pages/cooperativeOffice/collaborativeApproval/approve.vue b/src/pages/cooperativeOffice/collaborativeApproval/approve.vue new file mode 100644 index 0000000..6011e7b --- /dev/null +++ b/src/pages/cooperativeOffice/collaborativeApproval/approve.vue @@ -0,0 +1,519 @@ +<template> + <view class="approve-page"> + + <PageHeader title="瀹℃牳" @back="goBack" /> + + <!-- 鐢宠淇℃伅 --> + <view class="application-info"> + <view class="info-header"> + <text class="info-title">鐢宠淇℃伅</text> + </view> + <view class="info-content"> + <view class="info-row"> + <text class="info-label">鐢宠浜�</text> + <text class="info-value">{{ approvalData.approveUserName }}</text> + </view> + <view class="info-row"> + <text class="info-label">鐢宠閮ㄩ棬</text> + <text class="info-value">{{ approvalData.approveDeptName }}</text> + </view> + <view class="info-row"> + <text class="info-label">鐢宠浜嬬敱</text> + <text class="info-value">{{ approvalData.approveReason }}</text> + </view> + <view class="info-row"> + <text class="info-label">鐢宠鏃ユ湡</text> + <text class="info-value">{{ approvalData.approveTime }}</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="{ + '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> + </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> + </view> + + <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;"> + <text class="opinion-label">绛惧悕锛�</text> + <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 class="input-header"> + <text class="input-title">瀹℃牳鎰忚</text> + </view> + + <view class="input-content"> + <van-field + v-model="approvalOpinion" + type="textarea" + rows="4" + placeholder="璇疯緭鍏ュ鏍告剰瑙�" + maxlength="200" + show-word-limit + /> + </view> + </view> + + <!-- 搴曢儴鎿嶄綔鎸夐挳 --> + <view v-if="canApprove" class="footer-actions"> + <van-button class="reject-btn" @click="handleReject">椹冲洖</van-button> + <van-button class="approve-btn" @click="handleApprove">閫氳繃</van-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' +import { showToast } from 'vant' +import PageHeader from "@/components/PageHeader.vue"; + +const userStore = useUserStore() +const approvalData = ref({}) +const approvalSteps = ref([]) +const approvalOpinion = ref('') +const approveId = ref('') + +// 浠庤鎯呮帴鍙e瓧娈靛榻� canApprove锛氫粎褰撴湁 isShen 鐨勮妭鐐规椂鍙鎵� +const canApprove = computed(() => { + return approvalSteps.value.some(step => step.isShen === true) +}) + +onMounted(() => { + const pages = getCurrentPages() + const currentPage = pages[pages.length - 1] + approveId.value = currentPage.options.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.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) + } + }) +} + +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; + } +} + +.info-label { + font-size: 14px; + color: #666; + width: 80px; + flex-shrink: 0; +} + +.info-value { + font-size: 14px; + color: #333; + flex: 1; +} + +.approval-process { + background: #fff; + margin: 16px; + border-radius: 12px; + overflow: hidden; +} + +.process-header { + padding: 16px; + border-bottom: 1px solid #f0f0f0; + background: #f8f9fa; +} + +.process-title { + font-size: 16px; + font-weight: 600; + color: #333; +} + +.process-steps { + padding: 20px; +} + +.process-step { + display: flex; + position: relative; + margin-bottom: 24px; + + &:last-child { + margin-bottom: 0; + + .step-line { + display: none; + } + } +} + +.step-indicator { + display: flex; + flex-direction: column; + align-items: center; + margin-right: 16px; +} + +.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; +} + +.process-step.completed .step-dot { + background: #52c41a; + color: #fff; +} + +.process-step.current .step-dot { + background: #1890ff; + color: #fff; + animation: pulse 2s infinite; +} + +.process-step.pending .step-dot { + background: #d9d9d9; + color: #999; +} + +.step-line { + width: 2px; + height: 40px; + background: #d9d9d9; + margin-top: 8px; +} + +.process-step.completed .step-line { + background: #52c41a; +} + +.process-step.rejected .step-dot { + background: #ff4d4f; + color: #fff; +} +.process-step.rejected .step-line { + background: #ff4d4f; +} + +.step-content { + flex: 1; + padding-top: 4px; +} + +.step-info { + margin-bottom: 8px; +} + +.step-title { + font-size: 16px; + font-weight: 600; + color: #333; + display: block; + margin-bottom: 4px; +} + +.step-approver { + font-size: 14px; + color: #666; + display: block; + margin-bottom: 4px; +} + +.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; + border: none; +} + +.approve-btn { + width: 120px; + background: #52c41a; + color: #fff; + border: none; +} + +@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); + } +} +.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; +} +</style> \ No newline at end of file diff --git a/src/pages/cooperativeOffice/collaborativeApproval/contactSelect.vue b/src/pages/cooperativeOffice/collaborativeApproval/contactSelect.vue new file mode 100644 index 0000000..9259068 --- /dev/null +++ b/src/pages/cooperativeOffice/collaborativeApproval/contactSelect.vue @@ -0,0 +1,391 @@ +<template> + <view class="contact-select"> + <!-- 椤堕儴鏍囬鏍� --> + <view class="header"> + <up-icon name="arrow-left" size="20" color="#333" @click="goBack" /> + <text class="title">閫夋嫨鑱旂郴浜�</text> + <text class="confirm-btn" @click="confirmSelect">纭畾</text> + </view> + + <!-- 鎼滅储妗� --> +<!-- <view class="search-section">--> +<!-- <van-search--> +<!-- v-model="searchValue"--> +<!-- placeholder="鎼滅储鑱旂郴浜�"--> +<!-- @search="onSearch"--> +<!-- @input="onSearch"--> +<!-- />--> +<!-- </view>--> + + <!-- 宸查�夋嫨鐨勮仈绯讳汉 --> + <view class="selected-section" v-if="selectedContact"> + <view class="selected-header"> + <text class="selected-title">宸查�夋嫨</text> + <text class="clear-btn" @click="clearSelected">娓呯┖</text> + </view> + <view class="selected-item"> + <view class="contact-avatar"> + <text class="avatar-text">{{ selectedContact.nickName.charAt(0) }}</text> + </view> + <view class="contact-details"> + <text class="contact-name">{{ selectedContact.nickName }}</text> + </view> + <van-icon name="cross" size="16" color="#999" @click="clearSelected" /> + </view> + </view> + + <!-- 鑱旂郴浜哄垪琛� --> + <view class="contact-list"> + <view class="list-header"> + <text class="list-title">鍏ㄩ儴鑱旂郴浜�</text> + </view> + + <van-list + v-model:loading="loading" + :finished="finished" + finished-text="娌℃湁鏇村浜�" + @load="onLoad" + > + <view + v-for="contact in userList" + :key="contact.userId" + class="contact-item" + :class="{ 'selected': isSelected(contact) }" + @click="selectContact(contact)" + > + <view class="contact-info"> + <view class="contact-avatar"> + <text class="avatar-text">{{ contact.nickName.charAt(0) }}</text> + </view> + <view class="contact-details"> + <text class="contact-name">{{ contact.nickName }}</text> +<!-- <text class="contact-dept">{{ contact.department }}</text>--> + </view> + </view> + </view> + </van-list> + </view> + </view> +</template> + +<script setup> +import { ref, onMounted } from 'vue' +import { userListNoPageByTenantId } from "@/api/system/user" + +const loading = ref(false) +const finished = ref(false) +const selectedContact = ref(null) +const userList = ref([]) + +// 鎺ユ敹浼犻�掔殑鍙傛暟 +const stepIndex = ref(0) + +onMounted(() => { + // 鑾峰彇椤甸潰鍙傛暟 + const pages = getCurrentPages() + const currentPage = pages[pages.length - 1] + if (currentPage.options.stepIndex !== undefined) { + stepIndex.value = parseInt(currentPage.options.stepIndex) + } + + // 鍒濆鍖栬仈绯讳汉鏁版嵁 + initContacts() +}) + +const initContacts = () => { + userListNoPageByTenantId().then((res) => { + userList.value = res.data + }) + finished.value = true +} + +const onLoad = () => { + // 妯℃嫙鍔犺浇鏇村鏁版嵁 + setTimeout(() => { + loading.value = false + finished.value = true + }, 1000) +} + +const isSelected = (contact) => { + return selectedContact.value && selectedContact.value.userId === contact.userId +} + +const selectContact = (contact) => { + // 鍗曢�夋ā寮忥紝鐩存帴鏇挎崲閫変腑鐨勮仈绯讳汉 + selectedContact.value = contact +} + +const clearSelected = () => { + selectedContact.value = null +} + +const goBack = () => { + uni.navigateBack() +} + +const confirmSelect = () => { + if (!selectedContact.value) { + uni.showToast({ + title: '璇烽�夋嫨涓�涓仈绯讳汉', + icon: 'none' + }) + return + } + // 浣跨敤 uni.$emit 鍙戦�佹暟鎹� + uni.$emit('selectContact', { + stepIndex: stepIndex.value, + contact: selectedContact.value + }) + uni.navigateBack() +} +</script> + +<style scoped lang="scss"> +.contact-select { + min-height: 100vh; + background: #f8f9fa; +} + +.header { + background: #ffffff; + padding: 16px 20px; + display: flex; + align-items: center; + justify-content: space-between; + border-bottom: 1px solid #f0f0f0; + position: sticky; + /* 鍏煎 iOS 鍒樻捣/鐏靛姩宀涘畨鍏ㄥ尯 */ + padding-top: calc(env(safe-area-inset-top)); + top: 0; + z-index: 100; + position: relative; +} + +.title { + font-size: 18px; + font-weight: 600; + color: #333; +} + +.confirm-btn { + color: #006cfb; + font-size: 16px; + font-weight: 500; +} + +.search-section { + background: #fff; + padding: 12px 16px; + border-bottom: 1px solid #f0f0f0; +} + +.selected-section { + background: #fff; + margin-top: 8px; + padding: 16px; +} + +.selected-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 12px; +} + +.selected-title { + font-size: 14px; + color: #333; + font-weight: 500; +} + +.clear-btn { + color: #006cfb; + font-size: 14px; +} + +.selected-item { + display: flex; + align-items: center; + background: #f0f8ff; + border: 1px solid #006cfb; + border-radius: 12px; + padding: 12px; + gap: 12px; + position: relative; + + &::before { + content: ''; + position: absolute; + top: -2px; + right: -2px; + width: 16px; + height: 16px; + background: #52c41a; + border-radius: 50%; + border: 2px solid #fff; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + } + + &::after { + content: '鉁�'; + position: absolute; + top: -1px; + right: -1px; + width: 16px; + height: 16px; + display: flex; + align-items: center; + justify-content: center; + font-size: 10px; + color: #fff; + font-weight: bold; + } +} + +.contact-list { + background: #fff; + margin-top: 8px; +} + +.list-header { + padding: 16px; + border-bottom: 1px solid #f0f0f0; +} + +.list-title { + font-size: 14px; + color: #666; +} + +.contact-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 16px; + border-bottom: 1px solid #f8f9fa; + transition: all 0.2s; + position: relative; + + &.selected { + background-color: #f0f8ff; + + &::before { + content: ''; + position: absolute; + left: 8px; + top: 50%; + transform: translateY(-50%); + width: 4px; + height: 4px; + background: #006cfb; + border-radius: 50%; + box-shadow: 0 0 0 4px rgba(0, 108, 251, 0.2); + } + } + + &:active { + background-color: #f5f5f5; + } +} + +.contact-info { + display: flex; + align-items: center; + flex: 1; + padding-left: 16px; +} + +.contact-avatar { + width: 40px; + height: 40px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + margin-right: 12px; + position: relative; +} + +.avatar-text { + color: #fff; + font-size: 16px; + font-weight: 500; +} + +.contact-details { + flex: 1; +} + +.contact-name { + display: block; + font-size: 16px; + color: #333; +} + +.contact-dept { + font-size: 12px; + color: #999; +} + +// 鑷畾涔夊崟閫夋寜閽牱寮� +:deep(.van-radio) { + .van-radio__icon { + width: 20px; + height: 20px; + border: 2px solid #ddd; + border-radius: 50%; + background: #fff; + position: relative; + transition: all 0.2s; + + &::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%) scale(0); + width: 8px; + height: 8px; + background: #006cfb; + border-radius: 50%; + transition: transform 0.2s; + } + } + + &.van-radio--checked { + .van-radio__icon { + border-color: #006cfb; + background: #fff; + + &::before { + transform: translate(-50%, -50%) scale(1); + } + + &::after { + content: ''; + position: absolute; + top: -2px; + left: -2px; + right: -2px; + bottom: -2px; + border: 2px solid rgba(0, 108, 251, 0.2); + border-radius: 50%; + animation: ripple 0.6s ease-out; + } + } + } +} + +@keyframes ripple { + 0% { + transform: scale(0.8); + opacity: 1; + } + 100% { + transform: scale(1.2); + opacity: 0; + } +} +</style> \ No newline at end of file diff --git a/src/pages/cooperativeOffice/collaborativeApproval/detail.vue b/src/pages/cooperativeOffice/collaborativeApproval/detail.vue index 362b237..219b32a 100644 --- a/src/pages/cooperativeOffice/collaborativeApproval/detail.vue +++ b/src/pages/cooperativeOffice/collaborativeApproval/detail.vue @@ -1,58 +1,74 @@ <template> <view class="account-detail"> - <!-- 椤堕儴鏍囬鏍� --> - <view class="header"> - <up-icon name="arrow-left" size="20" color="#333" @click="goBack" /> - <text class="title">瀹℃壒娴佺▼</text> - </view> + <PageHeader title="瀹℃壒娴佺▼" @back="goBack" /> <!-- 琛ㄥ崟鍖哄煙 --> <view class="form-section"> - <van-form ref="formRef" @submit="submitForm" :rules="rules" input-align="right"> - <van-cell-group inset style="height:auto"> + <van-form ref="formRef" @submit="submitForm" :rules="rules" input-align="right" error-message-align="right" scroll-to-error scroll-to-error-position="center"> + <van-cell-group style="margin-bottom: 16px;"> + <van-field + v-model="form.approveReason" + name="approveReason" + rows="2" + autosize + label="鐢宠浜嬬敱" + type="textarea" + maxlength="200" + :rules="[{ required: true, message: '鐢宠浜嬬敱涓嶈兘涓虹┖' }]" + placeholder="璇疯緭鍏ョ敵璇蜂簨鐢�" + show-word-limit + required + /> + </van-cell-group> + <van-cell-group> + <van-field + v-model="form.approveDeptName" + readonly + name="picker" + label="鐢宠閮ㄩ棬" + placeholder="璇烽�夋嫨鐢宠閮ㄩ棬" + :rules="[{ required: true, message: '璇烽�夋嫨鐢宠閮ㄩ棬' }]" + @click="showPicker = true" + required + /> <van-field - v-model="taxPrice" + v-model="form.approveUserName" name="taxPrice" - label="濮撳悕" - placeholder="璇疯緭鍏ュ鍚�" - :rules="[{ required: true, message: '濮撳悕涓嶈兘涓虹┖' }]" + label="鐢宠浜�" + placeholder="璇疯緭鍏ョ敵璇蜂汉" + :rules="[{ required: true, message: '鐢宠浜轰笉鑳戒负绌�' }]" required readonly - /> - <van-field - v-model="result" - readonly - name="picker" - label="鐢宠閮ㄩ棬" - placeholder="璇烽�夋嫨鐢宠閮ㄩ棬" - :rules="[{ required: true, message: '璇烽�夋嫨鐢宠閮ㄩ棬' }]" - @click="showPicker = true" - required /> <van-popup v-model:show="showPicker" - destroy-on-close position="bottom" > <van-picker - :columns="columns" + :columns="productOptions" :model-value="pickerValue" @confirm="onConfirm" @cancel="showPicker = false" /> </van-popup> - <van-field - v-model="message" - name="message" - rows="1" - autosize - label="鐢宠浜嬬敱" - type="textarea" - placeholder="璇疯緭鍏ョ敵璇蜂簨鐢�" - height="100" - :rules="[{ required: true, message: '鐢宠浜嬬敱涓嶈兘涓虹┖' }]" - required - /> + <van-field + v-model="form.approveTime" + label="鐢宠鏃ユ湡" + placeholder="璇烽�夋嫨" + readonly + required + @click="showDatePicker" + :rules="[{ required: true, message: '璇烽�夋嫨鏉ユ鏃ユ湡' }]" + /> + <!-- 鏃ユ湡閫夋嫨鍣� --> + <van-popup v-model:show="showDate" position="bottom"> + <van-date-picker + v-model="currentDate" + title="閫夋嫨鏃ユ湡" + @confirm="onDateConfirm" + @cancel="showDate = false" + /> + </van-popup> </van-cell-group> </van-form> </view> @@ -60,30 +76,38 @@ <view class="approval-process"> <view class="approval-header"> <text class="approval-title">瀹℃牳娴佺▼</text> - <text class="approval-desc">宸茬敱绠$悊鍛橀璁句笉鍙慨鏀�</text> + <text class="approval-desc">姣忎釜姝ラ鍙兘閫夋嫨涓�涓鎵逛汉</text> </view> <view class="approval-steps"> - <view v-for="(step, stepIndex) in approvalSteps" :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="approvers-container"> - <view v-for="(approver, approverIndex) in step.approvers" :key="approverIndex" class="approver-item"> - <view class="approver-avatar"></view> - <text class="approver-name">{{ approver.name }}</text> - <view class="delete-approver-btn" @click="removeApprover(stepIndex, approverIndex)">脳</view> + <view class="approver-container"> + <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> + <view class="approver-info"> + <text class="approver-name">{{ step.nickName }}</text> + </view> + <view class="delete-approver-btn" @click="removeApprover(stepIndex)">脳</view> </view> - <view 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 < approvalSteps.length - 1"></view> - <view class="delete-step-btn" @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" @click="addApprovalStep"> - <text>鏂板鑺傜偣瀹℃牳浜�</text> + <view class="add-step-btn"> + <van-button icon="plus" plain type="primary" style="width: 100%" @click="addApprovalStep">鏂板鑺傜偣</van-button> </view> </view> @@ -95,166 +119,238 @@ </view> </template> -<script> -import { ref, onMounted } from "vue"; +<script setup> +import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue"; +import PageHeader from "@/components/PageHeader.vue"; +import useUserStore from "@/store/modules/user"; +import {getDept, approveProcessGetInfo, approveProcessAdd, approveProcessUpdate} from "@/api/collaborativeApproval/approvalProcess"; +import { showToast } from 'vant' +import {userListNoPageByTenantId} from "@/api/system/user"; -export default { - setup() { - const rules = ref({ - - taxPrice: { - rules: [{ required: true, errorMessage: '濮撳悕涓嶈兘涓虹┖' }] - }, - result: { - rules: [{ required: true, errorMessage: '璇烽�夋嫨鐢宠閮ㄩ棬' }] - }, - message: { - rules: [{ required: true, errorMessage: '鐢宠浜嬬敱涓嶈兘涓虹┖' }] - }, +const data = reactive({ + form: { + approveTime: "", + approveId: "", + approveUser: "", + approveUserName: "", + approveDeptName: "", + approveDeptId: "", + approveReason: "", + checkResult: "", + tempFileIds: [], + approverList: [] // 鏂板瀛楁锛屽瓨鍌ㄦ墍鏈夎妭鐐圭殑瀹℃壒浜篿d + }, + rules: { + approveTime: [{ required: false, message: "璇疯緭鍏�", trigger: "change" },], + approveId: [{ required: false, message: "璇疯緭鍏�", trigger: "blur" }], + approveUser: [{ required: false, message: "璇疯緭鍏�", trigger: "blur" }], + approveDeptId: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }], + approveReason: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }], + checkResult: [{ required: false, message: "璇疯緭鍏�", trigger: "blur" }], + }, }); - const result = ref(""); - const pickerValue = ref([]); - const showPicker = ref(false); - const columns = ref([]); - onMounted(async () => { - try { - // 鏇挎崲涓哄疄闄呮帴鍙e湴鍧� - // const response = await axios.get('/api/getDepartments'); - columns.value = [ - { - text: "鏉窞", - value: "Hangzhou", - }, - { - text: "瀹佹尝", - value: "Ningbo", - }, - { - text: "娓╁窞", - value: "Wenzhou", - }, - { - text: "缁嶅叴", - value: "Shaoxing", - }, - { - text: "婀栧窞", - value: "Huzhou", - }, - ]; - } catch (error) { - console.error("鑾峰彇閮ㄩ棬鏁版嵁澶辫触:", error); - } - }); - const onConfirm = ({ selectedValues, selectedOptions }) => { - result.value = selectedOptions[0]?.text; - pickerValue.value = selectedValues; - showPicker.value = false; - }; - const taxPrice = ref(""); - const contractAmount = ref(""); - const approvalSteps = ref([ - { approvers: [{ name: '鍗㈠皬鏁�' }, { name: '鍗㈠皬鏁�' }] }, - { approvers: [{ name: '鍗㈠皬鏁�' }] }, - { approvers: [{ name: '鍗㈠皬鏁�' }] }, - { approvers: [{ name: '鍗㈠皬鏁�' }] } - ]); +const { form, rules } = toRefs(data); +const result = ref(""); +const pickerValue = 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([new Date().getFullYear(), new Date().getMonth() + 1, new Date().getDate()]) +const userStore = useUserStore() - const goBack = () => { - uni.navigateBack(); - }; - - const formRef = ref(null); - - const submitForm = () => { - formRef.value.validate().then(() => { - // 琛ㄥ崟鏍¢獙閫氳繃锛屽彲浠ユ彁浜ゆ暟鎹� - console.log("琛ㄥ崟鏁版嵁:", { - taxPrice: taxPrice.value, - department: result.value, - message: message.value, - approvalSteps: approvalSteps.value - }); - - uni.showToast({ - title: "淇濆瓨鎴愬姛", - icon: "success", - }); - }).catch((error) => { - console.error("琛ㄥ崟鏍¢獙澶辫触:", error); - // 鏄剧ず鍏蜂綋鐨勯敊璇俊鎭� - if (error.length > 0) { - const firstError = error[0]; - uni.showToast({ - title: firstError.message || '琛ㄥ崟鏍¢獙澶辫触', - icon: 'none' - }); - } else { - uni.showToast({ - title: '琛ㄥ崟鏍¢獙澶辫触锛岃妫�鏌ュ繀濉」', - icon: 'none' - }); - } - }); - }; - - const message = ref(""); - - const addApprover = (stepIndex) => { - // 鍦ㄦ寚瀹氬鎵规楠ゆ坊鍔犳柊鐨勫鎵逛汉 - approvalSteps.value[stepIndex].approvers.push({ name: '鍗㈠皬鏁�' }); - }; - - const addApprovalStep = () => { - // 娣诲姞鏂扮殑瀹℃壒姝ラ - approvalSteps.value.push({ approvers: [{ name: '鍗㈠皬鏁�' }] }); - }; - - const removeApprover = (stepIndex, approverIndex) => { - // 纭繚姣忎釜姝ラ鑷冲皯淇濈暀涓�涓鎵逛汉 - if (approvalSteps.value[stepIndex].approvers.length > 1) { - approvalSteps.value[stepIndex].approvers.splice(approverIndex, 1); - } else { - uni.showToast({ - title: '姣忎釜姝ラ鑷冲皯闇�瑕佷竴涓鎵逛汉', - icon: 'none' - }); - } - }; - - const removeApprovalStep = (stepIndex) => { - // 纭繚鑷冲皯淇濈暀涓�涓鎵规楠� - if (approvalSteps.value.length > 1) { - approvalSteps.value.splice(stepIndex, 1); - } else { - uni.showToast({ - title: '鑷冲皯闇�瑕佷竴涓鎵规楠�', - icon: 'none' - }); - } - }; - - return { - rules, - removeApprovalStep, - removeApprover, - result, - pickerValue, - columns, - onConfirm, - showPicker, - taxPrice, - contractAmount, - goBack, - submitForm, - approvalSteps, - addApprover, - addApprovalStep, - formRef, - message - }; - }, +const getProductOptions = () => { + getDept().then((res) => { + productOptions.value = res.data.map(item => ({ + value: item.deptId, + text: 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(); + + // 鑾峰彇URL鍙傛暟 + const pages = getCurrentPages(); + const currentPage = pages[pages.length - 1]; + operationType.value = currentPage.options.operationType || 'add'; + + // 濡傛灉鏄紪杈戞ā寮忥紝浠庢湰鍦板瓨鍌ㄨ幏鍙栨暟鎹� + 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()); + // 浠巙serList涓壘鍒板搴旂殑鐢ㄦ埛淇℃伅 + 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 = ({ selectedValues, selectedOptions }) => { + form.value.approveDeptName = selectedOptions[0]?.text; + form.value.approveDeptId = selectedOptions[0]?.value; + pickerValue.value = selectedValues; + showPicker.value = false; +}; + +const goBack = () => { + // 娓呴櫎鏈湴瀛樺偍鐨勬暟鎹� + uni.removeStorageSync('invoiceLedgerEditRow'); + uni.navigateBack(); +}; + +const submitForm = () => { + // 妫�鏌ユ瘡涓鎵规楠ゆ槸鍚﹂兘鏈夊鎵逛汉 + const hasEmptyStep = approverNodes.value.some(step => !step.nickName); + if (hasEmptyStep) { + showToast('璇蜂负姣忎釜瀹℃壒姝ラ閫夋嫨瀹℃壒浜�'); + return; + } + + formRef.value.validate().then(() => { + // 琛ㄥ崟鏍¢獙閫氳繃锛屽彲浠ユ彁浜ゆ暟鎹� + // 鏀堕泦鎵�鏈夎妭鐐圭殑瀹℃壒浜篿d + console.log('approverNodes---', approverNodes.value) + form.value.approveUserIds = approverNodes.value.map(node => node.userId).join(',') + form.value.approveType = 0 + 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.length > 0) { + const firstError = error[0]; + uni.showToast({ + title: firstError.message || '琛ㄥ崟鏍¢獙澶辫触', + icon: 'none' + }); + } else { + 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.navigateTo({ + url: `/pages/cooperativeOffice/collaborativeApproval/contactSelect?stepIndex=${stepIndex}` + }); +}; + +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 = ({ selectedValues }) => { + form.value.approveTime = selectedValues.join('-') + currentDate.value = selectedValues + showDate.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"> @@ -287,74 +383,6 @@ margin-top: 16px; } -.van-field { - height: 56px; - line-height: 36px; -} - -.product-section { - background: #fff; - margin: 16px; - border-radius: 16px; - padding: 20px 16px 8px 16px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); -} - -.section-header { - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: 12px; -} - -.section-title { - font-size: 16px; - font-weight: 600; - color: #333; -} - -.add-btn { - background: #2979ff; - color: #fff; - border-radius: 8px; - padding: 4px 16px; - font-size: 14px; -} - -.product-card { - background: #f8f9fa; - border-radius: 12px; - padding: 12px; - margin-bottom: 16px; - box-shadow: 0 1px 4px rgba(41, 121, 255, 0.06); - position: relative; -} - -.product-row { - display: flex; - align-items: center; - margin-bottom: 8px; -} - -.product-label { - min-width: 60px; - color: #888; - font-size: 13px; -} - -.del-row { - justify-content: flex-end; -} - -.del-btn { - background: #ff4d4f; - color: #fff; - border-radius: 8px; - padding: 4px 16px; - font-size: 13px; - margin-top: 4px; -} - .approval-process { background: #fff; margin: 16px; @@ -380,156 +408,347 @@ color: #999; } +/* 鏍峰紡澧炲己涓衡�滅畝娲佸皬鍦嗗湀椋庢牸鈥� */ .approval-steps { - padding-left: 16px; + 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: 20px; + 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: 2px 8px; - border-radius: 4px; -} - -.approvers-container { - display: flex; - flex-wrap: wrap; - gap: 12px; - margin-bottom: 8px; + padding: 4px 12px; + border-radius: 12px; + position: relative; + line-height: 1.4; // 纭繚鏂囧瓧琛岄珮涓�鑷� } .approver-item { display: flex; - flex-direction: column; align-items: center; - width: 60px; + 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: 40px; - height: 40px; - background: #e6f7ff; + width: 48px; + height: 48px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 50%; - margin-bottom: 4px; display: flex; align-items: center; justify-content: center; + position: relative; + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3); } -.approver-avatar::after { - content: '馃懁'; - font-size: 20px; +.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 { - font-size: 12px; + display: block; + font-size: 16px; color: #333; - text-align: center; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - margin-bottom: 2px; + 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%; + } } .delete-approver-btn { - font-size: 12px; + font-size: 16px; color: #ff4d4f; - background: rgba(255, 77, 79, 0.1); - width: 16px; - height: 16px; + 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; - margin-top: 2px; + transition: all 0.3s ease; + position: relative; +} + +.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; + } } .delete-step-btn { - margin-top: 8px; color: #ff4d4f; font-size: 12px; - background: rgba(255, 77, 79, 0.1); - padding: 2px 8px; - border-radius: 4px; + 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%; } - -.add-approver-btn { - width: 40px; - height: 40px; - border: 1px dashed #ccc; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - font-size: 20px; - color: #999; - margin-top: 8px; } .step-line { - position: absolute; - left: 20px; - top: 100%; - width: 1px; - height: 30px; - background: #e0e0e0; + display: none; // 闅愯棌鍘熸潵鐨勭嚎鏉★紝浣跨敤浼厓绱犱唬鏇� } .add-step-btn { display: flex; align-items: center; justify-content: center; - margin-top: 16px; - color: #006cfb; - font-size: 14px; - padding: 8px 0; - border: 1px dashed #006cfb; - border-radius: 8px; } - .footer-btns { - position: fixed; - left: 0; - right: 0; - bottom: 0; - background: #fff; - display: flex; - justify-content: space-around; - align-items: center; - padding: 12px 0; - box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.05); - z-index: 1000; + 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: 16px; - color: #ffffff; - width: 102px; - background: #c7c9cc; - box-shadow: 0px 4px 10px 0px rgba(3, 88, 185, 0.2); - border-radius: 40px 40px 40px 40px; + 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: 16px; - color: #ffffff; - width: 224px; - background: linear-gradient(140deg, #00baff 0%, #006cfb 100%); - box-shadow: 0px 4px 10px 0px rgba(3, 88, 185, 0.2); - border-radius: 40px 40px 40px 40px; + 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; + } + 50% { + transform: scale(1.2); + opacity: 0.7; + } + 100% { + transform: scale(1); + opacity: 1; + } +} + +@keyframes rotate { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +@keyframes ripple { + 0% { + transform: translate(-50%, -50%) scale(0.8); + opacity: 1; + } + 100% { + transform: translate(-50%, -50%) scale(1.6); + opacity: 0; + } +} + +/* 濡傛灉宸叉湁 .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> \ No newline at end of file diff --git a/src/pages/cooperativeOffice/collaborativeApproval/index.vue b/src/pages/cooperativeOffice/collaborativeApproval/index.vue index 0ae745b..a8b4654 100644 --- a/src/pages/cooperativeOffice/collaborativeApproval/index.vue +++ b/src/pages/cooperativeOffice/collaborativeApproval/index.vue @@ -8,51 +8,87 @@ <view class="search-filter-section"> <view class="search-bar"> <view class="search-input"> - <u-input placeholder="璇疯緭鍏ラ噰璐悎鍚屽彿" class="search-text" v-model="searchKeyword"> - <template #suffix> - <up-icon name="search" size="24" color="#999" @click="getList"></up-icon> - </template> - </u-input> + <input + class="search-text" + placeholder="璇疯緭鍏ユ祦绋嬬紪鍙�" + v-model="searchForm.approveId" + /> </view> - <view class="filter-button" @click="showFilterOptions"> - <van-icon name="filter-o" size="24" color="#999"></van-icon> + <view class="search-button" @click="getList"> + <up-icon name="search" size="24" color="#999"></up-icon> </view> </view> </view> - <!-- 閿�鍞彴璐︾�戝竷娴� --> - <view class="ledger-list" v-if="total > 0"> + <!-- 瀹℃壒鍒楄〃 --> + <view class="ledger-list" v-if="ledgerList.length > 0"> <view v-for="(item, index) in ledgerList" :key="index"> - <view class="ledger-item" @click="handleItemClick(item)"> + <view class="ledger-item"> <view class="item-header"> <view class="item-left"> <view class="document-icon"> <up-icon name="file-text" size="16" color="#ffffff"></up-icon> </view> - <text class="item-id">{{ item.salesContractNo }}</text> + <text class="item-id">{{ item.approveId }}</text> + </view> + <view class="item-tag"> + <van-tag :type="getTagClass(item.approveStatus)" size="medium">{{ formatReceiptType(item.approveStatus) }}</van-tag> </view> </view> <up-divider></up-divider> <view class="item-details"> - <view class="detail-info"> - <view class="detail-row"> - <text class="detail-label">鐢宠浜�</text> - <text class="detail-value">{{ item.entryPersonName }}</text> - </view> - <view class="detail-row"> - <text class="detail-label">鐢宠鏃ユ湡</text> - <text class="detail-value highlightBlue">{{ item.entryDate }}</text> - </view> + <view class="detail-row"> + <text class="detail-label">鐢宠浜�</text> + <text class="detail-value">{{ item.approveUserName }}</text> </view> + <view class="detail-row"> + <text class="detail-label">鐢宠閮ㄩ棬</text> + <text class="detail-value">{{ item.approveDeptName }}</text> + </view> + <view class="detail-row-approveReason"> + <text class="detail-label">瀹℃壒浜嬬敱</text> + <text class="detail-value highlightBlue">{{ item.approveReason }}</text> + </view> + <view class="detail-row"> + <text class="detail-label">鐢宠鏃ユ湡</text> + <text class="detail-value">{{ item.approveTime }}</text> + </view> + <view class="detail-row"> + <text class="detail-label">缁撴潫鏃ユ湡</text> + <text class="detail-value">{{ item.approveOverTime }}</text> + </view> + <up-divider></up-divider> <view class="detail-info"> - <view class="detail-row"> - <text class="detail-label">鐢宠閮ㄩ棬</text> - <text class="detail-value">{{ item.entryPersonName }}</text> + <view class="detail-row-user"> + <text class="detail-label">褰撳墠瀹℃壒浜�</text> + <view class="detail-value approver-value"> + <view class="approver-chip"> + <text class="approver-name">{{ item.approveUserCurrentName || '鏈垎閰�' }}</text> + </view> + </view> </view> <view class="detail-row"> - <text class="detail-label">瀹℃壒鐘舵��</text> - <text class="detail-value highlightYellow">{{ item.entryDate }}</text> + <view class="actions"> + <van-button + type="primary" + size="small" + class="action-btn edit" + :disabled="item.approveStatus == 2 || item.approveStatus == 1 || item.approveStatus == 4" + @click="handleItemClick(item)" + > + 缂栬緫 + </van-button> + <van-button + type="success" + size="small" + class="action-btn approve" + :disabled="item.approveUserCurrentId == null || item.approveStatus == 2 || item.approveStatus == 3 || item.approveStatus == 4 || item.approveUserCurrentId !== userStore.id" + @click="approve(item)" + > + 瀹℃牳 + </van-button> + </view> </view> </view> </view> @@ -62,31 +98,34 @@ <view v-else class="no-data"> <text>鏆傛棤瀹℃壒鏁版嵁</text> </view> - +<van-floating-bubble icon="plus" @click="handleAdd"/> <!-- 娴姩鎿嶄綔鎸夐挳 --> - <view class="fab-button" @click="handleAdd"> + <!-- <view class="fab-button" @click="handleAdd"> <up-icon name="plus" size="24" color="#ffffff"></up-icon> - </view> + </view> --> </view> </template> <script setup> import { ref, - reactive, - onMounted + toRefs, + reactive } from "vue"; - import { - ledgerListPage - } from "@/api/cooperativeOffice/collaborativeApproval"; import PageHeader from "@/components/PageHeader.vue"; - - // 鎼滅储鍏抽敭璇� - const searchKeyword = ref(""); - - // 閿�鍞彴璐︽暟鎹� + import {approveProcessListPage} from "@/api/collaborativeApproval/approvalProcess"; + import {onShow} from "@dcloudio/uni-app"; + import useUserStore from "@/store/modules/user"; + + const userStore = useUserStore() + // 鏁版嵁 const ledgerList = ref([]); - const total = ref(0); + const data = reactive({ + searchForm: { + approveId: "", + }, + }); + const { searchForm } = toRefs(data); // 杩斿洖涓婁竴椤� const goBack = () => { @@ -98,12 +137,11 @@ current: -1, size: -1, }; - ledgerListPage({ - ...page + approveProcessListPage({ + ...page,approveType: 0,...searchForm.value }) .then((res) => { - ledgerList.value = res.records; - total.value = res.total; + ledgerList.value = res.data.records; }) .catch(() => { // tableLoading.value = false; @@ -118,23 +156,58 @@ }, }); }; + // 鏍煎紡鍖栧洖娆炬柟寮� + const formatReceiptType = (params) => { + if (params == 0) { + return "寰呭鏍�"; + } else if (params == 1) { + return "瀹℃牳涓�"; + } else if (params == 2) { + return "瀹℃牳瀹屾垚"; + } else if (params == 4) { + return "宸查噸鏂版彁浜�"; + } else { + return '涓嶉�氳繃'; + } + }; + // 鑾峰彇鏍囩鏍峰紡绫� + const getTagClass = (type) => { + if (type == 0) { + return "warning"; + } else if (type == 1) { + return "primary"; + } else if (type == 2) { + return "success"; + } else if (type == 4) { + return "primary"; + } else { + return "danger"; + } + }; // 鐐瑰嚮鍒楄〃椤� const handleItemClick = (item) => { - uni.showToast({ - title: `鏌ョ湅鍚堝悓: ${item.contractId}`, - icon: "none", + // 浣跨敤鏈湴瀛樺偍浼犻�掓暟鎹� + uni.setStorageSync('invoiceLedgerEditRow', JSON.stringify(item)); + uni.navigateTo({ + url: `/pages/cooperativeOffice/collaborativeApproval/detail?operationType=edit&approveId=${item.approveId}`, }); }; // 娣诲姞鏂拌褰� const handleAdd = () => { uni.navigateTo({ - url: "/pages/cooperativeOffice/collaborativeApproval/detail", + url: "/pages/cooperativeOffice/collaborativeApproval/detail?operationType=add", }); }; + // 鐐瑰嚮瀹℃牳 + const approve = (item) => { + uni.navigateTo({ + url: `/pages/cooperativeOffice/collaborativeApproval/approve?approveId=${item.approveId}` + }) + } - onMounted(() => { + onShow(() => { // 椤甸潰鍔犺浇瀹屾垚鍚庣殑鍒濆鍖栭�昏緫 getList(); }); @@ -150,7 +223,27 @@ background: #f8f9fa; position: relative; } - + .search-input { + flex: 1; + background: #f5f5f5; + border-radius: 24px; + padding: 10px 16px; + display: flex; + align-items: center; + gap: 8px; + } + .search-text { + flex: 1; + font-size: 14px; + color: #333; + background: transparent; + border: none; + outline: none; + } + + .search-text::placeholder { + color: #999; + } .search-filter-section { @@ -163,17 +256,16 @@ align-items: center; gap: 12px; } - + .search-input { flex: 1; background: #f5f5f5; border-radius: 24px; - padding: 4px 16px; + padding: 10px 16px; display: flex; align-items: center; gap: 8px; } - .search-text { flex: 1; font-size: 14px; @@ -182,7 +274,7 @@ border: none; outline: none; } - + .search-text::placeholder { color: #999; } @@ -239,7 +331,6 @@ } .item-tag { - background: #4caf50; border-radius: 4px; padding: 2px 4px; } @@ -253,16 +344,27 @@ .item-details { padding: 16px 0; } - + .detail-row { display: flex; align-items: flex-end; justify-content: space-between; margin-bottom: 8px; - + &:last-child { margin-bottom: 0; } + } + .detail-row-user { + display: flex; + align-items: center; + justify-content: space-between; + } + .detail-row-approveReason { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 8px; } .detail-info { @@ -316,4 +418,50 @@ box-shadow: 0 4px 16px rgba(41, 121, 255, 0.3); z-index: 1000; } + + .approver-value { + display: flex; + justify-content: flex-end; + } + .approver-chip { + display: inline-flex; + align-items: center; + gap: 6px; + background: #f0f6ff; + color: #2b7cff; + border: 1px solid #e0efff; + border-radius: 999px; + padding: 4px 10px; + max-width: 100%; + } + .approver-name { + font-size: 12px; + color: #2b7cff; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .actions { + display: flex; + gap: 10px; + align-items: center; + justify-content: flex-end; + } + + .action-btn { + border-radius: 16px; + height: 28px; + line-height: 28px; + padding: 0 12px; + } + .action-btn.edit { + /* primary 鏍峰紡鏉ヨ嚜缁勪欢锛岃繖閲屼繚鐣欓挬瀛愪互渚垮悗缁渶瑕佹墿灞� */ + } + .action-btn.approve { + /* success 鏍峰紡鏉ヨ嚜缁勪欢锛岃繖閲屼繚鐣欓挬瀛愪互渚垮悗缁渶瑕佹墿灞� */ + } + :deep(.van-floating-bubble) { + background: #ed8d05; + } </style> \ No newline at end of file -- Gitblit v1.9.3