From dd630fede0cc46500fe898c75464e3e04ce82b0f Mon Sep 17 00:00:00 2001
From: ZN <zhang_12370@163.com>
Date: 星期三, 25 三月 2026 18:01:03 +0800
Subject: [PATCH] feat(财务与售后): 新增财务管理与售后管理模块
---
src/pages/productionManagement/productionDispatching/components/formDia.vue | 2
src/pages.json | 77 +
src/pages/financialManagement/revenueManagement/index.vue | 149 +++
src/pages/customerService/feedbackRegistration/index.vue | 323 +++++++
src/pages/works.vue | 61 +
src/pages/financialManagement/expenseManagement/index.vue | 149 +++
src/pages/customerService/feedbackRegistration/edit.vue | 472 ++++++++++
src/pages/financialManagement/loanManagement/index.vue | 196 ++++
src/pages/financialManagement/revenueManagement/edit.vue | 120 ++
src/api/customerService/index.js | 93 ++
src/api/financialManagement/expenseManagement.js | 40
src/pages/financialManagement/loanManagement/edit.vue | 87 ++
src/api/financialManagement/revenueManagement.js | 40
src/pages/customerService/afterSalesHandling/fileList.vue | 300 ++++++
src/pages/customerService/afterSalesHandling/handle.vue | 121 ++
src/pages/financialManagement/expenseManagement/edit.vue | 120 ++
src/api/financialManagement/loanManagement.js | 33
src/pages/customerService/afterSalesHandling/index.vue | 181 ++++
18 files changed, 2,555 insertions(+), 9 deletions(-)
diff --git a/src/api/customerService/index.js b/src/api/customerService/index.js
new file mode 100644
index 0000000..b5d9cea
--- /dev/null
+++ b/src/api/customerService/index.js
@@ -0,0 +1,93 @@
+import request from "@/utils/request";
+
+// 鍙嶉鐧昏-鍒嗛〉鏌ヨ
+export function afterSalesServiceListPage(query) {
+ return request({
+ url: '/afterSalesService/listPage',
+ method: 'get',
+ params: query,
+ })
+}
+// 鍙嶉鐧昏-鍒犻櫎
+export function afterSalesServiceDelete(query) {
+ return request({
+ url: '/afterSalesService/delete',
+ method: 'delete',
+ data: query,
+ })
+}
+// 鍙嶉鐧昏-鏂板
+export function afterSalesServiceAdd(query) {
+ return request({
+ url: '/afterSalesService/add',
+ method: 'post',
+ data: query,
+ })
+}
+// 鍙嶉鐧昏-鏇存柊
+export function afterSalesServiceUpdate(query) {
+ return request({
+ url: '/afterSalesService/update',
+ method: 'post',
+ data: query,
+ })
+}
+// 鍞悗澶勭悊-鎻愪氦澶勭悊
+export function afterSalesServiceDispose(query) {
+ return request({
+ url: '/afterSalesService/dispose',
+ method: 'post',
+ data: query,
+ })
+}
+
+// 鍞悗澶勭悊-闄勪欢鍒楄〃
+export function afterSalesServiceFileListPage(query) {
+ return request({
+ url: '/afterSalesService/file/listPage',
+ method: 'get',
+ params: query,
+ })
+}
+// 鍞悗澶勭悊-闄勪欢鏂板
+export function afterSalesServiceFileAdd(data) {
+ return request({
+ url: '/afterSalesService/file/add',
+ method: 'post',
+ data,
+ })
+}
+// 鍞悗澶勭悊-闄勪欢鍒犻櫎
+export function afterSalesServiceFileDel(id) {
+ return request({
+ url: `/afterSalesService/file/del/${id}`,
+ method: 'delete',
+ })
+}
+
+// 鏌ヨ鎵�鏈夊鎴蜂俊鎭�
+export function getAllCustomerList(query) {
+ return request({
+ url: '/basic/customer/list',
+ method: 'get',
+ params: query,
+ })
+}
+
+// 鏍规嵁瀹㈡埛鏌ヨ閿�鍞鍗曞彿
+export function getSalesLedger(query) {
+ return request({
+ url: '/afterSalesService/listSalesLedger',
+ method: 'get',
+ params: query,
+ })
+}
+
+// 鑾峰彇缁熻鏁版嵁
+export function getSalesLedgerDetail(query) {
+ return request({
+ url: '/afterSalesService/count',
+ method: 'get',
+ params: query,
+ })
+}
diff --git a/src/api/financialManagement/expenseManagement.js b/src/api/financialManagement/expenseManagement.js
new file mode 100644
index 0000000..49e63ac
--- /dev/null
+++ b/src/api/financialManagement/expenseManagement.js
@@ -0,0 +1,40 @@
+import request from "@/utils/request";
+
+export const listPage = (params) => {
+ return request({
+ url: "/account/accountExpense/listPage",
+ method: "get",
+ params,
+ });
+};
+
+export function add(data) {
+ return request({
+ url: "/account/accountExpense/add",
+ method: "post",
+ data,
+ });
+}
+
+export function update(data) {
+ return request({
+ url: "/account/accountExpense/update",
+ method: "post",
+ data,
+ });
+}
+
+export const delAccountExpense = (data) => {
+ return request({
+ url: "account/accountExpense/del",
+ method: "delete",
+ data,
+ });
+};
+
+export const getAccountExpense = (id) => {
+ return request({
+ url: `/account/accountExpense/${id}`,
+ method: "get",
+ });
+};
diff --git a/src/api/financialManagement/loanManagement.js b/src/api/financialManagement/loanManagement.js
new file mode 100644
index 0000000..251da1c
--- /dev/null
+++ b/src/api/financialManagement/loanManagement.js
@@ -0,0 +1,33 @@
+import request from "@/utils/request";
+
+export const listPage = (params) => {
+ return request({
+ url: "/borrowInfo/listPage",
+ method: "get",
+ params,
+ });
+};
+
+export function add(data) {
+ return request({
+ url: "/borrowInfo/add",
+ method: "post",
+ data,
+ });
+}
+
+export function update(data) {
+ return request({
+ url: "/borrowInfo/update",
+ method: "post",
+ data,
+ });
+}
+
+export const delAccountLoan = (data) => {
+ return request({
+ url: "/borrowInfo/delete",
+ method: "delete",
+ data,
+ });
+};
diff --git a/src/api/financialManagement/revenueManagement.js b/src/api/financialManagement/revenueManagement.js
new file mode 100644
index 0000000..3a270fd
--- /dev/null
+++ b/src/api/financialManagement/revenueManagement.js
@@ -0,0 +1,40 @@
+import request from "@/utils/request";
+
+export const listPage = (params) => {
+ return request({
+ url: "/account/accountIncome/listPage",
+ method: "get",
+ params,
+ });
+};
+
+export function add(data) {
+ return request({
+ url: "/account/accountIncome/add",
+ method: "post",
+ data,
+ });
+}
+
+export function update(data) {
+ return request({
+ url: "/account/accountIncome/update",
+ method: "post",
+ data,
+ });
+}
+
+export const delAccountIncome = (data) => {
+ return request({
+ url: "account/accountIncome/del",
+ method: "delete",
+ data,
+ });
+};
+
+export const getAccountIncome = (id) => {
+ return request({
+ url: `/account/accountIncome/${id}`,
+ method: "get",
+ });
+};
diff --git a/src/pages.json b/src/pages.json
index 5d7da6d..5c4283d 100644
--- a/src/pages.json
+++ b/src/pages.json
@@ -19,6 +19,48 @@
}
},
{
+ "path": "pages/financialManagement/revenueManagement/index",
+ "style": {
+ "navigationBarTitleText": "鏀跺叆绠$悊",
+ "navigationStyle": "custom"
+ }
+ },
+ {
+ "path": "pages/financialManagement/revenueManagement/edit",
+ "style": {
+ "navigationBarTitleText": "鏀跺叆缂栬緫",
+ "navigationStyle": "custom"
+ }
+ },
+ {
+ "path": "pages/financialManagement/expenseManagement/index",
+ "style": {
+ "navigationBarTitleText": "鏀嚭绠$悊",
+ "navigationStyle": "custom"
+ }
+ },
+ {
+ "path": "pages/financialManagement/expenseManagement/edit",
+ "style": {
+ "navigationBarTitleText": "鏀嚭缂栬緫",
+ "navigationStyle": "custom"
+ }
+ },
+ {
+ "path": "pages/financialManagement/loanManagement/index",
+ "style": {
+ "navigationBarTitleText": "鍊熸绠$悊",
+ "navigationStyle": "custom"
+ }
+ },
+ {
+ "path": "pages/financialManagement/loanManagement/edit",
+ "style": {
+ "navigationBarTitleText": "鍊熸缂栬緫",
+ "navigationStyle": "custom"
+ }
+ },
+ {
"path": "pages/index",
"style": {
"navigationBarTitleText": "棣栭〉",
@@ -999,6 +1041,41 @@
}
},
{
+ "path": "pages/customerService/feedbackRegistration/index",
+ "style": {
+ "navigationBarTitleText": "鍞悗鐧昏",
+ "navigationStyle": "custom"
+ }
+ },
+ {
+ "path": "pages/customerService/feedbackRegistration/edit",
+ "style": {
+ "navigationBarTitleText": "鍞悗鍗曡鎯�",
+ "navigationStyle": "custom"
+ }
+ },
+ {
+ "path": "pages/customerService/afterSalesHandling/index",
+ "style": {
+ "navigationBarTitleText": "鍞悗澶勭悊",
+ "navigationStyle": "custom"
+ }
+ },
+ {
+ "path": "pages/customerService/afterSalesHandling/handle",
+ "style": {
+ "navigationBarTitleText": "鍞悗澶勭悊璇︽儏",
+ "navigationStyle": "custom"
+ }
+ },
+ {
+ "path": "pages/customerService/afterSalesHandling/fileList",
+ "style": {
+ "navigationBarTitleText": "鍞悗闄勪欢",
+ "navigationStyle": "custom"
+ }
+ },
+ {
"path": "pages/qualityManagement/finalInspection/add",
"style": {
"navigationBarTitleText": "鍑哄巶妫�楠屾坊鍔�",
diff --git a/src/pages/customerService/afterSalesHandling/fileList.vue b/src/pages/customerService/afterSalesHandling/fileList.vue
new file mode 100644
index 0000000..7e0194b
--- /dev/null
+++ b/src/pages/customerService/afterSalesHandling/fileList.vue
@@ -0,0 +1,300 @@
+<template>
+ <view class="file-list-page">
+ <PageHeader title="鍞悗闄勪欢" @back="goBack" />
+
+ <view class="file-list-container">
+ <view v-if="fileList.length > 0" class="file-list">
+ <view v-for="(file, index) in fileList" :key="file.id || index" class="file-item">
+ <view class="file-info">
+ <text class="file-name">{{ file.name }}</text>
+ </view>
+ <view class="file-actions">
+ <u-button size="small" type="info" plain @click="downloadFile(file)">涓嬭浇骞堕瑙�</u-button>
+ <u-button size="small" type="error" plain @click="confirmDelete(file)">鍒犻櫎</u-button>
+ </view>
+ </view>
+ </view>
+
+ <view v-else class="empty-state">
+ <up-icon name="document" size="64" color="#c0c4cc" />
+ <text class="empty-text">鏆傛棤闄勪欢</text>
+ </view>
+ </view>
+
+ <view class="upload-button" @click="chooseFile">
+ <up-icon name="plus" size="24" color="#ffffff" />
+ <text class="upload-text">涓婁紶闄勪欢</text>
+ </view>
+ </view>
+</template>
+
+<script setup>
+import { ref, onMounted } from "vue";
+import PageHeader from "@/components/PageHeader.vue";
+import config from "@/config";
+import { getToken } from "@/utils/auth";
+import { afterSalesServiceFileListPage, afterSalesServiceFileDel } from "@/api/customerService/index";
+
+const fileList = ref([]);
+const afterSalesServiceId = ref("");
+
+const goBack = () => {
+ uni.navigateBack();
+};
+
+const showToast = (message) => {
+ uni.showToast({
+ title: message,
+ icon: "none",
+ });
+};
+
+const normalizeFileRow = (row) => {
+ return {
+ id: row?.id,
+ name: row?.name || row?.fileName || "-",
+ url: row?.url || row?.fileUrl || "",
+ };
+};
+
+const getFileList = () => {
+ if (!afterSalesServiceId.value) {
+ fileList.value = [];
+ return;
+ }
+ uni.showLoading({ title: "鍔犺浇涓�...", mask: true });
+ afterSalesServiceFileListPage({
+ afterSalesServiceId: afterSalesServiceId.value,
+ current: 1,
+ size: 100,
+ })
+ .then((res) => {
+ const records = res?.data?.records ?? res?.records ?? [];
+ const list = Array.isArray(records) ? records : [];
+ fileList.value = list.map(normalizeFileRow);
+ })
+ .catch(() => {
+ showToast("鑾峰彇闄勪欢鍒楄〃澶辫触");
+ fileList.value = [];
+ })
+ .finally(() => {
+ uni.hideLoading();
+ });
+};
+
+const chooseFile = () => {
+ if (!afterSalesServiceId.value) {
+ showToast("缂哄皯鍞悗鍗旾D");
+ return;
+ }
+ uni.chooseImage({
+ count: 9,
+ sizeType: ["original", "compressed"],
+ sourceType: ["album", "camera"],
+ success: (res) => {
+ uploadFiles(res.tempFiles || []);
+ },
+ fail: () => {
+ showToast("閫夋嫨鏂囦欢澶辫触");
+ },
+ });
+};
+
+const uploadFiles = (tempFiles) => {
+ if (!Array.isArray(tempFiles) || tempFiles.length === 0) return;
+ tempFiles.forEach((tempFile) => {
+ uni.showLoading({ title: "涓婁紶涓�...", mask: true });
+ uni.uploadFile({
+ url: config.baseUrl + "/afterSalesService/file/upload",
+ filePath: tempFile.path,
+ name: "file",
+ formData: {
+ id: String(afterSalesServiceId.value),
+ },
+ header: {
+ Authorization: "Bearer " + getToken(),
+ },
+ success: (uploadRes) => {
+ uni.hideLoading();
+ try {
+ const data = JSON.parse(uploadRes.data || "{}");
+ if (data.code === 200 || data.code === undefined) {
+ showToast("涓婁紶鎴愬姛");
+ getFileList();
+ return;
+ }
+ showToast(data.msg || "涓婁紶澶辫触");
+ } catch (e) {
+ showToast("涓婁紶澶辫触");
+ }
+ },
+ fail: () => {
+ uni.hideLoading();
+ showToast("涓婁紶澶辫触");
+ },
+ });
+ });
+};
+
+const downloadFile = (file) => {
+ if (!file?.url) {
+ showToast("鏂囦欢鍦板潃涓虹┖");
+ return;
+ }
+ const url =
+ config.baseUrl +
+ "/common/download?fileName=" +
+ encodeURIComponent(file.url) +
+ "&delete=true";
+
+ uni
+ .downloadFile({
+ url,
+ responseType: "blob",
+ header: { Authorization: "Bearer " + getToken() },
+ })
+ .then((res) => {
+ const osType = uni.getStorageSync("deviceInfo")?.osName;
+ const filePath = res.tempFilePath;
+ if (osType === "ios") {
+ uni.openDocument({
+ filePath,
+ showMenu: true,
+ });
+ } else {
+ uni.saveFile({
+ tempFilePath: filePath,
+ success: (fileRes) => {
+ setTimeout(() => {
+ uni.openDocument({
+ filePath: fileRes.savedFilePath,
+ });
+ }, 300);
+ },
+ fail: () => {
+ showToast("淇濆瓨澶辫触");
+ },
+ });
+ }
+ })
+ .catch(() => {
+ showToast("涓嬭浇澶辫触");
+ });
+};
+
+const confirmDelete = (file) => {
+ uni.showModal({
+ title: "鍒犻櫎纭",
+ content: `纭畾瑕佸垹闄ら檮浠� \"${file.name}\" 鍚楋紵`,
+ success: (res) => {
+ if (res.confirm) {
+ deleteFile(file);
+ }
+ },
+ });
+};
+
+const deleteFile = (file) => {
+ if (!file?.id) return;
+ uni.showLoading({ title: "鍒犻櫎涓�...", mask: true });
+ afterSalesServiceFileDel(file.id)
+ .then((res) => {
+ if (res?.code === 200 || res?.code === undefined) {
+ showToast("鍒犻櫎鎴愬姛");
+ getFileList();
+ return;
+ }
+ showToast(res?.msg || "鍒犻櫎澶辫触");
+ })
+ .catch(() => {
+ showToast("鍒犻櫎澶辫触");
+ })
+ .finally(() => {
+ uni.hideLoading();
+ });
+};
+
+onMounted(() => {
+ afterSalesServiceId.value = String(uni.getStorageSync("afterSalesServiceFileId") || "");
+ getFileList();
+});
+</script>
+
+<style scoped lang="scss">
+@import "@/styles/sales-common.scss";
+
+.file-list-page {
+ min-height: 100vh;
+ background: #f8f9fa;
+ padding-bottom: 100rpx;
+}
+
+.file-list-container {
+ padding: 20rpx;
+}
+
+.file-list {
+ background: #ffffff;
+ border-radius: 8rpx;
+ overflow: hidden;
+ box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
+}
+
+.file-item {
+ display: flex;
+ align-items: center;
+ padding: 20rpx;
+ border-bottom: 1rpx solid #f0f0f0;
+}
+
+.file-info {
+ flex: 1;
+ margin-right: 20rpx;
+}
+
+.file-name {
+ font-size: 28rpx;
+ color: #333;
+ font-weight: 500;
+ display: block;
+}
+
+.file-actions {
+ display: flex;
+ gap: 16rpx;
+}
+
+.empty-state {
+ padding: 80rpx 0;
+ text-align: center;
+}
+
+.empty-text {
+ display: block;
+ margin-top: 20rpx;
+ color: #999;
+ font-size: 28rpx;
+}
+
+.upload-button {
+ position: fixed;
+ bottom: calc(30rpx + env(safe-area-inset-bottom));
+ right: 30rpx;
+ height: 88rpx;
+ padding: 0 28rpx;
+ background: #2979ff;
+ border-radius: 44rpx;
+ display: flex;
+ align-items: center;
+ gap: 14rpx;
+ box-shadow: 0 4rpx 16rpx rgba(41, 121, 255, 0.3);
+ z-index: 1000;
+}
+
+.upload-text {
+ color: #ffffff;
+ font-size: 28rpx;
+ font-weight: 500;
+}
+</style>
+
diff --git a/src/pages/customerService/afterSalesHandling/handle.vue b/src/pages/customerService/afterSalesHandling/handle.vue
new file mode 100644
index 0000000..57f6054
--- /dev/null
+++ b/src/pages/customerService/afterSalesHandling/handle.vue
@@ -0,0 +1,121 @@
+<template>
+ <view class="after-sales-handle">
+ <PageHeader :title="operationType === 'approve' ? '鍞悗澶勭悊' : '鍞悗璇︽儏'" @back="goBack" />
+
+ <view class="form-container">
+ <up-form ref="formRef" :model="form" :rules="rules" label-width="100">
+ <up-form-item label="鍙嶉鏃ユ湡" prop="feedbackDate">
+ <up-input v-model="form.feedbackDate" disabled />
+ </up-form-item>
+
+ <up-form-item label="鐧昏浜�" prop="checkNickName">
+ <up-input v-model="form.checkNickName" disabled />
+ </up-form-item>
+
+ <up-form-item label="瀹㈡埛鍚嶇О" prop="customerName">
+ <up-input v-model="form.customerName" disabled />
+ </up-form-item>
+
+ <up-form-item label="闂鎻忚堪" prop="proDesc" required>
+ <up-textarea v-model="form.proDesc" :disabled="operationType === 'view'" placeholder="璇疯緭鍏ラ棶棰樻弿杩�" count autoHeight></up-textarea>
+ </up-form-item>
+
+ <up-form-item label="澶勭悊缁撴灉" prop="disRes" required>
+ <up-textarea v-model="form.disRes" :disabled="operationType === 'view'" placeholder="璇疯緭鍏ュ鐞嗙粨鏋�" count autoHeight></up-textarea>
+ </up-form-item>
+
+ <up-form-item label="澶勭悊浜�" prop="disposeNickName" v-if="form.disposeNickName || operationType === 'approve'">
+ <up-input v-model="form.disposeNickName" disabled />
+ </up-form-item>
+
+ <up-form-item label="澶勭悊鏃ユ湡" prop="disDate" v-if="form.disDate || operationType === 'approve'">
+ <up-input v-model="form.disDate" disabled />
+ </up-form-item>
+ </up-form>
+ </view>
+
+ <FooterButtons :show="operationType === 'approve'" cancelText="鍙栨秷" confirmText="鎻愪氦" @cancel="goBack" @confirm="submitForm" />
+ </view>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from 'vue';
+import { afterSalesServiceDispose } from '@/api/customerService/index';
+import PageHeader from '@/components/PageHeader.vue';
+import FooterButtons from '@/components/FooterButtons.vue';
+import useUserStore from '@/store/modules/user';
+
+const userStore = useUserStore();
+const operationType = ref('view');
+const formRef = ref(null);
+const form = reactive({
+ id: null,
+ feedbackDate: '',
+ checkUserId: null,
+ checkNickName: '',
+ customerName: '',
+ proDesc: '',
+ disRes: '',
+ disposeUserId: null,
+ disposeNickName: '',
+ disDate: '',
+});
+
+const rules = {
+ proDesc: [{ required: true, message: '璇疯緭鍏ラ棶棰樻弿杩�', trigger: 'blur' }],
+ disRes: [{ required: true, message: '璇疯緭鍏ュ鐞嗙粨鏋�', trigger: 'blur' }],
+};
+
+const goBack = () => {
+ uni.navigateBack();
+};
+
+const submitForm = () => {
+ formRef.value.validate().then(valid => {
+ if (valid) {
+ afterSalesServiceDispose(form).then(() => {
+ uni.showToast({ title: '澶勭悊鎴愬姛', icon: 'success' });
+ setTimeout(() => goBack(), 1500);
+ });
+ }
+ });
+};
+
+onMounted(() => {
+ operationType.value = uni.getStorageSync('afterSalesHandleType') || 'view';
+ const dataStr = uni.getStorageSync('afterSalesHandleData');
+
+ if (dataStr) {
+ const data = JSON.parse(dataStr);
+ Object.assign(form, data);
+
+ // Normalize field names if they differ between API and web view
+ if (!form.proDesc) form.proDesc = data.disRes; // Fallback to disRes if proDesc is empty
+
+ if (operationType.value === 'approve') {
+ if (!form.disposeUserId) {
+ form.disposeUserId = userStore.id;
+ form.disposeNickName = userStore.nickName;
+ }
+ if (!form.disDate) {
+ const now = new Date();
+ form.disDate = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
+ }
+ }
+ }
+});
+</script>
+
+<style scoped lang="scss">
+.after-sales-handle {
+ min-height: 100vh;
+ background: #f8f9fa;
+ padding-bottom: 100px;
+}
+
+.form-container {
+ background: #ffffff;
+ padding: 10px 20px;
+ margin-top: 10px;
+}
+</style>
diff --git a/src/pages/customerService/afterSalesHandling/index.vue b/src/pages/customerService/afterSalesHandling/index.vue
new file mode 100644
index 0000000..1fa568a
--- /dev/null
+++ b/src/pages/customerService/afterSalesHandling/index.vue
@@ -0,0 +1,181 @@
+<template>
+ <view class="after-sales-handling">
+ <PageHeader title="鍞悗澶勭悊" @back="goBack" />
+
+ <!-- 鎼滅储鍜岀瓫閫夊尯鍩� -->
+ <view class="search-section">
+ <view class="search-bar">
+ <view class="search-input">
+ <up-input class="search-text" placeholder="璇疯緭鍏ュ伐鍗曠紪鍙锋悳绱�" v-model="searchForm.afterSalesServiceNo" @change="handleQuery" clearable />
+ </view>
+ <view class="filter-button" @click="handleQuery">
+ <up-icon name="search" size="24" color="#999"></up-icon>
+ </view>
+ </view>
+ </view>
+
+ <!-- 鍞悗澶勭悊鍒楄〃 -->
+ <view class="ledger-list" v-if="tableData.length > 0">
+ <view v-for="(item, index) in tableData" :key="index" class="ledger-item" @click="openHandleForm('view', 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.afterSalesServiceNo }}</text>
+ </view>
+ <view class="item-tag">
+ <up-tag :text="getStatusLabel(item.status)" :type="getStatusType(item.status)" size="mini"></up-tag>
+ </view>
+ </view>
+ <up-divider></up-divider>
+ <view class="item-details">
+ <view class="detail-row">
+ <text class="detail-label">閿�鍞崟鍙�</text>
+ <text class="detail-value">{{ item.salesContractNo }}</text>
+ </view>
+ <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.feedbackDate }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">鍞悗绫诲瀷</text>
+ <text class="detail-value">{{ getDictLabel(post_sale_waiting_list, item.serviceType) }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">绱ф�ョ▼搴�</text>
+ <text class="detail-value">{{ getDictLabel(degree_of_urgency, item.urgency) }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">闂鎻忚堪</text>
+ <text class="detail-value">{{ item.proDesc || item.disRes }}</text>
+ </view>
+ <view class="detail-row" v-if="item.status === 2">
+ <text class="detail-label">澶勭悊缁撴灉</text>
+ <text class="detail-value highlight">{{ item.disRes }}</text>
+ </view>
+ </view>
+ <up-divider></up-divider>
+ <view class="detail-buttons">
+ <up-button size="small" type="primary" v-if="item.status === 1" @click.stop="openHandleForm('approve', item)">澶勭悊</up-button>
+ <up-button size="small" type="primary" plain @click.stop="openHandleForm('view', item)">鏌ョ湅</up-button>
+ <up-button size="small" type="info" plain @click.stop="openFiles(item)">闄勪欢</up-button>
+ </view>
+ </view>
+ </view>
+ <view v-else class="no-data">
+ <text>鏆傛棤鍞悗澶勭悊鏁版嵁</text>
+ </view>
+ </view>
+</template>
+
+<script setup>
+import { ref, reactive, computed } from 'vue';
+import { onShow } from '@dcloudio/uni-app';
+import { afterSalesServiceListPage } from '@/api/customerService/index';
+import PageHeader from '@/components/PageHeader.vue';
+import { useDict } from '@/utils/dict';
+
+const { post_sale_waiting_list, degree_of_urgency } = useDict('post_sale_waiting_list', 'degree_of_urgency');
+
+const searchForm = reactive({
+ afterSalesServiceNo: '',
+ status: '',
+});
+
+const tableData = ref([]);
+const loading = ref(false);
+const page = reactive({
+ current: 1,
+ size: 20,
+ total: 0,
+});
+
+const goBack = () => {
+ uni.navigateBack();
+};
+
+const handleQuery = () => {
+ page.current = 1;
+ getList();
+};
+
+const getList = () => {
+ loading.value = true;
+ uni.showLoading({ title: '鍔犺浇涓�...' });
+
+ afterSalesServiceListPage({ ...searchForm, ...page })
+ .then((res) => {
+ const records = res?.data?.records ?? res?.records ?? [];
+ const total = res?.data?.total ?? res?.total ?? 0;
+ tableData.value = Array.isArray(records) ? records : [];
+ page.total = Number.isFinite(Number(total)) ? Number(total) : 0;
+ })
+ .finally(() => {
+ loading.value = false;
+ uni.hideLoading();
+ });
+};
+
+const getStatusLabel = (status) => {
+ if (status === 1) return '寰呭鐞�';
+ if (status === 2) return '宸插鐞�';
+ return '鏈煡';
+};
+
+const getStatusType = (status) => {
+ if (status === 1) return 'error';
+ if (status === 2) return 'success';
+ return 'info';
+};
+
+const getDictLabel = (dict, value) => {
+ if (!dict || !dict.value) return value;
+ const item = dict.value.find(i => i.value == value);
+ return item ? item.label : value;
+};
+
+const openHandleForm = (type, row) => {
+ uni.setStorageSync('afterSalesHandleType', type);
+ uni.setStorageSync('afterSalesHandleData', JSON.stringify(row));
+ uni.navigateTo({
+ url: '/pages/customerService/afterSalesHandling/handle'
+ });
+};
+
+const openFiles = (row) => {
+ uni.setStorageSync('afterSalesFileData', JSON.stringify(row));
+ uni.setStorageSync('afterSalesServiceFileId', row?.id);
+ uni.navigateTo({
+ url: '/pages/customerService/afterSalesHandling/fileList'
+ });
+};
+
+onShow(() => {
+ getList();
+});
+</script>
+
+<style scoped lang="scss">
+@import "@/styles/sales-common.scss";
+
+.after-sales-handling {
+ min-height: 100vh;
+ background: #f8f9fa;
+}
+
+.detail-buttons {
+ display: flex;
+ gap: 10px;
+ justify-content: flex-end;
+ padding: 12px 0;
+}
+
+.ledger-item {
+ padding: 0 16px;
+}
+</style>
diff --git a/src/pages/customerService/feedbackRegistration/edit.vue b/src/pages/customerService/feedbackRegistration/edit.vue
new file mode 100644
index 0000000..93db74f
--- /dev/null
+++ b/src/pages/customerService/feedbackRegistration/edit.vue
@@ -0,0 +1,472 @@
+<template>
+ <view class="after-sales-edit">
+ <PageHeader :title="pageTitle" @back="goBack" />
+
+ <view class="form-container">
+ <up-form ref="formRef" :model="form" :rules="isReadonly ? {} : rules" label-width="100">
+ <up-form-item label="瀹㈡埛鍚嶇О" prop="customerName" required @click="openCustomerPicker">
+ <up-input v-model="form.customerName" readonly placeholder="璇烽�夋嫨瀹㈡埛" />
+ <template #right>
+ <up-icon v-if="!isReadonly" name="arrow-right" @click="openCustomerPicker"></up-icon>
+ </template>
+ </up-form-item>
+
+ <up-form-item label="鍞悗绫诲瀷" prop="serviceType" required @click="openServiceTypePicker">
+ <up-input v-model="serviceTypeLabel" readonly placeholder="璇烽�夋嫨鍞悗绫诲瀷" />
+ <template #right>
+ <up-icon v-if="!isReadonly" name="arrow-right" @click="openServiceTypePicker"></up-icon>
+ </template>
+ </up-form-item>
+
+ <up-form-item label="鍏宠仈閿�鍞崟" prop="salesContractNo" required @click="openSalesOrderPicker">
+ <up-input v-model="form.salesContractNo" readonly placeholder="璇烽�夋嫨閿�鍞崟鍙�" />
+ <template #right>
+ <up-icon v-if="!isReadonly" name="arrow-right" @click="openSalesOrderPicker"></up-icon>
+ </template>
+ </up-form-item>
+
+ <up-form-item label="绱ф�ョ▼搴�" prop="urgency" required @click="openUrgencyPicker">
+ <up-input v-model="urgencyLabel" readonly placeholder="璇烽�夋嫨绱ф�ョ▼搴�" />
+ <template #right>
+ <up-icon v-if="!isReadonly" name="arrow-right" @click="openUrgencyPicker"></up-icon>
+ </template>
+ </up-form-item>
+
+ <up-form-item label="闂鎻忚堪" prop="disRes">
+ <up-textarea v-model="form.disRes" :disabled="isReadonly" placeholder="璇疯緭鍏ラ棶棰樻弿杩�" count autoHeight></up-textarea>
+ </up-form-item>
+ </up-form>
+
+ <!-- 鍏宠仈浜у搧鍖哄煙 -->
+ <view class="product-section">
+ <view class="section-header">
+ <up-button type="primary" size="small" @click="showProductSelect = true" v-if="!isReadonly && form.salesContractNo">閫夋嫨浜у搧</up-button>
+ </view>
+
+ <view class="product-list" v-if="tableData.length > 0">
+ <view v-for="(item, index) in tableData" :key="index" class="product-item">
+ <view class="product-info">
+ <view class="info-row">
+ <text class="info-label">浜у搧鍒嗙被锛�</text>
+ <text class="info-value">{{ item.productCategory }}</text>
+ </view>
+ <view class="info-row">
+ <text class="info-label">瑙勬牸鍨嬪彿锛�</text>
+ <text class="info-value">{{ item.specificationModel }}</text>
+ </view>
+ <view class="info-row">
+ <text class="info-label">鍗曚綅锛�</text>
+ <text class="info-value">{{ item.unit }}</text>
+ </view>
+ <view class="info-row">
+ <text class="info-label">鏁伴噺锛�</text>
+ <text class="info-value">{{ item.quantity }}</text>
+ </view>
+ </view>
+ <view class="product-action" v-if="!isReadonly">
+ <up-icon name="trash" color="#fa3534" size="20" @click="removeProduct(index)"></up-icon>
+ </view>
+ </view>
+ </view>
+ <view v-else class="no-product">
+ <text>鏆傛棤鍏宠仈浜у搧</text>
+ </view>
+ </view>
+ </view>
+
+ <!-- 鍚勭閫夋嫨鍣� -->
+ <up-action-sheet :show="showCustomerPicker" :actions="customerActions" title="閫夋嫨瀹㈡埛" @select="onCustomerSelect" @close="showCustomerPicker = false" />
+ <up-action-sheet :show="showServiceTypePicker" :actions="serviceTypeActions" title="閫夋嫨鍞悗绫诲瀷" @select="onServiceTypeSelect" @close="showServiceTypePicker = false" />
+ <up-action-sheet :show="showSalesOrderPicker" :actions="salesOrderActions" title="閫夋嫨鍏宠仈閿�鍞崟" @select="onSalesOrderSelect" @close="showSalesOrderPicker = false" />
+ <up-action-sheet :show="showUrgencyPicker" :actions="urgencyActions" title="閫夋嫨绱ф�ョ▼搴�" @select="onUrgencySelect" @close="showUrgencyPicker = false" />
+
+ <!-- 浜у搧閫夋嫨寮圭獥 -->
+ <up-popup :show="showProductSelect" mode="bottom" @close="showProductSelect = false" round="10">
+ <view class="product-select-popup">
+ <view class="popup-header">
+ <text class="popup-title">閫夋嫨浜у搧</text>
+ <up-icon name="close" size="20" @click="showProductSelect = false"></up-icon>
+ </view>
+ <scroll-view scroll-y class="product-scroll">
+ <view v-for="(item, index) in availableProducts" :key="index" class="selectable-product" @click="toggleProduct(item)">
+ <view class="product-checkbox">
+ <up-icon :name="isProductSelected(item) ? 'checkbox-mark' : 'minus-circle'" :color="isProductSelected(item) ? '#2979ff' : '#ccc'" size="20"></up-icon>
+ </view>
+ <view class="product-details">
+ <text class="p-name">{{ item.productCategory }}</text>
+ <text class="p-model">{{ item.specificationModel }} | {{ item.unit }}</text>
+ </view>
+ </view>
+ </scroll-view>
+ <view class="popup-footer">
+ <up-button type="primary" @click="showProductSelect = false">纭畾</up-button>
+ </view>
+ </view>
+ </up-popup>
+
+ <FooterButtons :show="!isReadonly" @cancel="goBack" @confirm="submitForm" />
+ </view>
+</template>
+
+<script setup>
+import { ref, reactive, computed, onMounted } from 'vue';
+import { getAllCustomerList, getSalesLedger, afterSalesServiceAdd, afterSalesServiceUpdate } from '@/api/customerService/index';
+import PageHeader from '@/components/PageHeader.vue';
+import FooterButtons from '@/components/FooterButtons.vue';
+import { useDict } from '@/utils/dict';
+import useUserStore from '@/store/modules/user';
+
+const userStore = useUserStore();
+const { post_sale_waiting_list, degree_of_urgency } = useDict('post_sale_waiting_list', 'degree_of_urgency');
+
+const operationType = ref('add');
+const isReadonly = computed(() => operationType.value === 'view');
+const pageTitle = computed(() => {
+ if (operationType.value === 'view') return '鍞悗鍗曡鎯�';
+ if (operationType.value === 'add') return '鏂板鍞悗鍗�';
+ return '缂栬緫鍞悗鍗�';
+});
+const formRef = ref(null);
+const form = reactive({
+ id: null,
+ customerName: '',
+ customerId: null,
+ serviceType: '',
+ salesContractNo: '',
+ salesLedgerId: null,
+ urgency: '',
+ disRes: '',
+ productModelIds: '',
+ checkUserId: null,
+ feedbackDate: '',
+});
+
+const rules = {
+ customerName: [{ required: true, message: '璇烽�夋嫨瀹㈡埛', trigger: 'change' }],
+ serviceType: [{ required: true, message: '璇烽�夋嫨鍞悗绫诲瀷', trigger: 'change' }],
+ salesContractNo: [{ required: true, message: '璇烽�夋嫨鍏宠仈閿�鍞崟', trigger: 'change' }],
+ urgency: [{ required: true, message: '璇烽�夋嫨绱ф�ョ▼搴�', trigger: 'change' }],
+};
+
+const tableData = ref([]);
+const customerList = ref([]);
+const salesOrderList = ref([]);
+const availableProducts = ref([]);
+
+const showCustomerPicker = ref(false);
+const showServiceTypePicker = ref(false);
+const showSalesOrderPicker = ref(false);
+const showUrgencyPicker = ref(false);
+const showProductSelect = ref(false);
+
+const customerActions = computed(() => customerList.value.map(item => ({ name: item.customerName, value: item.id })));
+const serviceTypeActions = computed(() => (post_sale_waiting_list.value || []).map(item => ({ name: item.label, value: item.value })));
+const salesOrderActions = computed(() => salesOrderList.value.map(item => ({ name: item.salesContractNo, value: item.salesContractNo, id: item.id, products: item.productData })));
+const urgencyActions = computed(() => (degree_of_urgency.value || []).map(item => ({ name: item.label, value: item.value })));
+
+const serviceTypeLabel = computed(() => {
+ const item = (post_sale_waiting_list.value || []).find(i => i.value == form.serviceType);
+ return item ? item.label : '';
+});
+
+const urgencyLabel = computed(() => {
+ const item = (degree_of_urgency.value || []).find(i => i.value == form.urgency);
+ return item ? item.label : '';
+});
+
+const goBack = () => {
+ uni.navigateBack();
+};
+
+const openCustomerPicker = () => {
+ if (isReadonly.value) return;
+ showCustomerPicker.value = true;
+};
+const openServiceTypePicker = () => {
+ if (isReadonly.value) return;
+ showServiceTypePicker.value = true;
+};
+const openSalesOrderPicker = () => {
+ if (isReadonly.value) return;
+ if (!form.customerName) {
+ uni.showToast({ title: '璇峰厛閫夋嫨瀹㈡埛', icon: 'none' });
+ return;
+ }
+ showSalesOrderPicker.value = true;
+};
+const openUrgencyPicker = () => {
+ if (isReadonly.value) return;
+ showUrgencyPicker.value = true;
+};
+
+const onCustomerSelect = (item) => {
+ form.customerName = item.name;
+ form.customerId = item.value;
+ form.salesContractNo = '';
+ form.salesLedgerId = null;
+ tableData.value = [];
+ availableProducts.value = [];
+ fetchSalesOrders(item.name);
+};
+
+const onServiceTypeSelect = (item) => {
+ form.serviceType = item.value;
+};
+
+const onSalesOrderSelect = (item) => {
+ form.salesContractNo = item.name;
+ form.salesLedgerId = item.id;
+ setProductsFromSalesOrder(item, true);
+};
+
+const onUrgencySelect = (item) => {
+ form.urgency = item.value;
+};
+
+const normalizeProduct = (p) => {
+ return {
+ ...p,
+ id: p.id || p.productModelId || p.modelId,
+ productCategory: p.productCategory || p.productName || '',
+ specificationModel: p.specificationModel || p.model || '',
+ };
+};
+
+const setProductsFromSalesOrder = (orderAction, selectAll = false) => {
+ const products = Array.isArray(orderAction?.products) ? orderAction.products : [];
+ const normalizedProducts = products.map(p => normalizeProduct(p));
+ availableProducts.value = normalizedProducts;
+ if (selectAll) {
+ tableData.value = normalizedProducts.slice();
+ return;
+ }
+ const ids = String(form.productModelIds || '')
+ .split(',')
+ .map(s => s.trim())
+ .filter(Boolean);
+ if (ids.length === 0) {
+ tableData.value = normalizedProducts.slice();
+ return;
+ }
+ const idSet = new Set(ids.map(String));
+ tableData.value = normalizedProducts.filter(p => idSet.has(String(p.id)));
+};
+
+const fetchCustomers = () => {
+ getAllCustomerList({ current: 1, size: 1000 }).then(res => {
+ const records = res?.records ?? res?.data?.records ?? [];
+ customerList.value = Array.isArray(records) ? records : [];
+ });
+};
+
+const fetchSalesOrders = (customerName) => {
+ getSalesLedger({ customerName }).then(res => {
+ const records = res?.records ?? res?.data?.records ?? [];
+ salesOrderList.value = Array.isArray(records) ? records : [];
+ if (form.salesContractNo) {
+ const match = salesOrderList.value.find(i => String(i.salesContractNo) === String(form.salesContractNo));
+ if (match) {
+ setProductsFromSalesOrder({ id: match.id, name: match.salesContractNo, products: match.productData }, false);
+ form.salesLedgerId = match.id;
+ }
+ }
+ });
+};
+
+const removeProduct = (index) => {
+ tableData.value.splice(index, 1);
+};
+
+const isProductSelected = (product) => {
+ const id = product.id || product.productModelId || product.modelId;
+ return tableData.value.some(p => (p.id || p.productModelId || p.modelId) === id);
+};
+
+const toggleProduct = (product) => {
+ if (isReadonly.value) return;
+ const id = product.id || product.productModelId || product.modelId;
+ const index = tableData.value.findIndex(p => (p.id || p.productModelId || p.modelId) === id);
+ if (index > -1) {
+ tableData.value.splice(index, 1);
+ } else {
+ tableData.value.push(normalizeProduct(product));
+ }
+};
+
+const submitForm = () => {
+ if (isReadonly.value) return;
+ formRef.value.validate().then(valid => {
+ if (valid) {
+ form.productModelIds = tableData.value.map(p => p.id || p.productModelId || p.modelId).join(',');
+ if (!form.checkUserId) {
+ form.checkUserId = userStore.id;
+ }
+ if (!form.feedbackDate) {
+ const now = new Date();
+ form.feedbackDate = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
+ }
+ const api = operationType.value === 'add' ? afterSalesServiceAdd : afterSalesServiceUpdate;
+
+ api(form).then((res) => {
+ if (res?.code === 200 || res?.code === undefined) {
+ uni.showToast({ title: operationType.value === 'add' ? '鏂板鎴愬姛' : '淇敼鎴愬姛', icon: 'success' });
+ setTimeout(() => goBack(), 500);
+ return;
+ }
+ uni.showToast({ title: res?.msg || '鎿嶄綔澶辫触', icon: 'none' });
+ }).catch(() => {});
+ }
+ });
+};
+
+onMounted(() => {
+ operationType.value = uni.getStorageSync('afterSalesOperationType') || 'add';
+ const editDataStr = uni.getStorageSync('afterSalesEditData');
+
+ fetchCustomers();
+
+ if (editDataStr) {
+ const editData = JSON.parse(editDataStr);
+ Object.assign(form, editData);
+ if (form.customerName) {
+ fetchSalesOrders(form.customerName);
+ }
+ } else {
+ form.checkUserId = userStore.id;
+ const now = new Date();
+ form.feedbackDate = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
+ }
+});
+</script>
+
+<style scoped lang="scss">
+.after-sales-edit {
+ min-height: 100vh;
+ background: #f8f9fa;
+ padding-bottom: 100px;
+}
+
+.form-container {
+ background: #ffffff;
+ padding: 10px 20px;
+ margin-top: 10px;
+}
+
+.product-section {
+ margin-top: 20px;
+ border-top: 1px solid #f0f0f0;
+ padding-top: 20px;
+}
+
+.section-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15px;
+}
+
+.section-title {
+ font-size: 16px;
+ font-weight: 600;
+ color: #333;
+}
+
+.product-list {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.product-item {
+ background: #f8f9fa;
+ border-radius: 8px;
+ padding: 12px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.product-info {
+ flex: 1;
+}
+
+.info-row {
+ display: flex;
+ margin-bottom: 4px;
+ font-size: 13px;
+}
+
+.info-label {
+ color: #909399;
+ width: 70px;
+}
+
+.info-value {
+ color: #303133;
+ flex: 1;
+}
+
+.no-product {
+ text-align: center;
+ padding: 30px 0;
+ color: #999;
+ font-size: 14px;
+}
+
+.product-select-popup {
+ height: 60vh;
+ display: flex;
+ flex-direction: column;
+ background: #fff;
+}
+
+.popup-header {
+ padding: 15px 20px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ border-bottom: 1px solid #f0f0f0;
+}
+
+.popup-title {
+ font-size: 16px;
+ font-weight: 600;
+}
+
+.product-scroll {
+ flex: 1;
+ padding: 10px 20px;
+}
+
+.selectable-product {
+ display: flex;
+ align-items: center;
+ padding: 15px 0;
+ border-bottom: 1px solid #f5f5f5;
+}
+
+.product-checkbox {
+ margin-right: 15px;
+}
+
+.product-details {
+ display: flex;
+ flex-direction: column;
+}
+
+.p-name {
+ font-size: 14px;
+ color: #333;
+ margin-bottom: 4px;
+}
+
+.p-model {
+ font-size: 12px;
+ color: #999;
+}
+
+.popup-footer {
+ padding: 20px;
+ border-top: 1px solid #f0f0f0;
+}
+</style>
diff --git a/src/pages/customerService/feedbackRegistration/index.vue b/src/pages/customerService/feedbackRegistration/index.vue
new file mode 100644
index 0000000..e2a681e
--- /dev/null
+++ b/src/pages/customerService/feedbackRegistration/index.vue
@@ -0,0 +1,323 @@
+<template>
+ <view class="after-sales-registration">
+ <!-- 浣跨敤閫氱敤椤甸潰澶撮儴缁勪欢 -->
+ <PageHeader title="鍞悗鐧昏" @back="goBack" />
+
+ <!-- 缁熻鍗$墖鍖哄煙 -->
+ <view class="stats-container">
+ <view v-for="(item, index) in statsList" :key="index" class="stat-card">
+ <view class="stat-icon" :style="{ backgroundColor: item.bgColor }">
+ <up-icon :name="item.icon" :color="item.color" size="20"></up-icon>
+ </view>
+ <view class="stat-info">
+ <text class="stat-number">{{ item.count }}</text>
+ <text class="stat-label">{{ item.label }}</text>
+ </view>
+ </view>
+ </view>
+
+ <!-- 鎼滅储鍜岀瓫閫夊尯鍩� -->
+ <view class="search-section">
+ <view class="search-bar">
+ <view class="search-input">
+ <up-input class="search-text" placeholder="璇疯緭鍏ュ伐鍗曠紪鍙锋悳绱�" v-model="searchForm.afterSalesServiceNo" @change="handleQuery" clearable />
+ </view>
+ <view class="filter-button" @click="handleQuery">
+ <up-icon name="search" size="24" color="#999"></up-icon>
+ </view>
+ </view>
+ </view>
+
+ <!-- 鍞悗鍗曞垪琛� -->
+ <view class="ledger-list" v-if="tableData.length > 0">
+ <view v-for="(item, index) in tableData" :key="index" class="ledger-item" @click="handleRowClick(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.afterSalesServiceNo }}</text>
+ </view>
+ <view class="item-tag">
+ <up-tag :text="getStatusLabel(item.status)" :type="getStatusType(item.status)" size="mini"></up-tag>
+ </view>
+ </view>
+ <up-divider></up-divider>
+ <view class="item-details">
+ <view class="detail-row">
+ <text class="detail-label">閿�鍞崟鍙�</text>
+ <text class="detail-value">{{ item.salesContractNo }}</text>
+ </view>
+ <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.feedbackDate }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">鍞悗绫诲瀷</text>
+ <text class="detail-value">{{ getDictLabel(post_sale_waiting_list, item.serviceType) }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">绱ф�ョ▼搴�</text>
+ <text class="detail-value">{{ getDictLabel(degree_of_urgency, item.urgency) }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">鐧昏浜�</text>
+ <text class="detail-value">{{ item.checkNickName }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">闂鎻忚堪</text>
+ <text class="detail-value">{{ item.disRes }}</text>
+ </view>
+ </view>
+ <up-divider v-if="canEdit(item)"></up-divider>
+ <view class="detail-buttons" v-if="canEdit(item)">
+ <up-button size="small" type="primary" plain @click.stop="openForm('edit', item)">缂栬緫</up-button>
+ <up-button size="small" type="error" plain @click.stop="handleDelete(item)">鍒犻櫎</up-button>
+ </view>
+ </view>
+ </view>
+ <view v-else class="no-data">
+ <text>鏆傛棤鍞悗鐧昏鏁版嵁</text>
+ </view>
+
+ <!-- 娴姩鎿嶄綔鎸夐挳 -->
+ <view class="fab-button" @click="openForm('add')">
+ <up-icon name="plus" size="24" color="#ffffff"></up-icon>
+ </view>
+ </view>
+</template>
+
+<script setup>
+import { ref, reactive, computed, onMounted } from 'vue';
+import { onShow } from '@dcloudio/uni-app';
+import { afterSalesServiceListPage, afterSalesServiceDelete, getSalesLedgerDetail } from '@/api/customerService/index';
+import useUserStore from '@/store/modules/user';
+import PageHeader from '@/components/PageHeader.vue';
+import { useDict } from '@/utils/dict';
+
+const userStore = useUserStore();
+
+// 瀛楀吀
+const { post_sale_waiting_list, degree_of_urgency, work_order_status } = useDict(
+ 'post_sale_waiting_list',
+ 'degree_of_urgency',
+ 'work_order_status'
+);
+
+const statsList = ref([
+ {
+ icon: 'file-text',
+ count: 0,
+ label: '鍏ㄩ儴宸ュ崟',
+ color: '#4080ff',
+ bgColor: '#eaf2ff'
+ },
+ {
+ icon: 'file-text',
+ count: 0,
+ label: '宸插鐞�',
+ color: '#ff9a2e',
+ bgColor: '#fff5e6'
+ },
+ {
+ icon: 'account',
+ count: 0,
+ label: '宸插畬鎴�',
+ color: '#00b42a',
+ bgColor: '#e6f7ed'
+ },
+]);
+
+const searchForm = reactive({
+ afterSalesServiceNo: '',
+ status: '',
+ urgency: '',
+ serviceType: '',
+ orderNo: '',
+});
+
+const tableData = ref([]);
+const loading = ref(false);
+const page = reactive({
+ current: 1,
+ size: 20,
+ total: 0,
+});
+
+const goBack = () => {
+ uni.navigateBack();
+};
+
+const handleQuery = () => {
+ page.current = 1;
+ getList();
+};
+
+const getList = () => {
+ loading.value = true;
+ uni.showLoading({ title: '鍔犺浇涓�...' });
+
+ getStats();
+
+ afterSalesServiceListPage({ ...searchForm, ...page })
+ .then((res) => {
+ const records = res?.data?.records ?? res?.records ?? [];
+ const total = res?.data?.total ?? res?.total ?? 0;
+ tableData.value = Array.isArray(records) ? records : [];
+ page.total = Number.isFinite(Number(total)) ? Number(total) : 0;
+ })
+ .finally(() => {
+ loading.value = false;
+ uni.hideLoading();
+ });
+};
+
+const getStats = () => {
+ getSalesLedgerDetail({}).then((res) => {
+ if (res.code === 200) {
+ const statsData = Array.isArray(res.data) ? res.data : [];
+ statsList.value[0].count = getStatsCountByStatus(statsData, 3);
+ statsList.value[1].count = getStatsCountByStatus(statsData, 2);
+ statsList.value[2].count = getStatsCountByStatus(statsData, 1);
+ }
+ });
+};
+
+const getStatsCountByStatus = (list, status) => {
+ if (!Array.isArray(list)) return 0;
+ return list.find((item) => item?.status === status)?.count || 0;
+};
+
+const getStatusLabel = (status) => {
+ if (status === 1) return '寰呭鐞�';
+ if (status === 2) return '宸插鐞�';
+ return '鏈煡';
+};
+
+const getStatusType = (status) => {
+ if (status === 1) return 'error';
+ if (status === 2) return 'success';
+ return 'info';
+};
+
+const getDictLabel = (dict, value) => {
+ if (!dict || !dict.value) return value;
+ const item = dict.value.find(i => i.value == value);
+ return item ? item.label : value;
+};
+
+const canEdit = (row) => {
+ if (!row) return false;
+ return row.status === 1 && String(row.checkUserId) === String(userStore.id);
+}
+
+const handleRowClick = (row) => {
+ if (canEdit(row)) {
+ openForm('edit', row)
+ return
+ }
+ openForm('view', row)
+}
+
+const openForm = (type, row) => {
+ uni.setStorageSync('afterSalesOperationType', type);
+ if (row) {
+ uni.setStorageSync('afterSalesEditData', JSON.stringify(row));
+ } else {
+ uni.removeStorageSync('afterSalesEditData');
+ }
+ uni.navigateTo({
+ url: '/pages/customerService/feedbackRegistration/edit'
+ });
+};
+
+const handleDelete = (row) => {
+ if (row.checkUserId !== userStore.id) {
+ uni.showToast({ title: '涓嶅彲鍒犻櫎浠栦汉缁存姢鐨勬暟鎹�', icon: 'none' });
+ return;
+ }
+
+ uni.showModal({
+ title: '鎻愮ず',
+ content: '閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�',
+ success: (res) => {
+ if (res.confirm) {
+ afterSalesServiceDelete([row.id]).then(() => {
+ uni.showToast({ title: '鍒犻櫎鎴愬姛', icon: 'success' });
+ getList();
+ });
+ }
+ }
+ });
+};
+
+onShow(() => {
+ getList();
+});
+</script>
+
+<style scoped lang="scss">
+@import "@/styles/sales-common.scss";
+
+.after-sales-registration {
+ min-height: 100vh;
+ background: #f8f9fa;
+}
+
+.stats-container {
+ display: flex;
+ gap: 10px;
+ padding: 15px 20px;
+ background: #ffffff;
+}
+
+.stat-card {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 12px 10px;
+ background-color: #f8f9fa;
+ border-radius: 8px;
+}
+
+.stat-icon {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 36px;
+ height: 36px;
+ border-radius: 6px;
+}
+
+.stat-info {
+ display: flex;
+ flex-direction: column;
+}
+
+.stat-number {
+ font-size: 16px;
+ font-weight: 600;
+ color: #303133;
+}
+
+.stat-label {
+ font-size: 10px;
+ color: #909399;
+}
+
+.detail-buttons {
+ display: flex;
+ gap: 10px;
+ justify-content: flex-end;
+ padding: 12px 0;
+}
+
+.ledger-item {
+ padding: 0 16px;
+}
+</style>
diff --git a/src/pages/financialManagement/expenseManagement/edit.vue b/src/pages/financialManagement/expenseManagement/edit.vue
new file mode 100644
index 0000000..292957a
--- /dev/null
+++ b/src/pages/financialManagement/expenseManagement/edit.vue
@@ -0,0 +1,120 @@
+<template>
+ <view class="sales-account">
+ <PageHeader :title="pageTitle" @back="goBack" />
+ <view class="search-section">
+ <up-form :model="form" :rules="rules" ref="formRef">
+ <up-form-item label="鏀嚭鏃ユ湡" prop="expenseDate">
+ <uni-datetime-picker type="date" v-model="form.expenseDate" />
+ </up-form-item>
+ <up-form-item label="鏀嚭绫诲瀷" prop="expenseType">
+ <up-picker :columns="[expenseTypes]" key-name="label" v-model="expenseTypeIndex" @confirm="onExpenseTypeConfirm" />
+ </up-form-item>
+ <up-form-item label="渚涘簲鍟嗗悕绉�" prop="supplierName">
+ <up-input v-model="form.supplierName" placeholder="璇疯緭鍏�" />
+ </up-form-item>
+ <up-form-item label="鏀嚭閲戦" prop="expenseMoney">
+ <up-input type="number" v-model="form.expenseMoney" placeholder="璇疯緭鍏�" />
+ </up-form-item>
+ <up-form-item label="鏀嚭鎻忚堪" prop="expenseDescribed">
+ <up-input v-model="form.expenseDescribed" placeholder="璇疯緭鍏�" />
+ </up-form-item>
+ <up-form-item label="浠樻鏂瑰紡" prop="expenseMethod">
+ <up-picker :columns="[checkoutPayment]" key-name="label" v-model="expenseMethodIndex" @confirm="onMethodConfirm" />
+ </up-form-item>
+ <up-form-item label="鍙戠エ鍙风爜" prop="invoiceNumber">
+ <up-input v-model="form.invoiceNumber" placeholder="璇疯緭鍏�" />
+ </up-form-item>
+ <up-form-item label="澶囨敞" prop="note">
+ <up-textarea v-model="form.note" placeholder="璇疯緭鍏�" autoHeight />
+ </up-form-item>
+ </up-form>
+ <view class="actions">
+ <u-button type="primary" @click="submitForm">淇濆瓨</u-button>
+ </view>
+ </view>
+ </view>
+</template>
+
+<script setup>
+import { ref, reactive, computed } from "vue";
+import { onLoad } from "@dcloudio/uni-app";
+import { useDict } from "@/utils/dict";
+import { add, update, getAccountExpense } from "@/api/financialManagement/expenseManagement";
+
+const operationType = ref("add");
+const id = ref(undefined);
+
+const { checkout_payment, expense_types } = useDict("checkout_payment", "expense_types");
+const checkoutPayment = ref([]);
+const expenseTypes = ref([]);
+const expenseTypeIndex = ref([0]);
+const expenseMethodIndex = ref([0]);
+
+const formRef = ref();
+const form = reactive({
+ expenseDate: undefined,
+ expenseType: undefined,
+ supplierName: "",
+ expenseMoney: undefined,
+ expenseDescribed: "",
+ expenseMethod: undefined,
+ invoiceNumber: "",
+ note: "",
+});
+
+const rules = {
+ expenseDate: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ expenseType: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ supplierName: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ expenseMoney: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ expenseDescribed: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ expenseMethod: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+};
+
+const pageTitle = computed(() => (operationType.value === "edit" ? "缂栬緫鏀嚭" : "鏂板鏀嚭"));
+
+const onExpenseTypeConfirm = (e) => {
+ const item = expenseTypes.value[e.value[0]];
+ if (item) form.expenseType = item.value;
+};
+const onMethodConfirm = (e) => {
+ const item = checkoutPayment.value[e.value[0]];
+ if (item) form.expenseMethod = item.value;
+};
+
+const syncDict = () => {
+ checkoutPayment.value = (checkout_payment?.value || []).map(i => ({ label: i.label, value: i.value }));
+ expenseTypes.value = (expense_types?.value || []).map(i => ({ label: i.label, value: i.value }));
+};
+
+const submitForm = () => {
+ formRef.value?.validate(async (valid) => {
+ if (!valid) return;
+ const payload = { ...form };
+ const res = operationType.value === "edit" ? await update({ id: id.value, ...payload }) : await add(payload);
+ if (res?.code === 200) {
+ uni.navigateBack();
+ }
+ });
+};
+
+const goBack = () => {
+ uni.navigateBack();
+};
+
+onLoad(async (query) => {
+ syncDict();
+ operationType.value = query?.type || "add";
+ if (query?.id) {
+ id.value = query.id;
+ const res = await getAccountExpense(id.value);
+ const data = res?.data ?? res;
+ Object.assign(form, data || {});
+ }
+});
+</script>
+
+<style scoped lang="scss">
+@import "@/styles/sales-common.scss";
+.actions { margin-top: 16px; }
+</style>
diff --git a/src/pages/financialManagement/expenseManagement/index.vue b/src/pages/financialManagement/expenseManagement/index.vue
new file mode 100644
index 0000000..397414b
--- /dev/null
+++ b/src/pages/financialManagement/expenseManagement/index.vue
@@ -0,0 +1,149 @@
+<template>
+ <view class="sales-account">
+ <PageHeader title="鏀嚭绠$悊" @back="goBack" />
+ <view class="search-section">
+ <view class="search-bar">
+ <view class="search-input">
+ <uni-datetime-picker type="daterange" v-model="filters.entryDate" @change="onDateChange" />
+ </view>
+ <view class="search-input">
+ <up-input readonly placeholder="浠樻鏂瑰紡" v-model="expenseMethodLabel" @click="methodPickerShow = true" />
+ </view>
+ <view class="filter-button" @click="getList">
+ <up-icon name="search" size="24" color="#999" />
+ </view>
+ </view>
+ <view class="actions">
+ <u-button type="primary" size="small" @click="goAdd">鏂板</u-button>
+ </view>
+ </view>
+ <view class="ledger-list" v-if="list.length>0">
+ <view class="ledger-item" v-for="item in list" :key="item.id">
+ <view class="item-header">
+ <view class="item-left">
+ <view class="document-icon"><up-icon name="file-text" color="#fff" size="16" /></view>
+ <text class="item-id">{{ item.supplierName || '--' }}</text>
+ </view>
+ <view class="item-tag">
+ <u-tag>{{ methodText(item.expenseMethod) }}</u-tag>
+ </view>
+ </view>
+ <up-divider></up-divider>
+ <view class="item-details">
+ <view class="detail-row"><text class="detail-label">鏀嚭鏃ユ湡</text><text class="detail-value">{{ item.expenseDate || '--' }}</text></view>
+ <view class="detail-row"><text class="detail-label">鏀嚭绫诲瀷</text><text class="detail-value">{{ expenseTypeText(item.expenseType) || '--' }}</text></view>
+ <view class="detail-row"><text class="detail-label">鏀嚭閲戦(鍏�)</text><text class="detail-value highlight">{{ fmtAmount(item.expenseMoney) }}</text></view>
+ <view class="detail-row"><text class="detail-label">鍙戠エ鍙风爜</text><text class="detail-value">{{ item.invoiceNumber || '--' }}</text></view>
+ <view class="detail-row"><text class="detail-label">澶囨敞</text><text class="detail-value">{{ item.note || '--' }}</text></view>
+ </view>
+ <view class="card-actions">
+ <u-button size="small" @click="goEdit(item)" :disabled="!!item.businessId">缂栬緫</u-button>
+ <u-button size="small" type="error" @click="confirmDelete(item)" :disabled="!!item.businessId">鍒犻櫎</u-button>
+ </view>
+ </view>
+ </view>
+ <view class="no-data" v-else><text>鏆傛棤鏁版嵁</text></view>
+
+ <up-action-sheet :show="methodPickerShow" :actions="checkoutPayment" title="浠樻鏂瑰紡" @select="onSelectMethod" @close="methodPickerShow=false" />
+ </view>
+ </template>
+
+<script setup>
+import { ref, reactive } from "vue";
+import { onShow } from "@dcloudio/uni-app";
+import { listPage, delAccountExpense } from "@/api/financialManagement/expenseManagement";
+import { useDict } from "@/utils/dict";
+
+const list = ref([]);
+const filters = reactive({ entryDate: null, expenseMethod: undefined, entryDateStart: undefined, entryDateEnd: undefined });
+const { checkout_payment, expense_types } = useDict("checkout_payment", "expense_types");
+const checkoutPayment = ref([]);
+const expenseTypes = ref([]);
+const methodPickerShow = ref(false);
+const expenseMethodLabel = ref("");
+
+const syncDict = () => {
+ checkoutPayment.value = (checkout_payment?.value || []).map(i => ({ label: i.label, value: i.value }));
+ expenseTypes.value = (expense_types?.value || []).map(i => ({ label: i.label, value: i.value }));
+};
+
+const getList = () => {
+ listPage({ expenseMethod: filters.expenseMethod, entryDateStart: filters.entryDateStart, entryDateEnd: filters.entryDateEnd, current: 1, size: 100 })
+ .then(res => {
+ const records = res?.data?.records ?? res?.records ?? [];
+ list.value = records;
+ });
+};
+
+const onDateChange = (val) => {
+ if (val && val.length === 2) {
+ filters.entryDateStart = val[0];
+ filters.entryDateEnd = val[1];
+ } else {
+ filters.entryDateStart = undefined;
+ filters.entryDateEnd = undefined;
+ }
+};
+
+const onSelectMethod = (e) => {
+ filters.expenseMethod = e.value;
+ expenseMethodLabel.value = e.label;
+ methodPickerShow.value = false;
+};
+
+const methodText = (v) => {
+ const m = checkoutPayment.value.find(i=>String(i.value)===String(v));
+ return m?.label || "--";
+};
+const expenseTypeText = (v) => {
+ const m = expenseTypes.value.find(i=>String(i.value)===String(v));
+ return m?.label || null;
+};
+const fmtAmount = (v) => {
+ const n = parseFloat(v || 0);
+ return n.toFixed(2);
+};
+
+const goAdd = () => {
+ uni.navigateTo({ url: "/pages/financialManagement/expenseManagement/edit?type=add" });
+};
+const goEdit = (row) => {
+ uni.navigateTo({ url: `/pages/financialManagement/expenseManagement/edit?type=edit&id=${row.id}` });
+};
+const confirmDelete = (row) => {
+ uni.showModal({
+ title: "鎻愮ず",
+ content: "纭鍒犻櫎璇ヨ褰曪紵",
+ success: async (r) => {
+ if (r.confirm) {
+ const ids = Array.isArray(row) ? row.map(i=>i.id) : [row.id];
+ const res = await delAccountExpense(ids);
+ if (res?.code === 200) getList();
+ }
+ },
+ });
+};
+
+const onExpenseTypeConfirm = (e) => {
+ const item = expenseTypes.value[e.value[0]];
+ if (item) form.expenseType = item.value;
+};
+const onMethodConfirm = (e) => {
+ const item = checkoutPayment.value[e.value[0]];
+ if (item) form.expenseMethod = item.value;
+};
+
+const goBack = () => {
+ uni.navigateBack();
+};
+
+syncDict();
+onShow(() => {
+ getList();
+});
+</script>
+
+<style scoped lang="scss">
+@import "@/styles/sales-common.scss";
+.actions { margin-top: 8px; }
+</style>
diff --git a/src/pages/financialManagement/loanManagement/edit.vue b/src/pages/financialManagement/loanManagement/edit.vue
new file mode 100644
index 0000000..a5226ed
--- /dev/null
+++ b/src/pages/financialManagement/loanManagement/edit.vue
@@ -0,0 +1,87 @@
+<template>
+ <view class="sales-account">
+ <PageHeader :title="pageTitle" @back="goBack" />
+ <view class="search-section">
+ <up-form :model="form" :rules="rules" ref="formRef">
+ <up-form-item label="鍊熸浜哄鍚�" prop="borrowerName">
+ <up-input v-model="form.borrowerName" placeholder="璇疯緭鍏�" />
+ </up-form-item>
+ <up-form-item label="鍊熸閲戦(鍏�)" prop="borrowAmount">
+ <up-input type="number" v-model="form.borrowAmount" placeholder="璇疯緭鍏�" />
+ </up-form-item>
+ <up-form-item label="鍊熸鍒╃巼(%)" prop="interestRate">
+ <up-input type="number" v-model="form.interestRate" placeholder="渚嬪 5.85" />
+ </up-form-item>
+ <up-form-item label="鍊熸鏃ユ湡" prop="borrowDate">
+ <uni-datetime-picker type="date" v-model="form.borrowDate" />
+ </up-form-item>
+ <up-form-item v-if="operationType==='repay'" label="瀹為檯杩樻鏃ユ湡" prop="repayDate">
+ <uni-datetime-picker type="date" v-model="form.repayDate" />
+ </up-form-item>
+ <up-form-item label="澶囨敞" prop="remark">
+ <up-textarea v-model="form.remark" autoHeight />
+ </up-form-item>
+ </up-form>
+ <view class="actions">
+ <u-button type="primary" @click="submitForm">淇濆瓨</u-button>
+ </view>
+ </view>
+ </view>
+</template>
+
+<script setup>
+import { ref, reactive, computed } from "vue";
+import { onLoad } from "@dcloudio/uni-app";
+import { add, update } from "@/api/financialManagement/loanManagement";
+
+const operationType = ref("add");
+const id = ref(undefined);
+
+const formRef = ref();
+const form = reactive({
+ borrowerName: "",
+ borrowAmount: undefined,
+ interestRate: undefined,
+ borrowDate: undefined,
+ repayDate: undefined,
+ remark: "",
+});
+
+const rules = {
+ borrowerName: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ borrowAmount: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ interestRate: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ borrowDate: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ repayDate: [{ validator: (_r, v, cb) => { if (operationType.value==='repay' && !v) return cb(new Error('璇烽�夋嫨')); cb(); }, trigger: "change" }],
+};
+
+const pageTitle = computed(() => operationType.value==='repay' ? "杩樻" : operationType.value==='edit' ? "缂栬緫鍊熸" : "鏂板鍊熸");
+
+const submitForm = () => {
+ formRef.value?.validate(async (valid) => {
+ if (!valid) return;
+ const payload = operationType.value==='repay' ? { ...form, status: 2 } : { ...form };
+ const res = operationType.value==='add' ? await add(payload) : await update({ id: id.value, ...payload });
+ if (res?.code === 200) {
+ uni.navigateBack();
+ }
+ });
+};
+
+const goBack = () => {
+ uni.navigateBack();
+};
+
+onLoad((query) => {
+ operationType.value = query?.type || "add";
+ if (query?.id) {
+ id.value = query.id;
+ // 閫氳繃瀵艰埅鍙傛暟鎼哄甫鐨勫瓧娈靛洖濉敱 index 椤靛喅瀹氾紝杩欓噷浠呬繚鐣欐渶绠�閫昏緫
+ }
+});
+</script>
+
+<style scoped lang="scss">
+@import "@/styles/sales-common.scss";
+.actions { margin-top: 16px; }
+</style>
diff --git a/src/pages/financialManagement/loanManagement/index.vue b/src/pages/financialManagement/loanManagement/index.vue
new file mode 100644
index 0000000..5383b9c
--- /dev/null
+++ b/src/pages/financialManagement/loanManagement/index.vue
@@ -0,0 +1,196 @@
+<template>
+ <view class="sales-account">
+ <PageHeader title="鍊熸绠$悊" @back="goBack" />
+ <view class="search-section">
+ <view class="search-bar">
+ <view class="search-input">
+ <up-input v-model="filters.borrowerName" placeholder="鍊熸浜哄鍚�" clearable />
+ </view>
+ <view class="search-input">
+ <uni-datetime-picker type="daterange" v-model="filters.borrowDate" @change="onDateChange" />
+ </view>
+ <view class="search-input">
+ <up-picker :columns="[statusOptions]" key-name="label" v-model="statusIndex" @confirm="onStatusConfirm">
+ <up-input readonly :value="statusLabel" placeholder="鍊熸鐘舵��" />
+ </up-picker>
+ </view>
+ <view class="filter-button" @click="getList">
+ <up-icon name="search" size="24" color="#999" />
+ </view>
+ </view>
+ <view class="actions">
+ <u-button type="primary" size="small" @click="goAdd">鏂板</u-button>
+ </view>
+ </view>
+ <view class="ledger-list" v-if="list.length>0">
+ <view class="ledger-item" v-for="item in list" :key="item.id">
+ <view class="item-header">
+ <view class="item-left">
+ <view class="document-icon"><up-icon name="file-text" color="#fff" size="16" /></view>
+ <text class="item-id">{{ item.borrowerName || '--' }}</text>
+ </view>
+ <view class="item-tag">
+ <u-tag :type="statusType(item.status)">{{ statusText(item.status) }}</u-tag>
+ </view>
+ </view>
+ <up-divider></up-divider>
+ <view class="item-details">
+ <view class="detail-row"><text class="detail-label">鍊熸閲戦(鍏�)</text><text class="detail-value highlight">{{ fmtAmount(item.borrowAmount) }}</text></view>
+ <view class="detail-row"><text class="detail-label">鍊熸鍒╃巼(%)</text><text class="detail-value">{{ fmtRate(item.interestRate) }}</text></view>
+ <view class="detail-row"><text class="detail-label">鍊熸鏃ユ湡</text><text class="detail-value">{{ item.borrowDate || '--' }}</text></view>
+ <view class="detail-row"><text class="detail-label">瀹為檯杩樻鏃ユ湡</text><text class="detail-value">{{ item.repayDate || '--' }}</text></view>
+ <view class="detail-row"><text class="detail-label">澶囨敞</text><text class="detail-value">{{ item.remark || '--' }}</text></view>
+ </view>
+ <view class="card-actions">
+ <u-button size="small" @click="goEdit(item)">缂栬緫</u-button>
+ <u-button size="small" type="warning" @click="goRepay(item)" :disabled="item.status!==1">杩樻</u-button>
+ <u-button size="small" type="error" @click="confirmDelete(item)">鍒犻櫎</u-button>
+ </view>
+ </view>
+ </view>
+ <view class="no-data" v-else><text>鏆傛棤鏁版嵁</text></view>
+
+ <up-popup :show="formShow" mode="bottom" @close="closeForm">
+ <view class="popup">
+ <view class="popup-header">{{ formMode==='add'?'鏂板鍊熸': formMode==='repay'?'杩樻':'缂栬緫鍊熸' }}</view>
+ <up-form :model="form" :rules="rules" ref="formRef">
+ <up-form-item label="鍊熸浜哄鍚�" prop="borrowerName">
+ <up-input v-model="form.borrowerName" placeholder="璇疯緭鍏�" />
+ </up-form-item>
+ <up-form-item label="鍊熸閲戦" prop="borrowAmount">
+ <up-input type="number" v-model="form.borrowAmount" placeholder="璇疯緭鍏�" />
+ </up-form-item>
+ <up-form-item label="鍊熸鍒╃巼(%)" prop="interestRate">
+ <up-input type="number" v-model="form.interestRate" placeholder="渚嬪 5.85" />
+ </up-form-item>
+ <up-form-item label="鍊熸鏃ユ湡" prop="borrowDate">
+ <uni-datetime-picker type="date" v-model="form.borrowDate" />
+ </up-form-item>
+ <up-form-item v-if="formMode==='repay'" label="瀹為檯杩樻鏃ユ湡" prop="repayDate">
+ <uni-datetime-picker type="date" v-model="form.repayDate" />
+ </up-form-item>
+ <up-form-item label="澶囨敞" prop="remark">
+ <up-textarea v-model="form.remark" autoHeight />
+ </up-form-item>
+ </up-form>
+ <view class="popup-actions">
+ <u-button @click="closeForm">鍙栨秷</u-button>
+ <u-button type="primary" @click="submitForm">淇濆瓨</u-button>
+ </view>
+ </view>
+ </up-popup>
+ </view>
+</template>
+
+<script setup>
+import { ref, reactive } from "vue";
+import { onShow } from "@dcloudio/uni-app";
+import { listPage, delAccountLoan } from "@/api/financialManagement/loanManagement";
+
+const list = ref([]);
+const filters = reactive({ borrowerName: "", borrowDate: null, entryDateStart: undefined, entryDateEnd: undefined, status: undefined });
+const statusOptions = ref([{ label: "鍏ㄩ儴", value: undefined }, { label: "寰呰繕娆�", value: 1 }, { label: "宸茶繕娆�", value: 2 }]);
+const statusIndex = ref([0]);
+const statusLabel = ref("");
+
+const formShow = ref(false);
+const formMode = ref("add");
+const formRef = ref();
+const form = reactive({
+ id: undefined,
+ borrowerName: "",
+ borrowAmount: undefined,
+ interestRate: undefined,
+ borrowDate: undefined,
+ repayDate: undefined,
+ remark: "",
+ status: undefined,
+});
+const rules = {
+ borrowerName: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ borrowAmount: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ interestRate: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ borrowDate: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ repayDate: [{ validator: (_r, v, cb)=>{ if (formMode.value==='repay' && !v) return cb(new Error('璇烽�夋嫨')); cb(); }, trigger: "change" }],
+};
+
+const getList = () => {
+ const extra = {};
+ if (filters.entryDateStart && filters.entryDateEnd) {
+ extra.entryDateStart = filters.entryDateStart;
+ extra.entryDateEnd = filters.entryDateEnd;
+ }
+ if (filters.status) extra.status = filters.status;
+ listPage({ borrowerName: filters.borrowerName, ...extra, current: 1, size: 100 })
+ .then(res => {
+ const records = res?.data?.records ?? res?.records ?? [];
+ list.value = records;
+ });
+};
+
+const onDateChange = (val) => {
+ if (val && val.length === 2) {
+ filters.entryDateStart = val[0];
+ filters.entryDateEnd = val[1];
+ } else {
+ filters.entryDateStart = undefined;
+ filters.entryDateEnd = undefined;
+ }
+};
+
+const statusText = (s) => s===1?'寰呰繕娆�': s===2?'宸茶繕娆�':'';
+const statusType = (s) => s===1?'error': s===2?'success':'primary';
+const fmtAmount = (v) => {
+ const n = parseFloat(v || 0);
+ return n.toFixed(2);
+};
+const fmtRate = (v) => {
+ if (v===undefined || v===null || v==='') return '-';
+ const n = parseFloat(v);
+ return n.toFixed(2) + '%';
+};
+
+const goAdd = () => {
+ uni.navigateTo({ url: "/pages/financialManagement/loanManagement/edit?type=add" });
+};
+const goEdit = (row) => {
+ uni.navigateTo({ url: `/pages/financialManagement/loanManagement/edit?type=edit&id=${row.id}` });
+};
+const goRepay = (row) => {
+ uni.navigateTo({ url: `/pages/financialManagement/loanManagement/edit?type=repay&id=${row.id}` });
+};
+const confirmDelete = (row) => {
+ uni.showModal({
+ title: "鎻愮ず",
+ content: "纭鍒犻櫎璇ヨ褰曪紵",
+ success: async (r) => {
+ if (r.confirm) {
+ const ids = Array.isArray(row) ? row.map(i=>i.id) : [row.id];
+ const res = await delAccountLoan(ids);
+ if (res?.code === 200) getList();
+ }
+ },
+ });
+};
+
+const onStatusConfirm = (e) => {
+ const item = statusOptions.value[e.value[0]];
+ if (item) {
+ filters.status = item.value;
+ statusLabel.value = item.label || "";
+ }
+};
+
+const goBack = () => {
+ uni.navigateBack();
+};
+
+onShow(() => {
+ getList();
+});
+</script>
+
+<style scoped lang="scss">
+@import "@/styles/sales-common.scss";
+.actions { margin-top: 8px; }
+</style>
diff --git a/src/pages/financialManagement/revenueManagement/edit.vue b/src/pages/financialManagement/revenueManagement/edit.vue
new file mode 100644
index 0000000..869e12c
--- /dev/null
+++ b/src/pages/financialManagement/revenueManagement/edit.vue
@@ -0,0 +1,120 @@
+<template>
+ <view class="sales-account">
+ <PageHeader :title="pageTitle" @back="goBack" />
+ <view class="search-section">
+ <up-form :model="form" :rules="rules" ref="formRef">
+ <up-form-item label="鏀跺叆鏃ユ湡" prop="incomeDate">
+ <uni-datetime-picker type="date" v-model="form.incomeDate" />
+ </up-form-item>
+ <up-form-item label="鏀跺叆绫诲瀷" prop="incomeType">
+ <up-picker :columns="[incomeTypes]" key-name="label" v-model="incomeTypeIndex" @confirm="onIncomeTypeConfirm" />
+ </up-form-item>
+ <up-form-item label="瀹㈡埛鍚嶇О" prop="customerName">
+ <up-input v-model="form.customerName" placeholder="璇疯緭鍏�" />
+ </up-form-item>
+ <up-form-item label="鏀跺叆閲戦" prop="incomeMoney">
+ <up-input type="number" v-model="form.incomeMoney" placeholder="璇疯緭鍏�" />
+ </up-form-item>
+ <up-form-item label="鏀跺叆鎻忚堪" prop="incomeDescribed">
+ <up-input v-model="form.incomeDescribed" placeholder="璇疯緭鍏�" />
+ </up-form-item>
+ <up-form-item label="鏀舵鏂瑰紡" prop="incomeMethod">
+ <up-picker :columns="[paymentMethods]" key-name="label" v-model="incomeMethodIndex" @confirm="onMethodConfirm" />
+ </up-form-item>
+ <up-form-item label="鍙戠エ鍙风爜" prop="invoiceNumber">
+ <up-input v-model="form.invoiceNumber" placeholder="璇疯緭鍏�" />
+ </up-form-item>
+ <up-form-item label="澶囨敞" prop="note">
+ <up-textarea v-model="form.note" placeholder="璇疯緭鍏�" autoHeight />
+ </up-form-item>
+ </up-form>
+ <view class="actions">
+ <u-button type="primary" @click="submitForm">淇濆瓨</u-button>
+ </view>
+ </view>
+ </view>
+</template>
+
+<script setup>
+import { ref, reactive, computed } from "vue";
+import { onLoad } from "@dcloudio/uni-app";
+import { useDict } from "@/utils/dict";
+import { add, update, getAccountIncome } from "@/api/financialManagement/revenueManagement";
+
+const operationType = ref("add");
+const id = ref(undefined);
+
+const { payment_methods, income_types } = useDict("payment_methods", "income_types");
+const paymentMethods = ref([]);
+const incomeTypes = ref([]);
+const incomeTypeIndex = ref([0]);
+const incomeMethodIndex = ref([0]);
+
+const formRef = ref();
+const form = reactive({
+ incomeDate: undefined,
+ incomeType: undefined,
+ customerName: "",
+ incomeMoney: undefined,
+ incomeDescribed: "",
+ incomeMethod: undefined,
+ invoiceNumber: "",
+ note: "",
+});
+
+const rules = {
+ incomeDate: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ incomeType: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+ customerName: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ incomeMoney: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ incomeDescribed: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" }],
+ incomeMethod: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
+};
+
+const pageTitle = computed(() => (operationType.value === "edit" ? "缂栬緫鏀跺叆" : "鏂板鏀跺叆"));
+
+const onIncomeTypeConfirm = (e) => {
+ const item = incomeTypes.value[e.value[0]];
+ if (item) form.incomeType = item.value;
+};
+const onMethodConfirm = (e) => {
+ const item = paymentMethods.value[e.value[0]];
+ if (item) form.incomeMethod = item.value;
+};
+
+const syncDict = () => {
+ paymentMethods.value = (payment_methods?.value || []).map(i => ({ label: i.label, value: i.value }));
+ incomeTypes.value = (income_types?.value || []).filter(i=>i.value!=3).map(i => ({ label: i.label, value: i.value }));
+};
+
+const submitForm = () => {
+ formRef.value?.validate(async (valid) => {
+ if (!valid) return;
+ const payload = { ...form };
+ const res = operationType.value === "edit" ? await update({ id: id.value, ...payload }) : await add(payload);
+ if (res?.code === 200) {
+ uni.navigateBack();
+ }
+ });
+};
+
+const goBack = () => {
+ uni.navigateBack();
+};
+
+onLoad(async (query) => {
+ syncDict();
+ operationType.value = query?.type || "add";
+ if (query?.id) {
+ id.value = query.id;
+ const res = await getAccountIncome(id.value);
+ const data = res?.data ?? res;
+ Object.assign(form, data || {});
+ }
+});
+</script>
+
+<style scoped lang="scss">
+@import "@/styles/sales-common.scss";
+.actions { margin-top: 16px; }
+</style>
diff --git a/src/pages/financialManagement/revenueManagement/index.vue b/src/pages/financialManagement/revenueManagement/index.vue
new file mode 100644
index 0000000..e7a4de5
--- /dev/null
+++ b/src/pages/financialManagement/revenueManagement/index.vue
@@ -0,0 +1,149 @@
+<template>
+ <view class="sales-account">
+ <PageHeader title="鏀跺叆绠$悊" @back="goBack" />
+ <view class="search-section">
+ <view class="search-bar">
+ <view class="search-input">
+ <uni-datetime-picker type="daterange" v-model="filters.entryDate" @change="onDateChange" />
+ </view>
+ <view class="search-input">
+ <up-input readonly placeholder="鏀舵鏂瑰紡" v-model="incomeMethodLabel" @click="methodPickerShow = true" />
+ </view>
+ <view class="filter-button" @click="getList">
+ <up-icon name="search" size="24" color="#999" />
+ </view>
+ </view>
+ <view class="actions">
+ <u-button type="primary" size="small" @click="goAdd">鏂板</u-button>
+ </view>
+ </view>
+ <view class="ledger-list" v-if="list.length>0">
+ <view class="ledger-item" v-for="item in list" :key="item.id">
+ <view class="item-header">
+ <view class="item-left">
+ <view class="document-icon"><up-icon name="file-text" color="#fff" size="16" /></view>
+ <text class="item-id">{{ item.customerName || '--' }}</text>
+ </view>
+ <view class="item-tag">
+ <u-tag>{{ methodText(item.incomeMethod) }}</u-tag>
+ </view>
+ </view>
+ <up-divider></up-divider>
+ <view class="item-details">
+ <view class="detail-row"><text class="detail-label">鏀跺叆鏃ユ湡</text><text class="detail-value">{{ item.incomeDate || '--' }}</text></view>
+ <view class="detail-row"><text class="detail-label">鏀跺叆绫诲瀷</text><text class="detail-value">{{ incomeTypeText(item.incomeType) || '--' }}</text></view>
+ <view class="detail-row"><text class="detail-label">鏀跺叆閲戦(鍏�)</text><text class="detail-value highlight">{{ fmtAmount(item.incomeMoney) }}</text></view>
+ <view class="detail-row"><text class="detail-label">鍙戠エ鍙风爜</text><text class="detail-value">{{ item.invoiceNumber || '--' }}</text></view>
+ <view class="detail-row"><text class="detail-label">澶囨敞</text><text class="detail-value">{{ item.note || '--' }}</text></view>
+ </view>
+ <view class="card-actions">
+ <u-button size="small" @click="goEdit(item)" :disabled="!!item.businessId">缂栬緫</u-button>
+ <u-button size="small" type="error" @click="confirmDelete(item)" :disabled="!!item.businessId">鍒犻櫎</u-button>
+ </view>
+ </view>
+ </view>
+ <view class="no-data" v-else><text>鏆傛棤鏁版嵁</text></view>
+
+ <up-action-sheet :show="methodPickerShow" :actions="paymentMethods" title="鏀舵鏂瑰紡" @select="onSelectMethod" @close="methodPickerShow=false" />
+ </view>
+</template>
+
+<script setup>
+import { ref, reactive } from "vue";
+import { onShow } from "@dcloudio/uni-app";
+import { listPage, delAccountIncome } from "@/api/financialManagement/revenueManagement";
+import { useDict } from "@/utils/dict";
+
+const list = ref([]);
+const filters = reactive({ entryDate: null, incomeMethod: undefined, entryDateStart: undefined, entryDateEnd: undefined });
+const { payment_methods, income_types } = useDict("payment_methods", "income_types");
+const paymentMethods = ref([]);
+const incomeTypes = ref([]);
+const methodPickerShow = ref(false);
+const incomeMethodLabel = ref("");
+
+const syncDict = () => {
+ paymentMethods.value = (payment_methods?.value || []).map(i => ({ label: i.label, value: i.value }));
+ incomeTypes.value = (income_types?.value || []).filter(i=>i.value!=3).map(i => ({ label: i.label, value: i.value }));
+};
+
+const getList = () => {
+ listPage({ incomeMethod: filters.incomeMethod, entryDateStart: filters.entryDateStart, entryDateEnd: filters.entryDateEnd, current: 1, size: 100 })
+ .then(res => {
+ const records = res?.data?.records ?? res?.records ?? [];
+ list.value = records;
+ });
+};
+
+const onDateChange = (val) => {
+ if (val && val.length === 2) {
+ filters.entryDateStart = val[0];
+ filters.entryDateEnd = val[1];
+ } else {
+ filters.entryDateStart = undefined;
+ filters.entryDateEnd = undefined;
+ }
+};
+
+const onSelectMethod = (e) => {
+ filters.incomeMethod = e.value;
+ incomeMethodLabel.value = e.label;
+ methodPickerShow.value = false;
+};
+
+const methodText = (v) => {
+ const m = paymentMethods.value.find(i=>String(i.value)===String(v));
+ return m?.label || "--";
+};
+const incomeTypeText = (v) => {
+ const m = incomeTypes.value.find(i=>String(i.value)===String(v));
+ return m?.label || null;
+};
+const fmtAmount = (v) => {
+ const n = parseFloat(v || 0);
+ return n.toFixed(2);
+};
+
+const goAdd = () => {
+ uni.navigateTo({ url: "/pages/financialManagement/revenueManagement/edit?type=add" });
+};
+const goEdit = (row) => {
+ uni.navigateTo({ url: `/pages/financialManagement/revenueManagement/edit?type=edit&id=${row.id}` });
+};
+const confirmDelete = (row) => {
+ uni.showModal({
+ title: "鎻愮ず",
+ content: "纭鍒犻櫎璇ヨ褰曪紵",
+ success: async (r) => {
+ if (r.confirm) {
+ const ids = Array.isArray(row) ? row.map(i=>i.id) : [row.id];
+ const res = await delAccountIncome(ids);
+ if (res?.code === 200) getList();
+ }
+ },
+ });
+};
+
+const onIncomeTypeConfirm = (e) => {
+ const item = incomeTypes.value[e.value[0]];
+ if (item) form.incomeType = item.value;
+};
+const onMethodConfirm = (e) => {
+ const item = paymentMethods.value[e.value[0]];
+ if (item) form.incomeMethod = item.value;
+};
+
+const goBack = () => {
+ uni.navigateBack();
+};
+
+syncDict();
+onShow(() => {
+ getList();
+});
+</script>
+
+<style scoped lang="scss">
+@import "@/styles/sales-common.scss";
+.actions { margin-top: 8px; }
+</style>
diff --git a/src/pages/productionManagement/productionDispatching/components/formDia.vue b/src/pages/productionManagement/productionDispatching/components/formDia.vue
index a60f751..72227ac 100644
--- a/src/pages/productionManagement/productionDispatching/components/formDia.vue
+++ b/src/pages/productionManagement/productionDispatching/components/formDia.vue
@@ -94,7 +94,7 @@
import {getStaffJoinInfo, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js";
import {userListNoPageByTenantId} from "@/api/system/user.js";
import {productionDispatch} from "@/api/productionManagement/productionOrder.js";
-import useUserStore from "@/store/modules/user.js";
+import useUserStore from "@/store/modules/user";
import dayjs from "dayjs";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
diff --git a/src/pages/works.vue b/src/pages/works.vue
index 41aad95..76ffe24 100644
--- a/src/pages/works.vue
+++ b/src/pages/works.vue
@@ -301,10 +301,6 @@
icon: "/static/images/icon/caigouguanli.svg",
label: "閲囪喘閫�璐�",
},
- {
- icon: "/static/images/icon/gongchuguanli.svg",
- label: "渚涘簲鍟嗘。妗�",
- },
]);
// 璐㈠姟绠$悊鍔熻兘鏁版嵁
@@ -341,14 +337,38 @@
icon: "/static/images/icon/fukuanliushui.svg",
label: "浠樻娴佹按",
},
+ {
+ icon: "/static/images/icon/huikuandengji.svg",
+ label: "鏀跺叆绠$悊",
+ },
+ {
+ icon: "/static/images/icon/fukuandengji.svg",
+ label: "鏀嚭绠$悊",
+ },
+ {
+ icon: "/static/images/icon/huikuanliushui.svg",
+ label: "鍊熸绠$悊",
+ },
]);
// 妗f绠$悊鍔熻兘鏁版嵁
const archiveManagementItems = reactive([
+ {
+ icon: "/static/images/icon/gongchuguanli.svg",
+ label: "渚涘簲鍟嗘。妗�",
+ },
]);
// 鍞悗鏈嶅姟鍔熻兘鏁版嵁
const afterSalesServiceItems = reactive([
+ {
+ icon: "/static/images/icon/xiaoshoutaizhang.svg",
+ label: "鍙嶉鐧昏",
+ },
+ {
+ icon: "/static/images/icon/caigouguanli.svg",
+ label: "鍞悗澶勭悊",
+ },
]);
const humanResourcesItems = reactive([
@@ -550,6 +570,21 @@
case "浠樻娴佹按":
uni.navigateTo({
url: "/pages/procurementManagement/receiptPaymentHistory/index",
+ });
+ break;
+ case "鏀跺叆绠$悊":
+ uni.navigateTo({
+ url: "/pages/financialManagement/revenueManagement/index",
+ });
+ break;
+ case "鏀嚭绠$悊":
+ uni.navigateTo({
+ url: "/pages/financialManagement/expenseManagement/index",
+ });
+ break;
+ case "鍊熸绠$悊":
+ uni.navigateTo({
+ url: "/pages/financialManagement/loanManagement/index",
});
break;
case "渚涘簲鍟嗗線鏉�":
@@ -810,6 +845,16 @@
url: "/pages/qualityManagement/finalInspection/index",
});
break;
+ case "鍙嶉鐧昏":
+ uni.navigateTo({
+ url: "/pages/customerService/feedbackRegistration/index",
+ });
+ break;
+ case "鍞悗澶勭悊":
+ uni.navigateTo({
+ url: "/pages/customerService/afterSalesHandling/index",
+ });
+ break;
default:
uni.showToast({
title: `鐐瑰嚮浜�${item.label}`,
@@ -998,6 +1043,7 @@
// 瀹氫箟鑿滃崟閰嶇疆鏄犲皠
const menuMapping = {
collaboration: { target: collaborationItems, specialMapping: { "瑙勭珷鍒跺害": "瑙勭珷鍒跺害绠$悊" } },
+ archiveManagement: { target: archiveManagementItems, specialMapping: { "渚涘簲鍟嗘。妗�": "渚涘簲鍟嗙鐞�" } },
};
console.log(allowedMenuTitles)
// 閫氱敤杩囨护鍑芥暟
@@ -1016,8 +1062,7 @@
filterArray(marketingItems);
filterArray(purchaseItems);
filterArray(financeManagementItems);
- filterArray(archiveManagementItems);
- filterArray(afterSalesServiceItems);
+ filterArray(archiveManagementItems, menuMapping.archiveManagement.specialMapping);
filterArray(collaborationItems, menuMapping.collaboration.specialMapping);
filterArray(safetyItems);
filterArray(humanResourcesItems);
@@ -1030,8 +1075,8 @@
const hasMarketingItems = computed(() => marketingItems.length > 0);
const hasPurchaseItems = computed(() => purchaseItems.length > 0);
const hasFinanceManagementItems = computed(() => financeManagementItems.length > 0);
- const hasArchiveManagementItems = computed(() => true);
- const hasAfterSalesServiceItems = computed(() => true);
+ const hasArchiveManagementItems = computed(() => archiveManagementItems.length > 0);
+ const hasAfterSalesServiceItems = computed(() => afterSalesServiceItems.length > 0);
const hasCollaborationItems = computed(() => collaborationItems.length > 0);
const hasSafetyItems = computed(() => safetyItems.length > 0);
const hasQualityItems = computed(() => qualityItems.length > 0);
--
Gitblit v1.9.3