| src/api/salesManagement/salesLedger.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages.json | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages/procurementManagement/paymentLedger/detail.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages/sales/salesAccount/goOut.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages/sales/salesAccount/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/pages/sales/salesAccount/out.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
src/api/salesManagement/salesLedger.js
@@ -125,3 +125,13 @@ params: query }) } // æ°å¢åè´§ä¿¡æ¯ export function addShippingInfo(data) { return request({ url: "/shippingInfo/add", method: "post", data, }); } src/pages.json
@@ -58,6 +58,20 @@ } }, { "path": "pages/sales/salesAccount/out", "style": { "navigationBarTitleText": "åè´§ç¶æ", "navigationStyle": "custom" } }, { "path": "pages/sales/salesAccount/goOut", "style": { "navigationBarTitleText": "åè´§", "navigationStyle": "custom" } }, { "path": "pages/sales/salesAccount/detail", "style": { "navigationBarTitleText": "ä¿®æ¹å°è´¦", src/pages/procurementManagement/paymentLedger/detail.vue
@@ -1,294 +1,305 @@ <template> <view class="receipt-payment-detail"> <!-- 使ç¨éç¨é¡µé¢å¤´é¨ç»ä»¶ --> <PageHeader title="ä¾åºå徿¥è¯¦æ " @back="goBack" /> <!-- ç»è®¡ä¿¡æ¯ --> <view class="summary-info" v-if="tableData.length > 0"> <view class="summary-item"> <text class="summary-label">æ»è®°å½æ°</text> <text class="summary-value">{{ tableData.length }}</text> </view> <view class="summary-item"> <text class="summary-label">å¼ç¥¨æ»éé¢</text> <text class="summary-value">{{ formatAmount(invoiceTotal) }}</text> </view> <view class="summary-item"> <text class="summary-label">忬¾æ»éé¢</text> <text class="summary-value highlight">{{ formatAmount(receiptTotal) }}</text> </view> <view class="summary-item"> <text class="summary-label">åºæ¶æ»éé¢</text> <text class="summary-value danger">{{ formatAmount(unReceiptTotal) }}</text> </view> </view> <!-- 忬¾è®°å½æç»å表 --> <view class="detail-list" v-if="tableData.length > 0"> <view v-for="(item, index) in tableData" :key="index" class="detail-item"> <view class="item-header"> <view class="item-left"> <view class="record-icon"> <up-icon name="file-text" size="16" color="#ffffff"></up-icon> </view> <text class="item-index">{{ index + 1 }}</text> </view> <view class="item-date">{{ item.happenTime }}</view> </view> <up-divider></up-divider> <view class="item-details"> <view class="detail-row"> <text class="detail-label">å票éé¢(å )</text> <text class="detail-value">{{ formatAmount(item.invoiceAmount) }}</text> </view> <view class="detail-row"> <text class="detail-label">仿¬¾éé¢(å )</text> <text class="detail-value highlight">{{ formatAmount(item.currentPaymentAmount) }}</text> </view> <view class="detail-row"> <text class="detail-label">åºä»éé¢(å )</text> <text class="detail-value danger">{{ formatAmount(item.payableAmount) }}</text> </view> </view> </view> </view> <view v-else class="no-data"> <text>ææ åæ¬¾è®°å½</text> </view> </view> <view class="receipt-payment-detail"> <!-- 使ç¨éç¨é¡µé¢å¤´é¨ç»ä»¶ --> <PageHeader title="ä¾åºå徿¥è¯¦æ " @back="goBack" /> <!-- ç»è®¡ä¿¡æ¯ --> <view class="summary-info" v-if="tableData.length > 0"> <view class="summary-item"> <text class="summary-label">æ»è®°å½æ°</text> <text class="summary-value">{{ tableData.length }}</text> </view> <view class="summary-item"> <text class="summary-label">å¼ç¥¨æ»éé¢</text> <text class="summary-value">{{ formatAmount(invoiceTotal) }}</text> </view> <view class="summary-item"> <text class="summary-label">忬¾æ»éé¢</text> <text class="summary-value highlight">{{ formatAmount(receiptTotal) }}</text> </view> <view class="summary-item"> <text class="summary-label">åºæ¶æ»éé¢</text> <text class="summary-value danger">{{ formatAmount(unReceiptTotal) }}</text> </view> </view> <!-- 忬¾è®°å½æç»å表 --> <view class="detail-list" v-if="tableData.length > 0"> <view v-for="(item, index) in tableData" :key="index" class="detail-item"> <view class="item-header"> <view class="item-left"> <view class="record-icon"> <up-icon name="file-text" size="16" color="#ffffff"></up-icon> </view> <text class="item-index">{{ index + 1 }}</text> </view> <view class="item-date">{{ item.happenTime }}</view> </view> <up-divider></up-divider> <view class="item-details"> <view class="detail-row"> <text class="detail-label">å票éé¢(å )</text> <text class="detail-value">{{ formatAmount(item.invoiceAmount) }}</text> </view> <view class="detail-row"> <text class="detail-label">仿¬¾éé¢(å )</text> <text class="detail-value highlight">{{ formatAmount(item.currentPaymentAmount) }}</text> </view> <view class="detail-row"> <text class="detail-label">åºä»éé¢(å )</text> <text class="detail-value danger">{{ formatAmount(item.payableAmount) }}</text> </view> </view> </view> </view> <view v-else class="no-data"> <text>ææ åæ¬¾è®°å½</text> </view> </view> </template> <script setup> import { ref, computed, onMounted } from 'vue'; import { onShow } from '@dcloudio/uni-app'; import {paymentLedgerList, paymentRecordList} from "@/api/procurementManagement/paymentLedger"; import { ref, computed, onMounted } from "vue"; import { onShow } from "@dcloudio/uni-app"; import { paymentLedgerList, paymentRecordList, } from "@/api/procurementManagement/paymentLedger"; // 客æ·ä¿¡æ¯ const supplierId = ref(''); // 客æ·ä¿¡æ¯ const supplierId = ref(""); // è¡¨æ ¼æ°æ® const tableData = ref([]); // è¡¨æ ¼æ°æ® const tableData = ref([]); const invoiceTotal = computed(() => { return tableData.value.reduce((sum, item) => { return sum + (parseFloat(item.invoiceAmount) || 0); }, 0); }); const invoiceTotal = computed(() => { return tableData.value.reduce((sum, item) => { return sum + (parseFloat(item.invoiceAmount) || 0); }, 0); }); const receiptTotal = computed(() => { return tableData.value.reduce((sum, item) => { return sum + (parseFloat(item.receiptAmount) || 0); }, 0); }); const receiptTotal = computed(() => { return tableData.value.reduce((sum, item) => { return sum + (parseFloat(item.receiptAmount) || 0); }, 0); }); const unReceiptTotal = computed(() => { return tableData.value.reduce((sum, item) => { return sum + (parseFloat(item.unReceiptAmount) || 0); }, 0); }); const unReceiptTotal = computed(() => { return tableData.value.reduce((sum, item) => { return sum + (parseFloat(item.unReceiptAmount) || 0); }, 0); }); // è¿åä¸ä¸é¡µ const goBack = () => { uni.removeStorageSync('supplierId') uni.navigateBack(); }; // è¿åä¸ä¸é¡µ const goBack = () => { uni.removeStorageSync("supplierId"); uni.navigateBack(); }; // è·å页é¢åæ° const getPageParams = () => { // 仿¬å°åå¨è·åä¾åºåID const storedSupplierId = uni.getStorageSync('supplierId'); if (storedSupplierId) { supplierId.value = storedSupplierId; } }; // è·å页é¢åæ° const getPageParams = () => { // 仿¬å°åå¨è·åä¾åºåID const storedSupplierId = uni.getStorageSync("supplierId"); if (storedSupplierId) { supplierId.value = storedSupplierId; } }; // æ¥è¯¢å表 const getList = () => { if (!supplierId.value) { uni.showToast({ title: '客æ·ä¿¡æ¯ç¼ºå¤±', icon: 'error' }); return; } showLoadingToast('å è½½ä¸...') paymentRecordList(supplierId.value).then((res) => { tableData.value = res.data; closeToast() }).catch(() => { closeToast() uni.showToast({ title: 'æ¥è¯¢å¤±è´¥', icon: 'error' }); }); }; // æ¥è¯¢å表 const getList = () => { if (!supplierId.value) { uni.showToast({ title: "客æ·ä¿¡æ¯ç¼ºå¤±", icon: "error", }); return; } showLoadingToast("å è½½ä¸..."); paymentRecordList(supplierId.value) .then(res => { tableData.value = res.data; closeToast(); }) .catch(() => { closeToast(); uni.showToast({ title: "æ¥è¯¢å¤±è´¥", icon: "error", }); }); }; // æ ¼å¼åéé¢ const formatAmount = (amount) => { return amount ? parseFloat(amount).toFixed(2) : '0.00'; }; // æ ¼å¼åéé¢ const formatAmount = amount => { return amount ? parseFloat(amount).toFixed(2) : "0.00"; }; // æ¾ç¤ºå è½½æç¤º const showLoadingToast = (message) => { uni.showLoading({ title: message, mask: true }); }; // æ¾ç¤ºå è½½æç¤º const showLoadingToast = message => { uni.showLoading({ title: message, mask: true, }); }; // å ³éæç¤º const closeToast = () => { uni.hideLoading(); }; // å ³éæç¤º const closeToast = () => { uni.hideLoading(); }; onMounted(() => { // 页é¢å è½½æ¶è·ååæ°å¹¶å·æ°å表 getPageParams(); getList(); }); onMounted(() => { // 页é¢å è½½æ¶è·ååæ°å¹¶å·æ°å表 getPageParams(); getList(); }); </script> <style scoped lang="scss"> .receipt-payment-detail { min-height: 100vh; background: #f8f9fa; position: relative; } .receipt-payment-detail { min-height: 100vh; background: #f8f9fa; position: relative; } .u-divider { margin: 0 !important; } .u-divider { margin: 0 !important; } .summary-info { background: #ffffff; margin: 20px 20px 0 20px; border-radius: 12px; padding: 16px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); } .summary-info { background: #ffffff; margin: 20px 20px 0 20px; border-radius: 12px; padding: 16px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); } .summary-item { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; &:last-child { margin-bottom: 0; } } .summary-item { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; .summary-label { font-size: 14px; color: #666; } &:last-child { margin-bottom: 0; } } .summary-value { font-size: 14px; color: #333; font-weight: 500; } .summary-label { font-size: 14px; color: #666; } .summary-value.highlight { color: #2979ff; font-weight: 600; } .summary-value { font-size: 14px; color: #333; font-weight: 500; } .summary-value.danger { color: #ff4757; font-weight: 600; } .summary-value.highlight { color: #2979ff; font-weight: 600; } .detail-list { padding: 20px; } .summary-value.danger { color: #ff4757; font-weight: 600; } .detail-item { background: #ffffff; border-radius: 12px; margin-bottom: 16px; overflow: hidden; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); padding: 0 16px; } .detail-list { padding: 20px; } .item-header { padding: 10px 0; display: flex; align-items: center; justify-content: space-between; } .detail-item { background: #ffffff; border-radius: 12px; margin-bottom: 16px; overflow: hidden; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); padding: 0 16px; } .item-left { display: flex; align-items: center; gap: 8px; } .item-header { padding: 10px 0; display: flex; align-items: center; justify-content: space-between; } .record-icon { width: 24px; height: 24px; background: #2979ff; border-radius: 4px; display: flex; align-items: center; justify-content: center; } .item-left { display: flex; align-items: center; gap: 8px; } .item-index { font-size: 14px; color: #333; font-weight: 500; } .record-icon { width: 24px; height: 24px; background: #2979ff; border-radius: 4px; display: flex; align-items: center; justify-content: center; } .item-date { font-size: 12px; color: #666; } .item-index { font-size: 14px; color: #333; font-weight: 500; } .item-details { padding: 16px 0; } .item-date { font-size: 12px; color: #666; } .detail-row { display: flex; align-items: flex-end; justify-content: space-between; margin-bottom: 8px; &:last-child { margin-bottom: 0; } } .item-details { padding: 16px 0; } .detail-label { font-size: 12px; color: #777777; min-width: 60px; } .detail-row { display: flex; align-items: flex-end; justify-content: space-between; margin-bottom: 8px; .detail-value { font-size: 12px; color: #000000; text-align: right; flex: 1; margin-left: 16px; } &:last-child { margin-bottom: 0; } } .detail-value.highlight { color: #2979ff; font-weight: 500; } .detail-label { font-size: 12px; color: #777777; min-width: 60px; } .detail-value.danger { color: #ff4757; font-weight: 500; } .detail-value { font-size: 12px; color: #000000; text-align: right; flex: 1; margin-left: 16px; } .no-data { padding: 40px 0; text-align: center; color: #999; } .detail-value.highlight { color: #2979ff; font-weight: 500; } .detail-value.danger { color: #ff4757; font-weight: 500; } .no-data { padding: 40px 0; text-align: center; color: #999; } </style> src/pages/sales/salesAccount/goOut.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,657 @@ <template> <view class="account-detail"> <PageHeader title="åè´§" @back="goBack" /> <!-- 表ååºå --> <u-form ref="formRef" @submit="submitForm" :rules="rules" :model="form" label-width="140rpx"> <u-form-item prop="typeValue" label="åè´§ç±»å" required> <u-input v-model="typeValue" readonly placeholder="è¯·éæ©åè´§æ¹å¼" @click="showPicker = true" /> <template #right> <up-icon name="arrow-right" @click="showPicker = true"></up-icon> </template> </u-form-item> </u-form> <!-- éæ©å¨å¼¹çª --> <up-action-sheet :show="showPicker" :actions="productOptions" title="åè´§æ¹å¼" @select="onConfirm" @close="showPicker = false" /> <!-- å®¡æ ¸æµç¨åºå --> <view class="approval-process"> <view class="approval-header"> <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 class="step-dot"></view> <view class="step-title"> <text>审æ¹äºº</text> </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 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> </view> <view class="add-step-btn"> <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> </view> </view> </template> <script setup> import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue"; import PageHeader from "@/components/PageHeader.vue"; import { addShippingInfo } from "@/api/salesManagement/salesLedger"; 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: { typeValue: [{ required: false, message: "è¯·éæ©", trigger: "change" }], }, }); const { form, rules } = toRefs(data); const showPicker = ref(false); const productOptions = ref([ { value: "货车", name: "货车", }, { value: "å¿«é", name: "å¿«é", }, ]); const operationType = ref(""); const currentApproveStatus = ref(""); const approverNodes = ref([]); const userList = ref([]); const formRef = ref(null); const approveType = ref(0); const goOutData = ref({}); onMounted(async () => { try { userListNoPageByTenantId().then(res => { userList.value = res.data; }); // 仿¬å°åå¨è·åå货详æ goOutData.value = JSON.parse(uni.getStorageSync("goOutData")); console.log(goOutData.value, "goOutData.value"); // åå§åå®¡æ¹æµç¨èç¹ï¼é»è®¤ä¸ä¸ªèç¹ approverNodes.value = [{ id: 1, userId: null }]; // çå¬èç³»äººéæ©äºä»¶ uni.$on("selectContact", handleSelectContact); } catch (error) { console.error("è·å失败:", error); } }); onUnmounted(() => { // ç§»é¤äºä»¶çå¬ uni.$off("selectContact", handleSelectContact); }); const typeValue = ref("货车"); const onConfirm = item => { // 设置éä¸çé¨é¨ typeValue.value = item.name; 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; } formRef.value .validate() .then(valid => { if (valid) { // è¡¨åæ ¡éªéè¿ï¼å¯ä»¥æäº¤æ°æ® // æ¶éææèç¹ç审æ¹äººid console.log("approverNodes---", approverNodes.value); const approveUserIds = approverNodes.value .map(node => node.userId) .join(","); const params = { salesLedgerId: goOutData.value.salesLedgerId, salesLedgerProductId: goOutData.value.id, type: typeValue.value, approveUserIds, }; console.log(params, "params"); addShippingInfo(params).then(res => { showToast("åè´§æå"); setTimeout(() => { goBack(); }, 500); }); } }) .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", }); } }; </script> <style scoped lang="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; 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; } .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%; } } .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; } .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 { 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 { 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; } 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> src/pages/sales/salesAccount/index.vue
@@ -1,196 +1,216 @@ <template> <view class="sales-account"> <!-- 使ç¨éç¨é¡µé¢å¤´é¨ç»ä»¶ --> <PageHeader title="éå®å°è´¦" @back="goBack" /> <!-- æç´¢åçéåºå --> <view class="search-section"> <view class="search-bar"> <view class="search-input"> <up-input class="search-text" placeholder="请è¾å ¥éå®ååå·æç´¢" v-model="salesContractNo" @change="getList" clearable /> </view> <view class="filter-button" @click="getList"> <up-icon name="search" size="24" color="#999"></up-icon> </view> </view> </view> <!-- éå®å°è´¦ç叿µ --> <view class="ledger-list" v-if="ledgerList.length > 0"> <view v-for="(item, index) in ledgerList" :key="index"> <view class="ledger-item" @click="handleInfo('edit', 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> </view> <!-- <view class="item-tag">--> <!-- <text class="tag-text">{{ item.recorder }}</text>--> <!-- </view>--> </view> <up-divider></up-divider> <view class="item-details"> <view class="detail-row"> <text class="detail-label">客æ·åç§°</text> <text class="detail-value">{{ item.customerName }}</text> </view> <view class="detail-row"> <text class="detail-label">客æ·ååå·</text> <text class="detail-value">{{ item.customerContractNo }}</text> </view> <view class="detail-row"> <text class="detail-label">ä¸å¡å</text> <text class="detail-value">{{ item.salesman }}</text> </view> <view class="detail-row"> <text class="detail-label">项ç®åç§°</text> <text class="detail-value">{{ item.projectName }}</text> </view> <view class="detail-row"> <text class="detail-label">仿¬¾æ¹å¼</text> <text class="detail-value">{{ item.paymentMethod }}</text> </view> <view class="detail-row"> <text class="detail-label">ååéé¢(å )</text> <text class="detail-value highlight">{{ item.contractAmount }}</text> </view> <view class="detail-row"> <text class="detail-label">ç¾è®¢æ¥æ</text> <text class="detail-value">{{ item.executionDate }}</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> <view class="detail-row"> <text class="detail-label">å½å ¥æ¥æ</text> <text class="detail-value">{{ item.entryDate }}</text> </view> </view> </view> </view> </view> </view> <view v-else class="no-data"> <text>ææ éå®å°è´¦æ°æ®</text> </view> <!-- æµ®å¨æä½æé® --> <view class="fab-button" @click="handleInfo('add')"> <up-icon name="plus" size="24" color="#ffffff"></up-icon> </view> </view> <view class="sales-account"> <!-- 使ç¨éç¨é¡µé¢å¤´é¨ç»ä»¶ --> <PageHeader title="éå®å°è´¦" @back="goBack" /> <!-- æç´¢åçéåºå --> <view class="search-section"> <view class="search-bar"> <view class="search-input"> <up-input class="search-text" placeholder="请è¾å ¥éå®ååå·æç´¢" v-model="salesContractNo" @change="getList" clearable /> </view> <view class="filter-button" @click="getList"> <up-icon name="search" size="24" color="#999"></up-icon> </view> </view> </view> <!-- éå®å°è´¦ç叿µ --> <view class="ledger-list" v-if="ledgerList.length > 0"> <view v-for="(item, index) in ledgerList" :key="index"> <view class="ledger-item" @click="handleInfo('edit', 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> </view> <!-- <view class="item-tag">--> <!-- <text class="tag-text">{{ item.recorder }}</text>--> <!-- </view>--> </view> <up-divider></up-divider> <view class="item-details"> <view class="detail-row"> <text class="detail-label">客æ·åç§°</text> <text class="detail-value">{{ item.customerName }}</text> </view> <view class="detail-row"> <text class="detail-label">客æ·ååå·</text> <text class="detail-value">{{ item.customerContractNo }}</text> </view> <view class="detail-row"> <text class="detail-label">ä¸å¡å</text> <text class="detail-value">{{ item.salesman }}</text> </view> <view class="detail-row"> <text class="detail-label">项ç®åç§°</text> <text class="detail-value">{{ item.projectName }}</text> </view> <view class="detail-row"> <text class="detail-label">仿¬¾æ¹å¼</text> <text class="detail-value">{{ item.paymentMethod }}</text> </view> <view class="detail-row"> <text class="detail-label">ååéé¢(å )</text> <text class="detail-value highlight">{{ item.contractAmount }}</text> </view> <view class="detail-row"> <text class="detail-label">ç¾è®¢æ¥æ</text> <text class="detail-value">{{ item.executionDate }}</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> <view class="detail-row"> <text class="detail-label">å½å ¥æ¥æ</text> <text class="detail-value">{{ item.entryDate }}</text> </view> </view> <up-divider></up-divider> <u-button class="detail-button" size="small" type="primary" @click="openOut(item)"> åè´§ç¶æ </u-button> </view> </view> </view> </view> <view v-else class="no-data"> <text>ææ éå®å°è´¦æ°æ®</text> </view> <!-- æµ®å¨æä½æé® --> <view class="fab-button" @click="handleInfo('add')"> <up-icon name="plus" size="24" color="#ffffff"></up-icon> </view> </view> </template> <script setup> import { ref } from 'vue'; import { onShow } from '@dcloudio/uni-app'; import {ledgerListPage} from "@/api/salesManagement/salesLedger"; import useUserStore from "@/store/modules/user"; import PageHeader from "@/components/PageHeader.vue"; const userStore = useUserStore() const showLoadingToast = (message) => { uni.showLoading({ title: message, mask: true }) } const closeToast = () => { uni.hideLoading() } import { ref } from "vue"; import { onShow } from "@dcloudio/uni-app"; import { ledgerListPage } from "@/api/salesManagement/salesLedger"; import useUserStore from "@/store/modules/user"; import PageHeader from "@/components/PageHeader.vue"; const userStore = useUserStore(); const showLoadingToast = message => { uni.showLoading({ title: message, mask: true, }); }; const closeToast = () => { uni.hideLoading(); }; // æç´¢å ³é®è¯ const salesContractNo = ref(''); // æç´¢å ³é®è¯ const salesContractNo = ref(""); // éå®å°è´¦æ°æ® const ledgerList = ref([]); // éå®å°è´¦æ°æ® const ledgerList = ref([]); // è¿åä¸ä¸é¡µ const goBack = () => { uni.navigateBack(); }; // æ¥è¯¢å表 const getList = () => { showLoadingToast('å è½½ä¸...') const page = { current: -1, size: -1 } ledgerListPage({...page, salesContractNo: salesContractNo.value}).then((res) => { console.log('éå®å°è´¦----', res); ledgerList.value = res.records; closeToast() }).catch(() => { closeToast() }); }; // è¿åä¸ä¸é¡µ const goBack = () => { uni.navigateBack(); }; // æ¥è¯¢å表 const getList = () => { showLoadingToast("å è½½ä¸..."); const page = { current: -1, size: -1, }; ledgerListPage({ ...page, salesContractNo: salesContractNo.value }) .then(res => { console.log("éå®å°è´¦----", res); ledgerList.value = res.records; closeToast(); }) .catch(() => { closeToast(); }); }; const openOut = item => { uni.setStorageSync("outData", JSON.stringify(item)); uni.navigateTo({ url: "/pages/sales/salesAccount/out", }); }; // å¤çå°è´¦ä¿¡æ¯æä½ï¼æ¥ç/ç¼è¾/æ°å¢ï¼ const handleInfo = (type, row) => { try { // 设置æä½ç±»å uni.setStorageSync("operationType", type); // å¤çå°è´¦ä¿¡æ¯æä½ï¼æ¥ç/ç¼è¾/æ°å¢ï¼ const handleInfo = (type, row) => { try { // 设置æä½ç±»å uni.setStorageSync('operationType', type); // å¦ææ¯æ¥çæç¼è¾æä½ if (type !== 'add') { // éªè¯è¡æ°æ®æ¯å¦åå¨ if (!row) { uni.showToast({ title: 'æ°æ®ä¸åå¨', icon: 'error' }); return; } // æ£æ¥æéï¼åªæå½å ¥äººæè½ç¼è¾ if (row.entryPerson != userStore.id) { // éå½å ¥äººè·³è½¬å°åªè¯»è¯¦æ é¡µé¢ uni.setStorageSync('editData', JSON.stringify(row)); uni.navigateTo({ url: '/pages/sales/salesAccount/view' }); return; } // å½å ¥äººç¼è¾ï¼å卿°æ®å¹¶è·³è½¬å°ç¼è¾é¡µé¢ uni.setStorageSync('editData', JSON.stringify(row)); uni.navigateTo({ url: '/pages/sales/salesAccount/detail' }); return; } // æ°å¢æä½ï¼ç´æ¥è·³è½¬å°ç¼è¾é¡µé¢ uni.navigateTo({ url: '/pages/sales/salesAccount/detail' }); } catch (error) { console.error('å¤çå°è´¦ä¿¡æ¯æä½å¤±è´¥:', error); uni.showToast({ title: 'æä½å¤±è´¥ï¼è¯·éè¯', icon: 'error' }); } }; // å¦ææ¯æ¥çæç¼è¾æä½ if (type !== "add") { // éªè¯è¡æ°æ®æ¯å¦åå¨ if (!row) { uni.showToast({ title: "æ°æ®ä¸åå¨", icon: "error", }); return; } onShow(() => { // 页颿¾ç¤ºæ¶å·æ°å表 getList(); }); // æ£æ¥æéï¼åªæå½å ¥äººæè½ç¼è¾ if (row.entryPerson != userStore.id) { // éå½å ¥äººè·³è½¬å°åªè¯»è¯¦æ é¡µé¢ uni.setStorageSync("editData", JSON.stringify(row)); uni.navigateTo({ url: "/pages/sales/salesAccount/view", }); return; } // å½å ¥äººç¼è¾ï¼å卿°æ®å¹¶è·³è½¬å°ç¼è¾é¡µé¢ uni.setStorageSync("editData", JSON.stringify(row)); uni.navigateTo({ url: "/pages/sales/salesAccount/detail", }); return; } // æ°å¢æä½ï¼ç´æ¥è·³è½¬å°ç¼è¾é¡µé¢ uni.navigateTo({ url: "/pages/sales/salesAccount/detail", }); } catch (error) { console.error("å¤çå°è´¦ä¿¡æ¯æä½å¤±è´¥:", error); uni.showToast({ title: "æä½å¤±è´¥ï¼è¯·éè¯", icon: "error", }); } }; onShow(() => { // 页颿¾ç¤ºæ¶å·æ°å表 getList(); }); </script> <style scoped lang="scss"> @import '@/styles/sales-common.scss'; @import "@/styles/sales-common.scss"; </style> src/pages/sales/salesAccount/out.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,407 @@ <template> <view class="receipt-payment-detail"> <!-- 使ç¨éç¨é¡µé¢å¤´é¨ç»ä»¶ --> <PageHeader title="åè´§ç¶æ" @back="goBack" /> <!-- ç»è®¡ä¿¡æ¯ --> <view class="summary-info"> <view class="summary-item"> <text class="summary-label">客æ·åç§°</text> <text class="summary-value">{{ outData.customerName }}</text> </view> <view class="summary-item"> <text class="summary-label">ååéé¢</text> <text class="summary-value">{{ outData.contractAmount }}</text> </view> <view class="summary-item"> <text class="summary-label">ç¾è®¢æ¥æ</text> <text class="summary-value">{{ outData.executionDate }}</text> </view> </view> <!-- 忬¾è®°å½æç»å表 --> <view class="detail-list" v-if="tableData.length > 0"> <view v-for="(item, index) in tableData" :key="index" class="detail-item"> <view class="item-header"> <view class="item-left"> <view class="record-icon"> <up-icon name="file-text" size="16" color="#ffffff"></up-icon> </view> <text class="item-index">{{ item.productCategory }}</text> </view> </view> <up-divider></up-divider> <view class="item-details"> <view class="detail-row"> <text class="detail-label">产å大类</text> <text class="detail-value">{{ item.productCategory }}</text> </view> <view class="detail-row"> <text class="detail-label">è§æ ¼åå·</text> <text class="detail-value">{{ item.specificationModel }}</text> </view> <view class="detail-row"> <text class="detail-label">åä½</text> <text class="detail-value">{{ item.unit }}</text> </view> <view class="detail-row"> <text class="detail-label">产åç¶æ</text> <text v-if="item.approveStatus === 1" class="detail-value highlight">å è¶³</text> <text v-else class="detail-value danger">ä¸è¶³</text> </view> <view class="detail-row"> <text class="detail-label">åè´§ç¶æ</text> <u-tag size="mini" :type="getShippingStatusType(item)">{{ getShippingStatusText(item) }}</u-tag> </view> <view class="detail-row"> <text class="detail-label">å¿«éå ¬å¸</text> <text class="detail-value">{{ item.expressCompany }}</text> </view> <view class="detail-row"> <text class="detail-label">å¿«éåå·</text> <text class="detail-value">{{ item.expressNumber }}</text> </view> <view class="detail-row"> <text class="detail-label">å货车ç</text> <u-tag size="mini" v-if="item.shippingCarNumber" type="success">{{ item.shippingCarNumber }}</u-tag> <u-tag v-else size="mini" type="info">-</u-tag> </view> <view class="detail-row"> <text class="detail-label">åè´§æ¥æ</text> <text class="detail-value">{{ item.shippingDate || '-' }}</text> </view> <view class="detail-row"> <text class="detail-label">æ°é</text> <text class="detail-value">{{ item.quantity }}</text> </view> <view class="detail-row"> <text class="detail-label">ç¨çï¼%ï¼</text> <text class="detail-value">{{ item.taxRate }}</text> </view> <view class="detail-row"> <text class="detail-label">å«ç¨åä»·ï¼å ï¼</text> <text class="detail-value">{{ item.taxInclusiveUnitPrice }}</text> </view> <view class="detail-row"> <text class="detail-label">å«ç¨æ»ä»·ï¼å ï¼</text> <text class="detail-value">{{ item.taxInclusiveTotalPrice }}</text> </view> <view class="detail-row"> <text class="detail-label">ä¸å«ç¨æ»ä»·ï¼å ï¼</text> <text class="detail-value">{{ item.taxExclusiveTotalPrice }}</text> </view> <up-divider></up-divider> <u-button class="detail-button" size="small" type="primary" :disabled="!canShip(item)" @click="goout(item)"> åè´§ </u-button> </view> </view> </view> <view v-else class="no-data"> <text>ææ åæ¬¾è®°å½</text> </view> </view> </template> <script setup> import { ref, computed, onMounted } from "vue"; import { productList } from "@/api/salesManagement/salesLedger"; // 客æ·ä¿¡æ¯ const supplierId = ref(""); // è¡¨æ ¼æ°æ® const tableData = ref([]); // è¿åä¸ä¸é¡µ const goBack = () => { uni.removeStorageSync("supplierId"); uni.navigateBack(); }; const getShippingStatusType = row => { // 妿已åè´§ï¼æåè´§æ¥ææè½¦çå·ï¼ï¼æ¾ç¤ºç»¿è² if (row.shippingDate || row.shippingCarNumber) { return "success"; } // è·ååè´§ç¶æåæ®µ const status = row.shippingStatus; // å¦æç¶æä¸ºç©ºææªå®ä¹ï¼é»è®¤ä¸ºç°è²ï¼å¾ åè´§ï¼ if (status === null || status === undefined || status === "") { return "info"; } // ç¶ææ¯å符串 const statusStr = String(status).trim(); const typeTextMap = { å¾ åè´§: "info", å¾ å®¡æ ¸: "info", å®¡æ ¸ä¸: "warning", å®¡æ ¸æç»: "danger", å®¡æ ¸éè¿: "success", å·²åè´§: "success", }; return typeTextMap[statusStr] || "info"; }; const getShippingStatusText = row => { // 妿已åè´§ï¼æåè´§æ¥ææè½¦çå·ï¼ï¼æ¾ç¤º"å·²åè´§" if (row.shippingDate || row.shippingCarNumber) { return "å·²åè´§"; } // è·ååè´§ç¶æåæ®µ const status = row.shippingStatus; // å¦æç¶æä¸ºç©ºææªå®ä¹ï¼é»è®¤ä¸º"å¾ åè´§" if (status === null || status === undefined || status === "") { return "å¾ åè´§"; } // ç¶ææ¯å符串 const statusStr = String(status).trim(); const statusTextMap = { å¾ åè´§: "å¾ åè´§", å¾ å®¡æ ¸: "å¾ å®¡æ ¸", å®¡æ ¸ä¸: "å®¡æ ¸ä¸", å®¡æ ¸æç»: "å®¡æ ¸æç»", å®¡æ ¸éè¿: "å®¡æ ¸éè¿", å·²åè´§: "å·²åè´§", }; return statusTextMap[statusStr] || "å¾ åè´§"; }; // è·å页é¢åæ° const getPageParams = () => { // 仿¬å°åå¨è·åä¾åºåID const storedSupplierId = uni.getStorageSync("supplierId"); if (storedSupplierId) { supplierId.value = storedSupplierId; } }; const goout = item => { uni.setStorageSync("goOutData", JSON.stringify(item)); uni.navigateTo({ url: "/pages/sales/salesAccount/goOut", }); }; // æ¥è¯¢å表 const getList = () => { showLoadingToast("å è½½ä¸..."); productList({ salesLedgerId: outData.value.id, type: 1, }) .then(res => { tableData.value = res.data; closeToast(); }) .catch(() => { closeToast(); uni.showToast({ title: "æ¥è¯¢å¤±è´¥", icon: "error", }); }); }; const canShip = row => { // 产åç¶æå¿ é¡»æ¯å è¶³ï¼approveStatus === 1ï¼ if (row.approveStatus !== 1) { return false; } // è·ååè´§ç¶æ const shippingStatus = row.shippingStatus; // 妿已åè´§ï¼æåè´§æ¥ææè½¦çå·ï¼ï¼ä¸è½å次åè´§ if (row.shippingDate || row.shippingCarNumber) { return false; } // åè´§ç¶æå¿ é¡»æ¯"å¾ åè´§"æ"å®¡æ ¸æç»" const statusStr = shippingStatus ? String(shippingStatus).trim() : ""; return statusStr === "å¾ åè´§" || statusStr === "å®¡æ ¸æç»"; }; // æ¾ç¤ºå è½½æç¤º const showLoadingToast = message => { uni.showLoading({ title: message, mask: true, }); }; // å ³éæç¤º const closeToast = () => { uni.hideLoading(); }; const outData = ref({}); onMounted(() => { // 页é¢å è½½æ¶è·ååæ°å¹¶å·æ°å表 getPageParams(); // 仿¬å°åå¨è·ååè´§ç¶ææ°æ® outData.value = JSON.parse(uni.getStorageSync("outData")); getList(); }); </script> <style scoped lang="scss"> .receipt-payment-detail { min-height: 100vh; background: #f8f9fa; position: relative; } .u-divider { margin: 0 !important; } .summary-info { background: #ffffff; margin: 20px 20px 0 20px; border-radius: 12px; padding: 16px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); } .summary-item { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; &:last-child { margin-bottom: 0; } } .summary-label { font-size: 14px; color: #666; } .summary-value { font-size: 14px; color: #333; font-weight: 500; } .summary-value.highlight { color: #2979ff; font-weight: 600; } .summary-value.danger { color: #ff4757; font-weight: 600; } .detail-list { padding: 20px; } .detail-item { background: #ffffff; border-radius: 12px; margin-bottom: 16px; overflow: hidden; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); padding: 0 16px; } .item-header { padding: 10px 0; display: flex; align-items: center; justify-content: space-between; } .item-left { display: flex; align-items: center; gap: 8px; } .record-icon { width: 24px; height: 24px; background: #2979ff; border-radius: 4px; display: flex; align-items: center; justify-content: center; } .item-index { font-size: 14px; color: #333; font-weight: 500; } .item-date { font-size: 12px; color: #666; } .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-label { font-size: 12px; color: #777777; min-width: 60px; } .detail-value { font-size: 12px; color: #000000; text-align: right; flex: 1; margin-left: 16px; } .detail-value.highlight { color: #2979ff; font-weight: 500; } .detail-value.danger { color: #ff4757; font-weight: 500; } .no-data { padding: 40px 0; text-align: center; color: #999; } </style>