From ea34f049755d89386a342f0eceb58a6bc4b23b91 Mon Sep 17 00:00:00 2001
From: ZN <zhang_12370@163.com>
Date: 星期一, 23 三月 2026 16:55:31 +0800
Subject: [PATCH] feat: 新增采购退货单与供应商管理功能
---
src/pages.json | 44 +
src/pages/basicData/supplierManage/index.vue | 197 ++++++
src/pages/procurementManagement/purchaseReturnOrder/add.vue | 758 ++++++++++++++++++++++++
src/pages/procurementManagement/purchaseReturnOrder/productList.vue | 163 +++++
src/pages/works.vue | 23
.vscode/settings.json | 15
src/pages/indexItem.vue | 4
src/pages/procurementManagement/purchaseReturnOrder/view.vue | 202 ++++++
src/pages/basicData/supplierManage/edit.vue | 216 ++++++
src/pages/procurementManagement/purchaseReturnOrder/index.vue | 185 +++++
src/api/procurementManagement/purchaseReturnOrder.js | 31 +
11 files changed, 1,834 insertions(+), 4 deletions(-)
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..17ea60e
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,15 @@
+{
+ "i18n-ally.localesPaths": [
+ "src/pages_template/common/locales",
+ "src/uni_modules/uni-table/i18n",
+ "src/uni_modules/uni-countdown/components/uni-countdown/i18n",
+ "src/uni_modules/uni-calendar/components/uni-calendar/i18n",
+ "src/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n",
+ "src/uni_modules/uni-fav/components/uni-fav/i18n",
+ "src/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n",
+ "src/uni_modules/uni-load-more/components/uni-load-more/i18n",
+ "src/uni_modules/uni-popup/components/uni-popup/i18n",
+ "src/uni_modules/uni-pagination/components/uni-pagination/i18n",
+ "src/uni_modules/uni-search-bar/components/uni-search-bar/i18n"
+ ]
+}
\ No newline at end of file
diff --git a/src/api/procurementManagement/purchaseReturnOrder.js b/src/api/procurementManagement/purchaseReturnOrder.js
new file mode 100644
index 0000000..80d171f
--- /dev/null
+++ b/src/api/procurementManagement/purchaseReturnOrder.js
@@ -0,0 +1,31 @@
+import request from "@/utils/request";
+
+export function findPurchaseReturnOrderListPage(query) {
+ return request({
+ url: "/purchaseReturnOrders/listPage",
+ method: "get",
+ params: query,
+ });
+}
+
+export function createPurchaseReturnOrder(data) {
+ return request({
+ url: "/purchaseReturnOrders/add",
+ method: "post",
+ data,
+ });
+}
+
+export function getPurchaseReturnOrderDetail(id) {
+ return request({
+ url: "/purchaseReturnOrders/selectById/" + id,
+ method: "get",
+ });
+}
+
+export function deletePurchaseReturnOrder(id) {
+ return request({
+ url: "/purchaseReturnOrders/deleteById/" + id,
+ method: "post",
+ });
+}
diff --git a/src/pages.json b/src/pages.json
index 044361a..5d7da6d 100644
--- a/src/pages.json
+++ b/src/pages.json
@@ -262,6 +262,48 @@
}
},
{
+ "path": "pages/procurementManagement/purchaseReturnOrder/index",
+ "style": {
+ "navigationBarTitleText": "閲囪喘閫�璐у崟",
+ "navigationStyle": "custom"
+ }
+ },
+ {
+ "path": "pages/procurementManagement/purchaseReturnOrder/add",
+ "style": {
+ "navigationBarTitleText": "鏂板閲囪喘閫�璐�",
+ "navigationStyle": "custom"
+ }
+ },
+ {
+ "path": "pages/procurementManagement/purchaseReturnOrder/view",
+ "style": {
+ "navigationBarTitleText": "閲囪喘閫�璐ц鎯�",
+ "navigationStyle": "custom"
+ }
+ },
+ {
+ "path": "pages/procurementManagement/purchaseReturnOrder/productList",
+ "style": {
+ "navigationBarTitleText": "閫夋嫨浜у搧",
+ "navigationStyle": "custom"
+ }
+ },
+ {
+ "path": "pages/basicData/supplierManage/index",
+ "style": {
+ "navigationBarTitleText": "渚涘簲鍟嗙鐞�",
+ "navigationStyle": "custom"
+ }
+ },
+ {
+ "path": "pages/basicData/supplierManage/edit",
+ "style": {
+ "navigationBarTitleText": "渚涘簲鍟嗕俊鎭�",
+ "navigationStyle": "custom"
+ }
+ },
+ {
"path": "pages/cooperativeOffice/collaborativeApproval/index1",
"style": {
"navigationBarTitleText": "鍏嚭绠$悊",
@@ -1225,4 +1267,4 @@
"navigationBarTitleText": "RuoYi",
"navigationBarBackgroundColor": "#FFFFFF"
}
-}
\ No newline at end of file
+}
diff --git a/src/pages/basicData/supplierManage/edit.vue b/src/pages/basicData/supplierManage/edit.vue
new file mode 100644
index 0000000..c890e88
--- /dev/null
+++ b/src/pages/basicData/supplierManage/edit.vue
@@ -0,0 +1,216 @@
+<template>
+ <view class="account-detail">
+ <PageHeader :title="pageTitle" @back="goBack" />
+ <up-form ref="formRef" :model="form" :rules="rules" label-width="120">
+ <u-cell-group title="渚涘簲鍟嗕俊鎭�" class="form-section">
+ <up-form-item label="渚涘簲鍟嗗悕绉�" prop="supplierName" required>
+ <up-input v-model="form.supplierName" placeholder="璇疯緭鍏�" clearable />
+ </up-form-item>
+ <up-form-item label="绾崇◣浜鸿瘑鍒彿" prop="taxpayerIdentificationNum" required>
+ <up-input v-model="form.taxpayerIdentificationNum" placeholder="璇疯緭鍏�" clearable />
+ </up-form-item>
+ <up-form-item label="鍏徃鍦板潃" prop="companyAddress" required>
+ <up-input v-model="form.companyAddress" placeholder="璇疯緭鍏�" clearable />
+ </up-form-item>
+ <up-form-item label="鍏徃鐢佃瘽" prop="companyPhone" required>
+ <up-input v-model="form.companyPhone" placeholder="璇疯緭鍏�" clearable />
+ </up-form-item>
+ <up-form-item label="寮�鎴疯" prop="bankAccountName" required>
+ <up-input v-model="form.bankAccountName" placeholder="璇疯緭鍏�" clearable />
+ </up-form-item>
+ <up-form-item label="璐﹀彿" prop="bankAccountNum" required>
+ <up-input v-model="form.bankAccountNum" placeholder="璇疯緭鍏�" clearable />
+ </up-form-item>
+ <up-form-item label="鑱旂郴浜�" prop="contactUserName">
+ <up-input v-model="form.contactUserName" placeholder="璇疯緭鍏�" clearable />
+ </up-form-item>
+ <up-form-item label="鑱旂郴鐢佃瘽" prop="contactUserPhone">
+ <up-input v-model="form.contactUserPhone" placeholder="璇疯緭鍏�" clearable />
+ </up-form-item>
+ <up-form-item label="渚涘簲鍟嗙被鍨�" prop="supplierType" required>
+ <up-input
+ v-model="supplierTypeText"
+ placeholder="璇烽�夋嫨"
+ readonly
+ @click="showSupplierTypeSheet = true"
+ />
+ <template #right>
+ <up-icon name="arrow-right" @click="showSupplierTypeSheet = true"></up-icon>
+ </template>
+ </up-form-item>
+ <up-form-item label="鏄惁鐧藉悕鍗�" prop="isWhite" required>
+ <up-input v-model="isWhiteText" placeholder="璇烽�夋嫨" readonly @click="showIsWhiteSheet = true" />
+ <template #right>
+ <up-icon name="arrow-right" @click="showIsWhiteSheet = true"></up-icon>
+ </template>
+ </up-form-item>
+ <up-form-item label="缁存姢浜�" prop="maintainUserName">
+ <up-input v-model="form.maintainUserName" disabled placeholder="鑷姩濉厖" />
+ </up-form-item>
+ <up-form-item label="缁存姢鏃堕棿" prop="maintainTime">
+ <up-input v-model="form.maintainTime" disabled placeholder="鑷姩濉厖" />
+ </up-form-item>
+ </u-cell-group>
+ </up-form>
+ <FooterButtons :loading="loading" confirmText="淇濆瓨" @cancel="goBack" @confirm="handleSubmit" />
+
+ <up-action-sheet
+ :show="showSupplierTypeSheet"
+ title="閫夋嫨渚涘簲鍟嗙被鍨�"
+ :actions="supplierTypeActions"
+ @select="onSelectSupplierType"
+ @close="showSupplierTypeSheet = false"
+ />
+ <up-action-sheet
+ :show="showIsWhiteSheet"
+ title="閫夋嫨鐧藉悕鍗�"
+ :actions="isWhiteActions"
+ @select="onSelectIsWhite"
+ @close="showIsWhiteSheet = false"
+ />
+ </view>
+</template>
+
+<script setup>
+ import { computed, onMounted, ref } from "vue";
+ import { onLoad } from "@dcloudio/uni-app";
+ import FooterButtons from "@/components/FooterButtons.vue";
+ import useUserStore from "@/store/modules/user";
+ import { formatDateToYMD } from "@/utils/ruoyi";
+ import { addSupplier, getSupplier, updateSupplier } from "@/api/basicData/supplierManageFile";
+
+ const userStore = useUserStore();
+ const formRef = ref();
+ const loading = ref(false);
+ const supplierId = ref(undefined);
+
+ const form = ref({
+ supplierName: "",
+ taxpayerIdentificationNum: "",
+ companyAddress: "",
+ companyPhone: "",
+ bankAccountName: "",
+ bankAccountNum: "",
+ contactUserName: "",
+ contactUserPhone: "",
+ maintainUserId: "",
+ maintainUserName: "",
+ maintainTime: "",
+ supplierType: "",
+ isWhite: 0,
+ });
+
+ const rules = {
+ supplierName: [{ required: true, message: "璇疯緭鍏ヤ緵搴斿晢鍚嶇О", trigger: "blur" }],
+ taxpayerIdentificationNum: [{ required: true, message: "璇疯緭鍏ョ撼绋庝汉璇嗗埆鍙�", trigger: "blur" }],
+ companyAddress: [{ required: true, message: "璇疯緭鍏ュ叕鍙稿湴鍧�", trigger: "blur" }],
+ companyPhone: [{ required: true, message: "璇疯緭鍏ュ叕鍙哥數璇�", trigger: "blur" }],
+ bankAccountName: [{ required: true, message: "璇疯緭鍏ュ紑鎴疯", trigger: "blur" }],
+ bankAccountNum: [{ required: true, message: "璇疯緭鍏ヨ处鍙�", trigger: "blur" }],
+ supplierType: [{ required: true, message: "璇烽�夋嫨渚涘簲鍟嗙被鍨�", trigger: "change" }],
+ isWhite: [{ required: true, message: "璇烽�夋嫨鐧藉悕鍗�", trigger: "change" }],
+ };
+
+ const pageTitle = computed(() => {
+ return supplierId.value ? "缂栬緫渚涘簲鍟�" : "鏂板渚涘簲鍟�";
+ });
+
+ const supplierTypeActions = [
+ { name: "鐢�", value: "鐢�" },
+ { name: "涔�", value: "涔�" },
+ { name: "涓�", value: "涓�" },
+ { name: "涓�", value: "涓�" },
+ ];
+ const isWhiteActions = [
+ { name: "鏄�", value: 0 },
+ { name: "鍚�", value: 1 },
+ ];
+
+ const showSupplierTypeSheet = ref(false);
+ const showIsWhiteSheet = ref(false);
+
+ const supplierTypeText = computed(() => form.value.supplierType || "");
+ const isWhiteText = computed(() => {
+ return String(form.value.isWhite) === "0" ? "鏄�" : "鍚�";
+ });
+
+ const onSelectSupplierType = action => {
+ form.value.supplierType = action.value;
+ showSupplierTypeSheet.value = false;
+ };
+
+ const onSelectIsWhite = action => {
+ form.value.isWhite = action.value;
+ showIsWhiteSheet.value = false;
+ };
+
+ const initForAdd = () => {
+ form.value.maintainUserId = userStore.id;
+ form.value.maintainUserName = userStore.nickName;
+ form.value.maintainTime = formatDateToYMD(Date.now());
+ form.value.isWhite = 0;
+ };
+
+ const loadDetail = () => {
+ if (!supplierId.value) return;
+ uni.showLoading({ title: "鍔犺浇涓�...", mask: true });
+ getSupplier(supplierId.value)
+ .then(res => {
+ form.value = { ...form.value, ...(res.data || {}) };
+ })
+ .catch(() => {
+ uni.showToast({ title: "鑾峰彇璇︽儏澶辫触", icon: "error" });
+ })
+ .finally(() => {
+ uni.hideLoading();
+ });
+ };
+
+ const goBack = () => {
+ uni.navigateBack();
+ };
+
+ const handleSubmit = async () => {
+ const valid = await formRef.value.validate().catch(() => false);
+ if (!valid) return;
+ loading.value = true;
+ const action = supplierId.value ? updateSupplier : addSupplier;
+ action({ ...form.value, id: supplierId.value })
+ .then(() => {
+ uni.showToast({ title: "淇濆瓨鎴愬姛", icon: "success" });
+ uni.navigateBack();
+ })
+ .catch(() => {
+ uni.showToast({ title: "淇濆瓨澶辫触", icon: "error" });
+ })
+ .finally(() => {
+ loading.value = false;
+ });
+ };
+
+ onMounted(() => {
+ userStore.getInfo();
+ initForAdd();
+ });
+
+ onLoad(options => {
+ if (options?.id) supplierId.value = options.id;
+ if (supplierId.value) loadDetail();
+ });
+</script>
+
+<style scoped lang="scss">
+ @import "@/styles/procurement-common.scss";
+
+ .account-detail {
+ min-height: 100vh;
+ background: #f8f9fa;
+ padding-bottom: 90px;
+ }
+
+ .form-section {
+ margin: 12px;
+ border-radius: 12px;
+ overflow: hidden;
+ }
+</style>
diff --git a/src/pages/basicData/supplierManage/index.vue b/src/pages/basicData/supplierManage/index.vue
new file mode 100644
index 0000000..a7c01a0
--- /dev/null
+++ b/src/pages/basicData/supplierManage/index.vue
@@ -0,0 +1,197 @@
+<template>
+ <view class="sales-account">
+ <PageHeader title="渚涘簲鍟嗙鐞�" @back="goBack">
+ <template #right>
+ <up-button
+ type="primary"
+ size="small"
+ text="鏂板"
+ :customStyle="{ marginRight: '12px' }"
+ @click="goAdd"
+ />
+ </template>
+ </PageHeader>
+ <view class="search-section">
+ <view class="search-bar">
+ <view class="search-input">
+ <up-input
+ class="search-text"
+ placeholder="璇疯緭鍏ヤ緵搴斿晢鍚嶇О"
+ v-model="supplierName"
+ @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="tabs-section">
+ <up-tabs
+ v-model="tabValue"
+ :list="tabList"
+ itemStyle="width: 50%;height: 80rpx;"
+ @change="onTabChange"
+ >
+ </up-tabs>
+ </view>
+ <view class="ledger-list" v-if="list.length > 0">
+ <view v-for="item in list" :key="item.id">
+ <view class="ledger-item">
+ <view class="item-header">
+ <view class="item-left">
+ <view class="document-icon">
+ <up-icon name="file-text" size="16" color="#ffffff"></up-icon>
+ </view>
+ <text class="item-id">{{ item.supplierName || "-" }}</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.supplierType || "-" }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">绾崇◣浜鸿瘑鍒彿</text>
+ <text class="detail-value">{{ item.taxpayerIdentificationNum || "-" }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">鍏徃鐢佃瘽</text>
+ <text class="detail-value">{{ item.companyPhone || "-" }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">鑱旂郴浜�</text>
+ <text class="detail-value">{{ item.contactUserName || "-" }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">鑱旂郴鐢佃瘽</text>
+ <text class="detail-value">{{ item.contactUserPhone || "-" }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">缁存姢浜�</text>
+ <text class="detail-value">{{ item.maintainUserName || "-" }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">缁存姢鏃堕棿</text>
+ <text class="detail-value">{{ item.maintainTime || "-" }}</text>
+ </view>
+ </view>
+ <view class="action-buttons">
+ <u-button size="small" class="action-btn" @click="goEdit(item)">缂栬緫</u-button>
+ <u-button
+ type="error"
+ size="small"
+ class="action-btn"
+ @click="handleDelete(item)"
+ >
+ 鍒犻櫎
+ </u-button>
+ </view>
+ </view>
+ </view>
+ </view>
+ <view v-else class="no-data">
+ <text>鏆傛棤渚涘簲鍟嗘暟鎹�</text>
+ </view>
+ </view>
+</template>
+
+<script setup>
+ import { reactive, ref } from "vue";
+ import { onShow } from "@dcloudio/uni-app";
+ import useUserStore from "@/store/modules/user";
+ import { delSupplier, listSupplier } from "@/api/basicData/supplierManageFile";
+
+ const userStore = useUserStore();
+ const supplierName = ref("");
+ const list = ref([]);
+
+ const tabList = reactive([
+ { name: "姝e父渚涘簲鍟�", value: 0 },
+ { name: "榛戝悕鍗�", value: 1 },
+ ]);
+ const tabValue = ref(0);
+
+ const page = {
+ current: -1,
+ size: -1,
+ };
+
+ const goBack = () => {
+ uni.navigateBack();
+ };
+
+ const goAdd = () => {
+ uni.navigateTo({ url: "/pages/basicData/supplierManage/edit" });
+ };
+
+ const goEdit = item => {
+ uni.navigateTo({ url: `/pages/basicData/supplierManage/edit?id=${item.id}` });
+ };
+
+ const onTabChange = val => {
+ tabValue.value = val.value;
+ getList();
+ };
+
+ const getList = () => {
+ uni.showLoading({ title: "鍔犺浇涓�...", mask: true });
+ listSupplier({
+ ...page,
+ supplierName: supplierName.value,
+ isWhite: tabValue.value,
+ })
+ .then(res => {
+ list.value = res?.data?.records || [];
+ })
+ .catch(() => {
+ uni.showToast({ title: "鏌ヨ澶辫触", icon: "error" });
+ })
+ .finally(() => {
+ uni.hideLoading();
+ });
+ };
+
+ const handleDelete = item => {
+ if (!item?.id) return;
+ if (item.maintainUserName && item.maintainUserName !== userStore.nickName) {
+ uni.showToast({ title: "涓嶅彲鍒犻櫎浠栦汉缁存姢鐨勬暟鎹�", icon: "none" });
+ return;
+ }
+ uni.showModal({
+ title: "鍒犻櫎鎻愮ず",
+ content: "纭畾瑕佸垹闄ゅ悧锛熷垹闄ゅ悗鏃犳硶鎭㈠",
+ success: res => {
+ if (!res.confirm) return;
+ uni.showLoading({ title: "鍒犻櫎涓�...", mask: true });
+ delSupplier([item.id])
+ .then(() => {
+ uni.showToast({ title: "鍒犻櫎鎴愬姛", icon: "success" });
+ getList();
+ })
+ .catch(() => {
+ uni.showToast({ title: "鍒犻櫎澶辫触", icon: "error" });
+ })
+ .finally(() => {
+ uni.hideLoading();
+ });
+ },
+ });
+ };
+
+ onShow(() => {
+ userStore.getInfo();
+ getList();
+ });
+</script>
+
+<style scoped lang="scss">
+ @import "@/styles/procurement-common.scss";
+
+ .tabs-section {
+ background: #fff;
+ padding: 0 12px 8px 12px;
+ }
+</style>
diff --git a/src/pages/indexItem.vue b/src/pages/indexItem.vue
index 21b4958..61f6c15 100644
--- a/src/pages/indexItem.vue
+++ b/src/pages/indexItem.vue
@@ -49,6 +49,8 @@
"浠樻鐧昏": "/pages/procurementManagement/paymentEntry/index",
"浠樻娴佹按": "/pages/procurementManagement/receiptPaymentHistory/index",
"渚涘簲鍟嗗線鏉�": "/pages/procurementManagement/paymentLedger/index",
+ "閲囪喘閫�璐у崟": "/pages/procurementManagement/purchaseReturnOrder/index",
+ "渚涘簲鍟嗙鐞�": "/pages/basicData/supplierManage/index",
"鍏嚭绠$悊": "/pages/cooperativeOffice/collaborativeApproval/index1",
"璇峰亣绠$悊": "/pages/cooperativeOffice/collaborativeApproval/index2",
"鍑哄樊绠$悊": "/pages/cooperativeOffice/collaborativeApproval/index3",
@@ -831,4 +833,4 @@
box-shadow: 0 0.375rem 1.25rem rgba(0, 0, 0, 0.4);
}
}
-</style>
\ No newline at end of file
+</style>
diff --git a/src/pages/procurementManagement/purchaseReturnOrder/add.vue b/src/pages/procurementManagement/purchaseReturnOrder/add.vue
new file mode 100644
index 0000000..fd5591e
--- /dev/null
+++ b/src/pages/procurementManagement/purchaseReturnOrder/add.vue
@@ -0,0 +1,758 @@
+<template>
+ <view class="account-detail">
+ <PageHeader title="鏂板閲囪喘閫�璐�" @back="goBack" />
+ <up-form ref="formRef" :model="form" :rules="rules" label-width="120">
+ <u-cell-group title="鍩烘湰淇℃伅" class="form-section">
+ <up-form-item label="閫�鏂欏崟鍙�" prop="no">
+ <up-input
+ v-model="form.no"
+ :disabled="form.isDefaultNo"
+ :placeholder="form.isDefaultNo ? '浣跨敤绯荤粺缂栧彿' : '璇疯緭鍏ラ��鏂欏崟鍙�'"
+ clearable
+ />
+ <template #right>
+ <up-switch v-model="form.isDefaultNo" @change="onDefaultNoChange" />
+ </template>
+ </up-form-item>
+ <up-form-item label="閫�璐ф柟寮�" prop="returnType" required>
+ <up-input
+ v-model="returnTypeText"
+ placeholder="璇烽�夋嫨"
+ readonly
+ @click="showReturnTypeSheet = true"
+ />
+ <template #right>
+ <up-icon name="arrow-right" @click="showReturnTypeSheet = true"></up-icon>
+ </template>
+ </up-form-item>
+ <up-form-item label="渚涘簲鍟�" prop="supplierId" required>
+ <up-input
+ v-model="supplierText"
+ placeholder="璇烽�夋嫨"
+ readonly
+ @click="showSupplierSheet = true"
+ />
+ <template #right>
+ <up-icon name="arrow-right" @click="showSupplierSheet = true"></up-icon>
+ </template>
+ </up-form-item>
+ <up-form-item label="椤圭洰闃舵" prop="projectPhase">
+ <up-input
+ v-model="projectPhaseText"
+ placeholder="璇烽�夋嫨"
+ readonly
+ @click="showProjectPhaseSheet = true"
+ />
+ <template #right>
+ <up-icon name="arrow-right" @click="showProjectPhaseSheet = true"></up-icon>
+ </template>
+ </up-form-item>
+ <up-form-item label="鍒朵綔鏃ユ湡" prop="preparedAt" required>
+ <up-input
+ v-model="form.preparedAt"
+ placeholder="璇烽�夋嫨"
+ readonly
+ @click="showPreparedAtPicker = true"
+ />
+ <template #right>
+ <up-icon name="arrow-right" @click="showPreparedAtPicker = true"></up-icon>
+ </template>
+ </up-form-item>
+ <up-form-item label="鍒跺崟浜�" prop="preparedUserId" required>
+ <up-input
+ v-model="preparedUserText"
+ placeholder="璇烽�夋嫨"
+ readonly
+ @click="showPreparedUserSheet = true"
+ />
+ <template #right>
+ <up-icon name="arrow-right" @click="showPreparedUserSheet = true"></up-icon>
+ </template>
+ </up-form-item>
+ <up-form-item label="閫�鏂欎汉" prop="returnUserId" required>
+ <up-input
+ v-model="returnUserText"
+ placeholder="璇烽�夋嫨"
+ readonly
+ @click="showReturnUserSheet = true"
+ />
+ <template #right>
+ <up-icon name="arrow-right" @click="showReturnUserSheet = true"></up-icon>
+ </template>
+ </up-form-item>
+ <up-form-item label="閲囪喘鍚堝悓鍙�" prop="purchaseLedgerId" required>
+ <up-input
+ v-model="purchaseContractText"
+ placeholder="璇烽�夋嫨"
+ readonly
+ @click="showPurchaseLedgerSheet = true"
+ />
+ <template #right>
+ <up-icon name="arrow-right" @click="showPurchaseLedgerSheet = true"></up-icon>
+ </template>
+ </up-form-item>
+ <up-form-item label="澶囨敞" prop="remark">
+ <up-textarea v-model="form.remark" placeholder="璇疯緭鍏�" auto-height />
+ </up-form-item>
+ </u-cell-group>
+
+ <u-cell-group title="浜у搧鍒楄〃" class="form-section">
+ <view class="product-actions">
+ <up-button
+ type="primary"
+ size="small"
+ text="閫夋嫨浜у搧"
+ :disabled="!form.purchaseLedgerId"
+ @click="goSelectProducts"
+ />
+ <view class="amount-summary">
+ <text class="amount-text">鍚堣锛歿{ formatAmount(baseAmount) }}</text>
+ </view>
+ </view>
+ <view v-if="form.purchaseReturnOrderProductsDtos.length === 0" class="empty-products">
+ <text>鏆傛棤浜у搧锛岃鍏堥�夋嫨浜у搧</text>
+ </view>
+ <view v-else class="product-list">
+ <view
+ v-for="(item, index) in form.purchaseReturnOrderProductsDtos"
+ :key="item.salesLedgerProductId || item.id || index"
+ class="product-card"
+ >
+ <view class="product-header">
+ <view class="product-title">
+ <view class="document-icon">
+ <up-icon name="file-text" size="16" color="#ffffff"></up-icon>
+ </view>
+ <text class="product-name">浜у搧 {{ index + 1 }}</text>
+ </view>
+ <up-icon name="trash" size="18" color="#ee0a24" @click="removeProduct(index)" />
+ </view>
+ <up-divider></up-divider>
+ <view class="product-body">
+ <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.quantity ?? "-" }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">鍚◣鍗曚环(鍏�)</text>
+ <text class="detail-value">{{ formatAmount(item.taxInclusiveUnitPrice) }}</text>
+ </view>
+ <view class="qty-row">
+ <text class="qty-label">閫�璐ф暟閲�</text>
+ <up-number-box
+ v-model="item.returnQuantity"
+ :min="0"
+ :max="getReturnQtyMax(item)"
+ :step="1"
+ @change="syncRowTotal(item)"
+ />
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">閫�璐ф�讳环(鍏�)</text>
+ <text class="detail-value highlight">{{ formatAmount(item.taxInclusiveTotalPrice) }}</text>
+ </view>
+ </view>
+ </view>
+ </view>
+ </u-cell-group>
+
+ <u-cell-group title="璐圭敤淇℃伅" class="form-section">
+ <up-form-item label="鏁村崟鎶樻墸棰�" prop="totalDiscountAmount">
+ <up-input
+ v-model="form.totalDiscountAmount"
+ type="number"
+ placeholder="璇疯緭鍏�"
+ @blur="onDiscountAmountBlur"
+ clearable
+ />
+ </up-form-item>
+ <up-form-item label="鏁村崟鎶樻墸鐜�(%)" prop="totalDiscountRate">
+ <up-input
+ v-model="form.totalDiscountRate"
+ type="number"
+ placeholder="璇疯緭鍏�"
+ @blur="onDiscountRateBlur"
+ clearable
+ />
+ </up-form-item>
+ <up-form-item label="鎴愪氦閲戦" prop="totalAmount" required>
+ <up-input v-model="form.totalAmount" disabled placeholder="鑷姩璁$畻" />
+ </up-form-item>
+ <up-form-item label="鏀舵鏂瑰紡" prop="incomeType" required>
+ <up-input
+ v-model="incomeTypeText"
+ placeholder="璇烽�夋嫨"
+ readonly
+ @click="showIncomeTypeSheet = true"
+ />
+ <template #right>
+ <up-icon name="arrow-right" @click="showIncomeTypeSheet = true"></up-icon>
+ </template>
+ </up-form-item>
+ </u-cell-group>
+ </up-form>
+
+ <FooterButtons :loading="loading" confirmText="鎻愪氦" @cancel="goBack" @confirm="handleSubmit" />
+
+ <up-action-sheet
+ :show="showReturnTypeSheet"
+ title="閫夋嫨閫�璐ф柟寮�"
+ :actions="returnTypeActions"
+ @select="onSelectReturnType"
+ @close="showReturnTypeSheet = false"
+ />
+ <up-action-sheet
+ :show="showProjectPhaseSheet"
+ title="閫夋嫨椤圭洰闃舵"
+ :actions="projectPhaseActions"
+ @select="onSelectProjectPhase"
+ @close="showProjectPhaseSheet = false"
+ />
+ <up-action-sheet
+ :show="showSupplierSheet"
+ title="閫夋嫨渚涘簲鍟�"
+ :actions="supplierActions"
+ @select="onSelectSupplier"
+ @close="showSupplierSheet = false"
+ />
+ <up-action-sheet
+ :show="showPreparedUserSheet"
+ title="閫夋嫨鍒跺崟浜�"
+ :actions="userActions"
+ @select="onSelectPreparedUser"
+ @close="showPreparedUserSheet = false"
+ />
+ <up-action-sheet
+ :show="showReturnUserSheet"
+ title="閫夋嫨閫�鏂欎汉"
+ :actions="userActions"
+ @select="onSelectReturnUser"
+ @close="showReturnUserSheet = false"
+ />
+ <up-action-sheet
+ :show="showPurchaseLedgerSheet"
+ title="閫夋嫨閲囪喘鍚堝悓鍙�"
+ :actions="purchaseLedgerActions"
+ @select="onSelectPurchaseLedger"
+ @close="showPurchaseLedgerSheet = false"
+ />
+ <up-action-sheet
+ :show="showIncomeTypeSheet"
+ title="閫夋嫨鏀舵鏂瑰紡"
+ :actions="incomeTypeActions"
+ @select="onSelectIncomeType"
+ @close="showIncomeTypeSheet = false"
+ />
+ <up-datetime-picker
+ :show="showPreparedAtPicker"
+ v-model="preparedAtPickerValue"
+ mode="date"
+ @confirm="onPreparedAtConfirm"
+ @cancel="showPreparedAtPicker = false"
+ />
+ </view>
+</template>
+
+<script setup>
+ import { computed, onMounted, ref, watch } from "vue";
+ import { onShow } from "@dcloudio/uni-app";
+ import FooterButtons from "@/components/FooterButtons.vue";
+ import { formatDateToYMD } from "@/utils/ruoyi";
+ import { createPurchaseReturnOrder } from "@/api/procurementManagement/purchaseReturnOrder";
+ import { getOptions, purchaseListPage, productList } from "@/api/procurementManagement/procurementLedger";
+ import { userListNoPageByTenantId } from "@/api/system/user";
+
+ const formRef = ref();
+ const loading = ref(false);
+
+ const form = ref({
+ no: "",
+ isDefaultNo: true,
+ returnType: 0,
+ supplierId: undefined,
+ supplierName: "",
+ projectPhase: undefined,
+ preparedAt: "",
+ preparedUserId: undefined,
+ preparedUserName: "",
+ returnUserId: undefined,
+ returnUserName: "",
+ purchaseLedgerId: undefined,
+ purchaseContractNumber: "",
+ remark: "",
+ totalDiscountAmount: 0,
+ totalDiscountRate: "",
+ totalAmount: 0,
+ incomeType: undefined,
+ purchaseReturnOrderProductsDtos: [],
+ });
+
+ const rules = {
+ returnType: [{ required: true, message: "璇烽�夋嫨閫�璐ф柟寮�", trigger: "change" }],
+ supplierId: [{ required: true, message: "璇烽�夋嫨渚涘簲鍟�", trigger: "change" }],
+ preparedAt: [{ required: true, message: "璇烽�夋嫨鍒朵綔鏃ユ湡", trigger: "change" }],
+ preparedUserId: [{ required: true, message: "璇烽�夋嫨鍒跺崟浜�", trigger: "change" }],
+ returnUserId: [{ required: true, message: "璇烽�夋嫨閫�鏂欎汉", trigger: "change" }],
+ purchaseLedgerId: [{ required: true, message: "璇烽�夋嫨閲囪喘鍚堝悓鍙�", trigger: "change" }],
+ totalAmount: [{ required: true, message: "鎴愪氦閲戦涓嶈兘涓虹┖", trigger: "change" }],
+ incomeType: [{ required: true, message: "璇烽�夋嫨鏀舵鏂瑰紡", trigger: "change" }],
+ };
+
+ const showReturnTypeSheet = ref(false);
+ const showProjectPhaseSheet = ref(false);
+ const showSupplierSheet = ref(false);
+ const showPreparedUserSheet = ref(false);
+ const showReturnUserSheet = ref(false);
+ const showPurchaseLedgerSheet = ref(false);
+ const showIncomeTypeSheet = ref(false);
+ const showPreparedAtPicker = ref(false);
+ const preparedAtPickerValue = ref(Date.now());
+
+ const supplierOptions = ref([]);
+ const userOptions = ref([]);
+ const purchaseLedgerOptions = ref([]);
+
+ const returnTypeActions = [
+ { name: "閫�璐ч��娆�", value: 0 },
+ { name: "鎷掓敹", value: 1 },
+ ];
+ const projectPhaseActions = [
+ { name: "绔嬮」", value: 0 },
+ { name: "璁捐", value: 1 },
+ { name: "閲囪喘", value: 2 },
+ { name: "鐢熶骇", value: 3 },
+ { name: "鍑鸿揣", value: 4 },
+ ];
+ const incomeTypeActions = [
+ { name: "鐜伴噾", value: "0" },
+ { name: "鏀エ", value: "1" },
+ { name: "閾惰杞处", value: "2" },
+ { name: "鍏朵粬", value: "3" },
+ ];
+
+ const returnTypeText = computed(() => {
+ return returnTypeActions.find(i => String(i.value) === String(form.value.returnType))?.name || "";
+ });
+ const projectPhaseText = computed(() => {
+ return projectPhaseActions.find(i => String(i.value) === String(form.value.projectPhase))?.name || "";
+ });
+ const supplierText = computed(() => {
+ return supplierOptions.value.find(i => String(i.id) === String(form.value.supplierId))?.supplierName || "";
+ });
+ const preparedUserText = computed(() => {
+ return userOptions.value.find(i => String(i.userId) === String(form.value.preparedUserId))?.nickName || "";
+ });
+ const returnUserText = computed(() => {
+ return userOptions.value.find(i => String(i.userId) === String(form.value.returnUserId))?.nickName || "";
+ });
+ const purchaseContractText = computed(() => {
+ return purchaseLedgerOptions.value.find(i => String(i.id) === String(form.value.purchaseLedgerId))?.purchaseContractNumber || "";
+ });
+ const incomeTypeText = computed(() => {
+ return incomeTypeActions.find(i => String(i.value) === String(form.value.incomeType))?.name || "";
+ });
+
+ const supplierActions = computed(() => {
+ return supplierOptions.value.map(i => ({ name: i.supplierName, value: i.id }));
+ });
+ const userActions = computed(() => {
+ return userOptions.value.map(i => ({ name: i.nickName, value: i.userId }));
+ });
+ const purchaseLedgerActions = computed(() => {
+ return purchaseLedgerOptions.value.map(i => ({ name: i.purchaseContractNumber, value: i.id }));
+ });
+
+ const toNumber = val => {
+ const num = Number(val);
+ return Number.isNaN(num) ? 0 : num;
+ };
+
+ const formatAmount = value => {
+ if (value === null || value === undefined || value === "") return "0.00";
+ const num = Number(value);
+ if (Number.isNaN(num)) return "0.00";
+ return num.toFixed(2);
+ };
+
+ const baseAmount = computed(() => {
+ const rows = form.value.purchaseReturnOrderProductsDtos || [];
+ return rows.reduce((sum, item) => sum + toNumber(item.taxInclusiveTotalPrice), 0);
+ });
+
+ const syncTotalAmount = () => {
+ const total = baseAmount.value - toNumber(form.value.totalDiscountAmount);
+ form.value.totalAmount = Number(total.toFixed(2));
+ };
+
+ const getReturnQtyMax = row => {
+ const qty = Number(row?.quantity);
+ if (Number.isNaN(qty) || qty < 0) return 0;
+ return qty;
+ };
+
+ const syncRowTotal = row => {
+ if (!row) return;
+ const qty = toNumber(row.returnQuantity);
+ const unitPrice = toNumber(row.taxInclusiveUnitPrice);
+ row.taxInclusiveTotalPrice = Number((qty * unitPrice).toFixed(2));
+ syncTotalAmount();
+ };
+
+ const removeProduct = index => {
+ form.value.purchaseReturnOrderProductsDtos.splice(index, 1);
+ syncTotalAmount();
+ };
+
+ const resetFeeInfo = () => {
+ form.value.totalDiscountAmount = 0;
+ form.value.totalDiscountRate = "";
+ form.value.totalAmount = 0;
+ form.value.incomeType = undefined;
+ };
+
+ const onDefaultNoChange = checked => {
+ if (checked) form.value.no = "";
+ };
+
+ const onSelectReturnType = action => {
+ form.value.returnType = action.value;
+ showReturnTypeSheet.value = false;
+ };
+ const onSelectProjectPhase = action => {
+ form.value.projectPhase = action.value;
+ showProjectPhaseSheet.value = false;
+ };
+ const onSelectSupplier = action => {
+ form.value.supplierId = action.value;
+ form.value.supplierName = supplierOptions.value.find(i => String(i.id) === String(action.value))?.supplierName || "";
+ form.value.purchaseLedgerId = undefined;
+ form.value.purchaseContractNumber = "";
+ form.value.purchaseReturnOrderProductsDtos = [];
+ resetFeeInfo();
+ showSupplierSheet.value = false;
+ fetchPurchaseLedgerOptions();
+ };
+ const onSelectPreparedUser = action => {
+ form.value.preparedUserId = action.value;
+ form.value.preparedUserName = userOptions.value.find(i => String(i.userId) === String(action.value))?.nickName || "";
+ showPreparedUserSheet.value = false;
+ };
+ const onSelectReturnUser = action => {
+ form.value.returnUserId = action.value;
+ form.value.returnUserName = userOptions.value.find(i => String(i.userId) === String(action.value))?.nickName || "";
+ showReturnUserSheet.value = false;
+ };
+ const onSelectPurchaseLedger = action => {
+ form.value.purchaseLedgerId = action.value;
+ form.value.purchaseContractNumber =
+ purchaseLedgerOptions.value.find(i => String(i.id) === String(action.value))?.purchaseContractNumber || "";
+ form.value.purchaseReturnOrderProductsDtos = [];
+ resetFeeInfo();
+ showPurchaseLedgerSheet.value = false;
+ };
+ const onSelectIncomeType = action => {
+ form.value.incomeType = action.value;
+ showIncomeTypeSheet.value = false;
+ };
+
+ const onPreparedAtConfirm = e => {
+ form.value.preparedAt = formatDateToYMD(e.value);
+ showPreparedAtPicker.value = false;
+ };
+
+ const onDiscountRateBlur = () => {
+ const rate = toNumber(form.value.totalDiscountRate);
+ if (rate < 0 || rate > 100) {
+ uni.showToast({ title: "鎶樻墸鐜囬渶鍦�0-100", icon: "none" });
+ return;
+ }
+ form.value.totalDiscountAmount = Number((baseAmount.value * (rate / 100)).toFixed(2));
+ syncTotalAmount();
+ };
+
+ const onDiscountAmountBlur = () => {
+ const amount = toNumber(form.value.totalDiscountAmount);
+ if (amount < 0) {
+ form.value.totalDiscountAmount = 0;
+ }
+ const base = baseAmount.value;
+ if (base <= 0) {
+ form.value.totalDiscountRate = "";
+ syncTotalAmount();
+ return;
+ }
+ if (toNumber(form.value.totalDiscountAmount) > base) {
+ form.value.totalDiscountAmount = Number(base.toFixed(2));
+ }
+ const rate = (toNumber(form.value.totalDiscountAmount) / base) * 100;
+ form.value.totalDiscountRate = Number(rate.toFixed(2));
+ syncTotalAmount();
+ };
+
+ const goBack = () => {
+ uni.removeStorageSync("purchaseReturnOrderSelectedProducts");
+ uni.navigateBack();
+ };
+
+ const goSelectProducts = () => {
+ if (!form.value.purchaseLedgerId) return;
+ uni.navigateTo({
+ url: `/pages/procurementManagement/purchaseReturnOrder/productList?purchaseLedgerId=${form.value.purchaseLedgerId}`,
+ });
+ };
+
+ const fetchSupplierOptions = () => {
+ getOptions()
+ .then(res => {
+ supplierOptions.value = res.data || [];
+ })
+ .catch(() => {
+ supplierOptions.value = [];
+ });
+ };
+
+ const fetchUserOptions = () => {
+ userListNoPageByTenantId()
+ .then(res => {
+ userOptions.value = res.data || [];
+ })
+ .catch(() => {
+ userOptions.value = [];
+ });
+ };
+
+ const fetchPurchaseLedgerOptions = () => {
+ purchaseLedgerOptions.value = [];
+ if (!form.value.supplierId) return;
+ purchaseListPage({
+ current: -1,
+ size: -1,
+ supplierId: form.value.supplierId,
+ approvalStatus: 3,
+ })
+ .then(res => {
+ purchaseLedgerOptions.value = res?.data?.records || [];
+ })
+ .catch(() => {
+ purchaseLedgerOptions.value = [];
+ });
+ };
+
+ const mergeSelectedProducts = selectedRows => {
+ const existing = new Set((form.value.purchaseReturnOrderProductsDtos || []).map(i => String(i.salesLedgerProductId || i.id)));
+ const toAdd = (selectedRows || [])
+ .filter(i => !existing.has(String(i.id)))
+ .map(i => ({
+ ...i,
+ salesLedgerProductId: i.id,
+ returnQuantity: 0,
+ taxInclusiveTotalPrice: 0,
+ }));
+ form.value.purchaseReturnOrderProductsDtos.push(...toAdd);
+ syncTotalAmount();
+ };
+
+ const loadProductsFromPurchaseLedger = () => {
+ if (!form.value.purchaseLedgerId) return;
+ uni.showLoading({ title: "鍔犺浇浜у搧...", mask: true });
+ productList({ salesLedgerId: form.value.purchaseLedgerId, type: 2 })
+ .then(res => {
+ const rows = res.data || [];
+ mergeSelectedProducts(rows);
+ })
+ .catch(() => {
+ uni.showToast({ title: "鍔犺浇浜у搧澶辫触", icon: "error" });
+ })
+ .finally(() => {
+ uni.hideLoading();
+ });
+ };
+
+ const validateProducts = () => {
+ const rows = form.value.purchaseReturnOrderProductsDtos || [];
+ if (rows.length === 0) {
+ uni.showToast({ title: "璇峰厛閫夋嫨浜у搧", icon: "none" });
+ return false;
+ }
+ const invalid = rows.findIndex(i => {
+ const qty = toNumber(i.returnQuantity);
+ if (qty <= 0) return true;
+ if (qty > getReturnQtyMax(i)) return true;
+ return false;
+ });
+ if (invalid !== -1) {
+ uni.showToast({ title: `绗�${invalid + 1}琛岄��璐ф暟閲忎笉鍚堟硶`, icon: "none" });
+ return false;
+ }
+ return true;
+ };
+
+ const handleSubmit = async () => {
+ if (!validateProducts()) return;
+ const valid = await formRef.value.validate().catch(() => false);
+ if (!valid) return;
+ loading.value = true;
+ const rows = (form.value.purchaseReturnOrderProductsDtos || []).map(i => {
+ const cloned = { ...i };
+ syncRowTotal(cloned);
+ return cloned;
+ });
+ const payload = {
+ ...form.value,
+ purchaseReturnOrderProductsDtos: rows.filter(i => toNumber(i.returnQuantity) > 0),
+ };
+ createPurchaseReturnOrder(payload)
+ .then(() => {
+ uni.showToast({ title: "鎻愪氦鎴愬姛", icon: "success" });
+ uni.removeStorageSync("purchaseReturnOrderSelectedProducts");
+ uni.navigateBack();
+ })
+ .catch(() => {
+ uni.showToast({ title: "鎻愪氦澶辫触", icon: "error" });
+ })
+ .finally(() => {
+ loading.value = false;
+ });
+ };
+
+ watch(
+ () => baseAmount.value,
+ () => {
+ syncTotalAmount();
+ }
+ );
+
+ onMounted(() => {
+ form.value.preparedAt = formatDateToYMD(Date.now());
+ preparedAtPickerValue.value = Date.now();
+ fetchSupplierOptions();
+ fetchUserOptions();
+ });
+
+ onShow(() => {
+ const stored = uni.getStorageSync("purchaseReturnOrderSelectedProducts");
+ if (stored) {
+ try {
+ const rows = JSON.parse(stored);
+ mergeSelectedProducts(rows);
+ uni.removeStorageSync("purchaseReturnOrderSelectedProducts");
+ } catch {
+ uni.removeStorageSync("purchaseReturnOrderSelectedProducts");
+ }
+ }
+ });
+
+ const showAutoLoadModalOnceKey = "purchaseReturnOrderAutoLoadShown";
+ watch(
+ () => form.value.purchaseLedgerId,
+ (val, oldVal) => {
+ if (!val || String(val) === String(oldVal)) return;
+ if (uni.getStorageSync(showAutoLoadModalOnceKey)) return;
+ uni.setStorageSync(showAutoLoadModalOnceKey, "1");
+ uni.showModal({
+ title: "鎻愮ず",
+ content: "鏄惁鑷姩鍔犺浇璇ラ噰璐悎鍚屼笅鍏ㄩ儴浜у搧锛�",
+ success: res => {
+ if (res.confirm) loadProductsFromPurchaseLedger();
+ },
+ });
+ }
+ );
+</script>
+
+<style scoped lang="scss">
+ @import "@/styles/procurement-common.scss";
+
+ .account-detail {
+ min-height: 100vh;
+ background: #f8f9fa;
+ padding-bottom: 90px;
+ }
+
+ .form-section {
+ margin: 12px;
+ border-radius: 12px;
+ overflow: hidden;
+ }
+
+ .product-actions {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 12px 12px 0 12px;
+ }
+
+ .amount-summary {
+ display: flex;
+ align-items: center;
+ }
+
+ .amount-text {
+ font-size: 14px;
+ color: #333;
+ font-weight: 600;
+ }
+
+ .empty-products {
+ padding: 16px 12px;
+ color: #999;
+ font-size: 14px;
+ }
+
+ .product-list {
+ padding: 12px;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ }
+
+ .product-card {
+ background: #fff;
+ border-radius: 12px;
+ padding: 0 12px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+ }
+
+ .product-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 12px 0;
+ }
+
+ .product-title {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ }
+
+ .product-name {
+ font-size: 14px;
+ color: #333;
+ font-weight: 500;
+ }
+
+ .product-body {
+ padding: 12px 0;
+ }
+
+ .qty-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 8px 0;
+ }
+
+ .qty-label {
+ font-size: 12px;
+ color: #777;
+ }
+</style>
diff --git a/src/pages/procurementManagement/purchaseReturnOrder/index.vue b/src/pages/procurementManagement/purchaseReturnOrder/index.vue
new file mode 100644
index 0000000..2cd1a14
--- /dev/null
+++ b/src/pages/procurementManagement/purchaseReturnOrder/index.vue
@@ -0,0 +1,185 @@
+<template>
+ <view class="sales-account">
+ <PageHeader title="閲囪喘閫�璐у崟" @back="goBack">
+ <template #right>
+ <up-button
+ type="primary"
+ size="small"
+ text="鏂板"
+ :customStyle="{ marginRight: '12px' }"
+ @click="goAdd"
+ />
+ </template>
+ </PageHeader>
+ <view class="search-section">
+ <view class="search-bar">
+ <view class="search-input">
+ <up-input
+ class="search-text"
+ placeholder="璇疯緭鍏ラ��鏂欏崟鍙�"
+ v-model="searchNo"
+ @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="list.length > 0">
+ <view v-for="item in list" :key="item.id">
+ <view class="ledger-item">
+ <view class="item-header">
+ <view class="item-left">
+ <view class="document-icon">
+ <up-icon name="file-text" size="16" color="#ffffff"></up-icon>
+ </view>
+ <text class="item-id">{{ item.no || "-" }}</text>
+ </view>
+ </view>
+ <up-divider></up-divider>
+ <view class="item-details">
+ <view class="detail-row">
+ <text class="detail-label">閫�璐ф柟寮�</text>
+ <text class="detail-value">{{ getReturnTypeLabel(item.returnType) }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">渚涘簲鍟嗗悕绉�</text>
+ <text class="detail-value">{{ item.supplierName || "-" }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">椤圭洰闃舵</text>
+ <text class="detail-value">{{ getProjectPhaseLabel(item.projectPhase) }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">鍒朵綔鏃ユ湡</text>
+ <text class="detail-value">{{ item.preparedAt || "-" }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">鎴愪氦閲戦(鍏�)</text>
+ <text class="detail-value highlight">{{ formatAmount(item.totalAmount) }}</text>
+ </view>
+ </view>
+ <view class="action-buttons">
+ <u-button size="small" class="action-btn" @click="goView(item)">璇︽儏</u-button>
+ <u-button
+ type="error"
+ size="small"
+ class="action-btn"
+ @click="handleDelete(item)"
+ >
+ 鍒犻櫎
+ </u-button>
+ </view>
+ </view>
+ </view>
+ </view>
+ <view v-else class="no-data">
+ <text>鏆傛棤閲囪喘閫�璐у崟鏁版嵁</text>
+ </view>
+ </view>
+</template>
+
+<script setup>
+ import { ref } from "vue";
+ import { onShow } from "@dcloudio/uni-app";
+ import { findPurchaseReturnOrderListPage, deletePurchaseReturnOrder } from "@/api/procurementManagement/purchaseReturnOrder";
+
+ const searchNo = ref("");
+ const list = ref([]);
+
+ const page = {
+ current: -1,
+ size: -1,
+ };
+
+ const goBack = () => {
+ uni.navigateBack();
+ };
+
+ const goAdd = () => {
+ uni.navigateTo({
+ url: "/pages/procurementManagement/purchaseReturnOrder/add",
+ });
+ };
+
+ const goView = item => {
+ uni.navigateTo({
+ url: `/pages/procurementManagement/purchaseReturnOrder/view?id=${item.id}`,
+ });
+ };
+
+ const getReturnTypeLabel = value => {
+ if (String(value) === "0") return "閫�璐ч��娆�";
+ if (String(value) === "1") return "鎷掓敹";
+ return "-";
+ };
+
+ const getProjectPhaseLabel = value => {
+ const map = {
+ 0: "绔嬮」",
+ 1: "璁捐",
+ 2: "閲囪喘",
+ 3: "鐢熶骇",
+ 4: "鍑鸿揣",
+ };
+ const key = String(value);
+ return map[key] || "-";
+ };
+
+ const formatAmount = value => {
+ if (value === null || value === undefined || value === "") return "-";
+ const num = Number(value);
+ if (Number.isNaN(num)) return "-";
+ return num.toFixed(2);
+ };
+
+ const getList = () => {
+ uni.showLoading({ title: "鍔犺浇涓�...", mask: true });
+ findPurchaseReturnOrderListPage({
+ ...page,
+ no: searchNo.value,
+ })
+ .then(res => {
+ list.value = res?.data?.records || [];
+ })
+ .catch(() => {
+ uni.showToast({ title: "鏌ヨ澶辫触", icon: "error" });
+ })
+ .finally(() => {
+ uni.hideLoading();
+ });
+ };
+
+ const handleDelete = item => {
+ if (!item?.id) return;
+ uni.showModal({
+ title: "鍒犻櫎鎻愮ず",
+ content: "纭畾瑕佸垹闄ゅ悧锛熷垹闄ゅ悗鏃犳硶鎭㈠",
+ success: res => {
+ if (!res.confirm) return;
+ uni.showLoading({ title: "鍒犻櫎涓�...", mask: true });
+ deletePurchaseReturnOrder(item.id)
+ .then(() => {
+ uni.showToast({ title: "鍒犻櫎鎴愬姛", icon: "success" });
+ getList();
+ })
+ .catch(() => {
+ uni.showToast({ title: "鍒犻櫎澶辫触", icon: "error" });
+ })
+ .finally(() => {
+ uni.hideLoading();
+ });
+ },
+ });
+ };
+
+ onShow(() => {
+ getList();
+ });
+</script>
+
+<style scoped lang="scss">
+ @import "@/styles/procurement-common.scss";
+</style>
diff --git a/src/pages/procurementManagement/purchaseReturnOrder/productList.vue b/src/pages/procurementManagement/purchaseReturnOrder/productList.vue
new file mode 100644
index 0000000..048d975
--- /dev/null
+++ b/src/pages/procurementManagement/purchaseReturnOrder/productList.vue
@@ -0,0 +1,163 @@
+<template>
+ <view class="sales-account">
+ <PageHeader title="閫夋嫨浜у搧" @back="goBack" />
+ <view class="ledger-list" v-if="list.length > 0">
+ <view v-for="item in list" :key="item.id">
+ <view class="ledger-item" @click="toggle(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.productCategory || "浜у搧" }}</text>
+ </view>
+ <view class="item-right">
+ <u-tag :type="isSelected(item.id) ? 'success' : 'info'">
+ {{ isSelected(item.id) ? "宸查��" : "鏈��" }}
+ </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.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 class="detail-value">{{ item.quantity ?? "-" }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">鍚◣鍗曚环(鍏�)</text>
+ <text class="detail-value highlight">{{ formatAmount(item.taxInclusiveUnitPrice) }}</text>
+ </view>
+ </view>
+ </view>
+ </view>
+ </view>
+ <view v-else class="no-data">
+ <text>鏆傛棤浜у搧鏁版嵁</text>
+ </view>
+ <view class="footer">
+ <u-button class="cancel-btn" @click="goBack">鍙栨秷</u-button>
+ <u-button class="save-btn" type="primary" @click="confirm">纭({{ selectedIds.size }})</u-button>
+ </view>
+ </view>
+</template>
+
+<script setup>
+ import { onLoad } from "@dcloudio/uni-app";
+ import { ref } from "vue";
+ import { productList } from "@/api/procurementManagement/procurementLedger";
+
+ const purchaseLedgerId = ref(undefined);
+ const list = ref([]);
+ const selectedIds = ref(new Set());
+ const selectedRows = ref(new Map());
+
+ const formatAmount = value => {
+ if (value === null || value === undefined || value === "") return "0.00";
+ const num = Number(value);
+ if (Number.isNaN(num)) return "0.00";
+ return num.toFixed(2);
+ };
+
+ const isSelected = id => {
+ return selectedIds.value.has(String(id));
+ };
+
+ const toggle = item => {
+ const key = String(item.id);
+ if (selectedIds.value.has(key)) {
+ selectedIds.value.delete(key);
+ selectedRows.value.delete(key);
+ } else {
+ selectedIds.value.add(key);
+ selectedRows.value.set(key, item);
+ }
+ };
+
+ const goBack = () => {
+ uni.navigateBack();
+ };
+
+ const confirm = () => {
+ const rows = Array.from(selectedRows.value.values());
+ uni.setStorageSync("purchaseReturnOrderSelectedProducts", JSON.stringify(rows));
+ uni.navigateBack();
+ };
+
+ const loadList = () => {
+ if (!purchaseLedgerId.value) return;
+ uni.showLoading({ title: "鍔犺浇涓�...", mask: true });
+ productList({ salesLedgerId: purchaseLedgerId.value, type: 2 })
+ .then(res => {
+ list.value = res.data || [];
+ })
+ .catch(() => {
+ uni.showToast({ title: "鍔犺浇澶辫触", icon: "error" });
+ })
+ .finally(() => {
+ uni.hideLoading();
+ });
+ };
+
+ const initSelectionFromStorage = () => {
+ const stored = uni.getStorageSync("purchaseReturnOrderSelectedProducts");
+ if (!stored) return;
+ try {
+ const rows = JSON.parse(stored) || [];
+ rows.forEach(r => {
+ const key = String(r.id);
+ selectedIds.value.add(key);
+ selectedRows.value.set(key, r);
+ });
+ } catch {}
+ };
+
+ onLoad(options => {
+ if (options?.purchaseLedgerId) {
+ purchaseLedgerId.value = options.purchaseLedgerId;
+ }
+ initSelectionFromStorage();
+ loadList();
+ });
+</script>
+
+<style scoped lang="scss">
+ @import "@/styles/procurement-common.scss";
+
+ .footer {
+ position: fixed;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: #fff;
+ display: flex;
+ justify-content: space-around;
+ align-items: center;
+ padding: 12px 0;
+ box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.05);
+ z-index: 1000;
+ }
+
+ .cancel-btn {
+ width: 120px;
+ background: #c7c9cc;
+ color: #fff;
+ border-radius: 40px;
+ }
+
+ .save-btn {
+ width: 200px;
+ border-radius: 40px;
+ }
+
+ .sales-account {
+ padding-bottom: 90px;
+ }
+</style>
diff --git a/src/pages/procurementManagement/purchaseReturnOrder/view.vue b/src/pages/procurementManagement/purchaseReturnOrder/view.vue
new file mode 100644
index 0000000..bc33f1c
--- /dev/null
+++ b/src/pages/procurementManagement/purchaseReturnOrder/view.vue
@@ -0,0 +1,202 @@
+<template>
+ <view class="sales-account">
+ <PageHeader title="閲囪喘閫�璐ц鎯�" @back="goBack" />
+ <view class="ledger-list" v-if="loaded">
+ <view class="ledger-item">
+ <view class="item-header">
+ <view class="item-left">
+ <view class="document-icon">
+ <up-icon name="file-text" size="16" color="#ffffff"></up-icon>
+ </view>
+ <text class="item-id">{{ detail.no || "-" }}</text>
+ </view>
+ </view>
+ <up-divider></up-divider>
+ <view class="item-details">
+ <view class="detail-row">
+ <text class="detail-label">閫�璐ф柟寮�</text>
+ <text class="detail-value">{{ getReturnTypeLabel(detail.returnType) }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">渚涘簲鍟嗗悕绉�</text>
+ <text class="detail-value">{{ detail.supplierName || "-" }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">椤圭洰闃舵</text>
+ <text class="detail-value">{{ getProjectPhaseLabel(detail.projectPhase) }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">鍏宠仈鍗曞彿</text>
+ <text class="detail-value">{{ detail.purchaseContractNumber || "-" }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">鍒朵綔鏃ユ湡</text>
+ <text class="detail-value">{{ detail.preparedAt || "-" }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">鍒跺崟浜�</text>
+ <text class="detail-value">{{ detail.preparedUserName || "-" }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">閫�鏂欎汉</text>
+ <text class="detail-value">{{ detail.returnUserName || "-" }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">鏁村崟鎶樻墸棰�</text>
+ <text class="detail-value">{{ formatAmount(detail.totalDiscountAmount) }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">鏁村崟鎶樻墸鐜�</text>
+ <text class="detail-value">{{ detail.totalDiscountRate ?? "-" }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">鎴愪氦閲戦</text>
+ <text class="detail-value highlight">{{ formatAmount(detail.totalAmount) }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">澶囨敞</text>
+ <text class="detail-value">{{ detail.remark || "-" }}</text>
+ </view>
+ </view>
+ </view>
+
+ <view class="section-title">
+ <text class="section-text">浜у搧鍒楄〃</text>
+ </view>
+
+ <view v-if="products.length === 0" class="no-data">
+ <text>鏆傛棤浜у搧鏁版嵁</text>
+ </view>
+ <view v-else>
+ <view v-for="(p, idx) in products" :key="idx" class="ledger-item">
+ <view class="item-header">
+ <view class="item-left">
+ <view class="document-icon">
+ <up-icon name="file-text" size="16" color="#ffffff"></up-icon>
+ </view>
+ <text class="item-id">浜у搧 {{ idx + 1 }}</text>
+ </view>
+ </view>
+ <up-divider></up-divider>
+ <view class="item-details">
+ <view class="detail-row">
+ <text class="detail-label">浜у搧澶х被</text>
+ <text class="detail-value">{{ p.productCategory || "-" }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">瑙勬牸鍨嬪彿</text>
+ <text class="detail-value">{{ p.specificationModel || "-" }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">鍗曚綅</text>
+ <text class="detail-value">{{ p.unit || "-" }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">鏁伴噺</text>
+ <text class="detail-value">{{ p.quantity ?? "-" }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">閫�璐ф暟閲�</text>
+ <text class="detail-value highlight">{{ p.returnQuantity ?? "-" }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">绋庣巼(%)</text>
+ <text class="detail-value">{{ p.taxRate ?? "-" }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">鍚◣鍗曚环(鍏�)</text>
+ <text class="detail-value">{{ formatAmount(p.taxInclusiveUnitPrice) }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">鍚◣鎬讳环(鍏�)</text>
+ <text class="detail-value">{{ formatAmount(p.taxInclusiveTotalPrice) }}</text>
+ </view>
+ <view class="detail-row">
+ <text class="detail-label">鏄惁璐ㄦ</text>
+ <text class="detail-value">{{ p.isChecked ? "鏄�" : "鍚�" }}</text>
+ </view>
+ </view>
+ </view>
+ </view>
+ </view>
+ </view>
+</template>
+
+<script setup>
+ import { ref } from "vue";
+ import { onLoad } from "@dcloudio/uni-app";
+ import { getPurchaseReturnOrderDetail } from "@/api/procurementManagement/purchaseReturnOrder";
+
+ const id = ref(undefined);
+ const loaded = ref(false);
+ const detail = ref({});
+ const products = ref([]);
+
+ const goBack = () => {
+ uni.navigateBack();
+ };
+
+ const getReturnTypeLabel = value => {
+ if (String(value) === "0") return "閫�璐ч��娆�";
+ if (String(value) === "1") return "鎷掓敹";
+ return "-";
+ };
+
+ const getProjectPhaseLabel = value => {
+ const map = {
+ 0: "绔嬮」",
+ 1: "璁捐",
+ 2: "閲囪喘",
+ 3: "鐢熶骇",
+ 4: "鍑鸿揣",
+ };
+ const key = String(value);
+ return map[key] || "-";
+ };
+
+ const formatAmount = value => {
+ if (value === null || value === undefined || value === "") return "-";
+ const num = Number(value);
+ if (Number.isNaN(num)) return "-";
+ return num.toFixed(2);
+ };
+
+ const loadDetail = () => {
+ if (!id.value) return;
+ uni.showLoading({ title: "鍔犺浇涓�...", mask: true });
+ getPurchaseReturnOrderDetail(id.value)
+ .then(res => {
+ const payload = res?.data || {};
+ detail.value = payload;
+ const rows = payload.purchaseReturnOrderProductsDetailVoList || [];
+ products.value = rows.map(i => ({ ...i, ...(i.salesLedgerProduct || {}) }));
+ loaded.value = true;
+ })
+ .catch(() => {
+ uni.showToast({ title: "鑾峰彇璇︽儏澶辫触", icon: "error" });
+ })
+ .finally(() => {
+ uni.hideLoading();
+ });
+ };
+
+ onLoad(options => {
+ if (options?.id) id.value = options.id;
+ loadDetail();
+ });
+</script>
+
+<style scoped lang="scss">
+ @import "@/styles/procurement-common.scss";
+
+ .section-title {
+ padding: 0 20px;
+ margin: 12px 0 8px 0;
+ }
+
+ .section-text {
+ font-size: 14px;
+ color: #333;
+ font-weight: 600;
+ }
+</style>
diff --git a/src/pages/works.vue b/src/pages/works.vue
index 3d7c2c9..41aad95 100644
--- a/src/pages/works.vue
+++ b/src/pages/works.vue
@@ -297,6 +297,14 @@
icon: "/static/images/icon/gongyingshangwanglai.svg",
label: "渚涘簲鍟嗗線鏉�",
},
+ {
+ icon: "/static/images/icon/caigouguanli.svg",
+ label: "閲囪喘閫�璐�",
+ },
+ {
+ icon: "/static/images/icon/gongchuguanli.svg",
+ label: "渚涘簲鍟嗘。妗�",
+ },
]);
// 璐㈠姟绠$悊鍔熻兘鏁版嵁
@@ -547,6 +555,16 @@
case "渚涘簲鍟嗗線鏉�":
uni.navigateTo({
url: "/pages/procurementManagement/paymentLedger/index",
+ });
+ break;
+ case "閲囪喘閫�璐�":
+ uni.navigateTo({
+ url: "/pages/procurementManagement/purchaseReturnOrder/index",
+ });
+ break;
+ case "渚涘簲鍟嗘。妗�":
+ uni.navigateTo({
+ url: "/pages/basicData/supplierManage/index",
});
break;
case "鍏嚭绠$悊":
@@ -963,6 +981,7 @@
// 鏀堕泦鎵�鏈夋湁鏉冮檺鐨勮彍鍗曟爣棰橈紙鏍规嵁 meta.title锛�
const allowedMenuTitles = new Set();
+ const alwaysShowTitles = new Set(["閲囪喘閫�璐у崟", "渚涘簲鍟嗙鐞�"]);
const collectMenuTitles = routes => {
if (!Array.isArray(routes)) return;
routes.forEach(route => {
@@ -980,7 +999,7 @@
const menuMapping = {
collaboration: { target: collaborationItems, specialMapping: { "瑙勭珷鍒跺害": "瑙勭珷鍒跺害绠$悊" } },
};
-
+ console.log(allowedMenuTitles)
// 閫氱敤杩囨护鍑芥暟
const filterArray = (targetArray, specialMapping) => {
const filtered = targetArray.filter(item => {
@@ -1702,4 +1721,4 @@
box-shadow: 0 0.375rem 1.25rem rgba(0, 0, 0, 0.4);
}
}
-</style>
\ No newline at end of file
+</style>
--
Gitblit v1.9.3