From fe40c87fe44c8565576c27bbc6794e06c034ce33 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期三, 20 八月 2025 11:09:53 +0800
Subject: [PATCH] 1.开票台账联调

---
 src/pages/index.vue                      |    5 
 src/pages.json                           |   14 
 src/pages/sales/invoiceLedger/detail.vue |  276 +++++++++++++++
 src/store/modules/user.ts                |    3 
 src/pages/sales/invoiceLedger/index.vue  |  758 ++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 1,055 insertions(+), 1 deletions(-)

diff --git a/src/pages.json b/src/pages.json
index d14e8ad..bebcc42 100644
--- a/src/pages.json
+++ b/src/pages.json
@@ -92,6 +92,20 @@
       }
     },
     {
+      "path": "pages/sales/invoiceLedger/index",
+      "style": {
+        "navigationBarTitleText": "寮�绁ㄥ彴璐�",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/sales/invoiceLedger/detail",
+      "style": {
+        "navigationBarTitleText": "缂栬緫寮�绁ㄥ彴璐�",
+        "navigationStyle": "custom"
+      }
+    },
+    {
       "path": "pages/common/webview/index",
       "style": {
         "navigationBarTitleText": "娴忚缃戦〉"
diff --git a/src/pages/index.vue b/src/pages/index.vue
index 1b9f36e..f073745 100644
--- a/src/pages/index.vue
+++ b/src/pages/index.vue
@@ -298,6 +298,11 @@
 				url: '/pages/sales/invoicingRegistration/index'
 			});
 			break;
+		case '寮�绁ㄥ彴璐�':
+			uni.navigateTo({
+				url: '/pages/sales/invoiceLedger/index'
+			});
+			break;
 		case '鍗忓悓瀹℃壒':
 			uni.navigateTo({
 				url: '/pages/cooperativeOffice/collaborativeApproval/index'
diff --git a/src/pages/sales/invoiceLedger/detail.vue b/src/pages/sales/invoiceLedger/detail.vue
new file mode 100644
index 0000000..2ddba5e
--- /dev/null
+++ b/src/pages/sales/invoiceLedger/detail.vue
@@ -0,0 +1,276 @@
+<template>
+	<view class="account-detail">
+		<van-nav-bar
+			title="缂栬緫寮�绁ㄥ彴璐�"
+			left-text="杩斿洖"
+			left-arrow
+			@click-left="goBack"
+			fixed
+			placeholder
+		/>
+
+		<van-form @submit="submitForm" ref="formRef" label-width="110px" input-align="right" error-message-align="right" scroll-to-error scroll-to-error-position="center">
+			<van-cell-group title="鍩烘湰淇℃伅" inset>
+				<van-field v-model="form.salesContractNo" label="閿�鍞悎鍚屽彿" readonly />
+				<van-field v-model="form.customerName" label="瀹㈡埛鍚嶇О" readonly />
+				<van-field v-model="form.invoiceNo" label="鍙戠エ鍙�" placeholder="璇疯緭鍏�" required :rules="[{ required: true, message: '璇疯緭鍏ュ彂绁ㄥ彿' }]" />
+				<van-field v-model="form.invoiceTotal" label="鍙戠エ閲戦(鍏�)" type="number" placeholder="璇疯緭鍏�" required :rules="[{ required: true, message: '璇疯緭鍏ュ彂绁ㄩ噾棰�' }]" />
+				<view class="tip-text" v-if="form.taxInclusiveTotalPrice">鍚堝悓鎬婚锛歿{ formatAmount(form.taxInclusiveTotalPrice) }} 鍏�</view>
+				<van-field v-model="form.invoicePerson" label="寮�绁ㄤ汉" readonly />
+				<van-field v-model="form.invoiceDate" label="寮�绁ㄦ棩鏈�" readonly placeholder="璇烽�夋嫨" @click="showInvoiceDatePicker = true" required :rules="[{ required: true, message: '璇烽�夋嫨寮�绁ㄦ棩鏈�' }]" />
+			</van-cell-group>
+
+			<van-cell-group title="闄勪欢鏉愭枡锛堜粎鏀寔 pdf锛�" inset>
+				<van-uploader
+					accept=".pdf"
+					multiple
+					:after-read="afterReadUpload"
+					:before-read="beforeReadPdf"
+				>
+					<van-button class="upload-btn" icon="plus" type="primary" block>涓婁紶鏂囦欢</van-button>
+				</van-uploader>
+				<view class="uploaded-list" v-if="fileList.length">
+					<view class="uploaded-item" v-for="(f, idx) in fileList" :key="idx">
+						<text class="file-name">{{ f.name || getFileNameFromUrl(f.url) }}</text>
+						<van-button size="mini" type="danger" plain @click="removeUploaded(idx)">绉婚櫎</van-button>
+					</view>
+				</view>
+			</van-cell-group>
+
+			<view class="footer-btns">
+				<van-button class="cancel-btn" @click="goBack">鍙栨秷</van-button>
+				<van-button class="save-btn" native-type="submit" form-type="submit">淇濆瓨</van-button>
+			</view>
+		</van-form>
+
+		<van-popup v-model:show="showInvoiceDatePicker" position="bottom">
+			<van-date-picker
+				v-model="currentInvoiceDate"
+				title="閫夋嫨寮�绁ㄦ棩鏈�"
+				@confirm="onInvoiceDateConfirm"
+				@cancel="showInvoiceDatePicker = false"
+			/>
+		</van-popup>
+	</view>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+import { showToast, showLoadingToast, closeToast } from 'vant'
+import dayjs from 'dayjs'
+import useUserStore from '@/store/modules/user'
+import { getToken } from '@/utils/auth'
+import { invoiceLedgerProductInfo, invoiceLedgerSaveOrUpdate } from '@/api/salesManagement/invoiceLedger.js'
+import config from '@/config.js'
+
+const userStore = useUserStore()
+
+const formRef = ref()
+let form = ref({
+	salesLedgerId: '',
+	customerId: '',
+	invoiceNo: '',
+	invoiceTotal: '',
+	taxRate: '',
+	invoicePerson: '',
+	invoiceDate: '',
+	customerName: '',
+	fileList: [],
+	createTime: '',
+	taxInclusiveTotalPrice: ''
+})
+const fileList = ref([])
+const currentId = ref('')
+
+// 鏃ユ湡閫夋嫨
+const showInvoiceDatePicker = ref(false)
+const currentInvoiceDate = ref([new Date().getFullYear(), new Date().getMonth() + 1, new Date().getDate()])
+
+const goBack = () => {
+	uni.navigateBack()
+}
+
+const formatAmount = (val) => {
+	if (val === undefined || val === null || val === '') return '0.00'
+	const num = Number(val)
+	if (Number.isNaN(num)) return '0.00'
+	return num.toFixed(2)
+}
+
+const onInvoiceDateConfirm = ({ selectedValues }) => {
+	form.value.invoiceDate = selectedValues.join('-')
+	currentInvoiceDate.value = selectedValues
+	showInvoiceDatePicker.value = false
+}
+
+// 涓婁紶鍓嶆牎楠岋紙鍏煎 Vant Uploader 鐨� file/fileList 缁撴瀯锛�
+const beforeReadPdf = (file) => {
+	const items = Array.isArray(file) ? file : [file]
+	for (const it of items) {
+		const raw = it?.file || it
+		const fileName = raw?.name || it?.name || ''
+		const ext = fileName.split('.').pop()?.toLowerCase()
+		const sizeOk = (raw?.size || 0) <= 10 * 1024 * 1024
+		if (ext !== 'pdf') {
+			showToast('浠呮敮鎸乸df鏂囦欢')
+			return false
+		}
+		if (!sizeOk) {
+			showToast('涓婁紶鏂囦欢澶у皬涓嶈兘瓒呰繃10MB')
+			return false
+		}
+	}
+	return true
+}
+
+const uploadSingleFile = async (fileObj) => {
+	return new Promise((resolve, reject) => {
+		showLoadingToast({ message: '姝e湪涓婁紶...' })
+		const baseUrl = config.baseUrl + '/invoiceLedger/uploadFile'
+
+		const filePath = fileObj?.url || fileObj?.tempFilePath || fileObj?.file?.path
+		if (filePath) {
+			uni.uploadFile({
+				url: baseUrl,
+				filePath,
+				name: 'file',
+				header: { Authorization: 'Bearer ' + getToken() },
+				success: (res) => {
+					closeToast()
+					try {
+						const data = JSON.parse(res.data || '{}')
+						if (data.code === 200) {
+							resolve(data.data)
+						} else {
+							reject(new Error(data.msg || '涓婁紶澶辫触'))
+						}
+					} catch (err) {
+						reject(err)
+					}
+				},
+				fail: (err) => {
+					closeToast()
+					reject(err)
+				}
+			})
+			return
+		}
+
+		// H5: 浣跨敤鍘熷 File锛坕nput 閫夋嫨锛�
+		const rawFile = fileObj?.file
+		if (rawFile) {
+			// uni.uploadFile 鍦� H5 涓嶆敮鎸佸師鐢� File 瀵硅薄锛岃繖閲岀敤 fetch 鍙戦�� FormData
+			const formData = new FormData()
+			formData.append('file', rawFile, rawFile.name || 'file.pdf')
+			formData.append('salesLedgerId', form.value.salesLedgerId || currentId.value || '')
+			fetch(baseUrl, {
+				method: 'POST',
+				headers: { Authorization: 'Bearer ' + getToken() },
+				body: formData
+			}).then(async (res) => {
+				closeToast()
+				const data = await res.json()
+				if (data.code === 200) {
+					resolve(data.data)
+				} else {
+					reject(new Error(data.msg || '涓婁紶澶辫触'))
+				}
+			}).catch((err) => {
+				closeToast()
+				reject(err)
+			})
+			return
+		}
+
+		closeToast()
+		reject(new Error('鏈壘鍒板彲涓婁紶鐨勬枃浠�'))
+	})
+}
+
+const afterReadUpload = async (file) => {
+	try {
+		const files = Array.isArray(file) ? file : file?.file ? [file] : [file]
+		for (const f of files) {
+			const uploaded = await uploadSingleFile(f)
+			fileList.value.push(uploaded)
+		}
+		showToast('涓婁紶鎴愬姛')
+	} catch (e) {
+		showToast('涓婁紶澶辫触')
+	}
+}
+
+const removeUploaded = (index) => {
+	fileList.value.splice(index, 1)
+}
+
+const getFileNameFromUrl = (url) => {
+	try { if (!url) return ''; return decodeURIComponent(url.split('/').pop()) } catch (e) { return url }
+}
+
+const loadDetail = async (id) => {
+	try {
+		showLoadingToast({ message: '鍔犺浇涓�...' })
+		const res = await invoiceLedgerProductInfo({ id })
+		const data = res?.data || res
+		form.value = { ...data }
+		fileList.value = data?.fileList || []
+		if (!form.value.invoicePerson) {
+			form.value.invoicePerson = userStore.nickName
+		}
+		if (!form.value.invoiceDate) {
+			form.value.invoiceDate = dayjs().format('YYYY-MM-DD')
+		}
+		closeToast()
+	} catch (e) {
+		closeToast()
+		showToast('鍔犺浇澶辫触')
+	}
+}
+
+const submitForm = async () => {
+	try {
+		if (!form.value.invoiceNo) { showToast('璇疯緭鍏ュ彂绁ㄥ彿'); return }
+		if (!form.value.invoiceTotal) { showToast('璇疯緭鍏ュ彂绁ㄩ噾棰�'); return }
+		if (!form.value.invoiceDate) { showToast('璇烽�夋嫨寮�绁ㄦ棩鏈�'); return }
+		showLoadingToast({ message: '鎻愪氦涓�...' })
+		form.value.fileList = fileList.value
+		await invoiceLedgerSaveOrUpdate(form.value)
+		closeToast()
+		showToast('鎻愪氦鎴愬姛')
+		setTimeout(() => { uni.navigateBack() }, 800)
+	} catch (e) {
+		closeToast()
+		showToast('鎻愪氦澶辫触锛岃閲嶈瘯')
+	}
+}
+
+onMounted(() => {
+	const rowStr = uni.getStorageSync('invoiceLedgerEditRow')
+	if (rowStr) {
+		try {
+			const row = JSON.parse(rowStr)
+			currentId.value = row.id
+			loadDetail(currentId.value)
+		} catch (e) {
+			// ignore
+		}
+	}
+})
+</script>
+
+<style scoped lang="scss">
+.account-detail {
+	min-height: 100vh;
+	background: #f8f9fa;
+	padding-bottom: 5rem;
+}
+.uploaded-list { padding: 8px 16px 0 16px; }
+.uploaded-item { display: flex; align-items: center; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #f5f5f5; }
+.file-name { font-size: 12px; color: #333; margin-right: 8px; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+.tip-text { padding: 4px 16px 0 16px; font-size: 12px; color: #888; }
+.footer-btns { position: fixed; left: 0; right: 0; bottom: 0; background: #fff; display: flex; justify-content: space-around; align-items: center; padding: 0.75rem 0; box-shadow: 0 -0.125rem 0.5rem rgba(0,0,0,0.05); z-index: 1000; }
+.cancel-btn { font-weight: 400; font-size: 1rem; color: #FFFFFF; width: 6.375rem; background: #C7C9CC; box-shadow: 0 0.25rem 0.625rem 0 rgba(3,88,185,0.2); border-radius: 2.5rem 2.5rem 2.5rem 2.5rem; }
+.save-btn { font-weight: 400; font-size: 1rem; color: #FFFFFF; width: 14rem; background: linear-gradient( 140deg, #00BAFF 0%, #006CFB 100%); box-shadow: 0 0.25rem 0.625rem 0 rgba(3,88,185,0.2); border-radius: 2.5rem 2.5rem 2.5rem 2.5rem; }
+</style>
+
diff --git a/src/pages/sales/invoiceLedger/index.vue b/src/pages/sales/invoiceLedger/index.vue
new file mode 100644
index 0000000..60a3533
--- /dev/null
+++ b/src/pages/sales/invoiceLedger/index.vue
@@ -0,0 +1,758 @@
+<template>
+	<view class="sales-account">
+		<!-- 椤甸潰澶撮儴 -->
+		<van-nav-bar
+			title="寮�绁ㄥ彴璐�"
+			left-text="杩斿洖"
+			left-arrow
+			@click-left="goBack"
+			fixed
+			placeholder
+		/>
+
+		<!-- 鎼滅储鍜岀瓫閫夊尯鍩燂紙淇濇寔涓庨攢鍞彴璐﹂鏍间竴鑷达級 -->
+		<view class="search-filter-section">
+			<view class="search-bar">
+				<view class="search-input">
+					<input
+						class="search-text"
+						placeholder="瀹㈡埛鍚嶇О/閿�鍞悎鍚屽彿"
+						v-model="searchForm.searchText"
+						confirm-type="search"
+						@confirm="handleQuery"
+					/>
+				</view>
+<!--				<view class="filter-button" @click="showFilter = true">-->
+<!--					<up-icon name="list" size="24" color="#999"></up-icon>-->
+<!--				</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="total > 0">
+			<view v-for="(item, index) in ledgerList" :key="index">
+				<view class="ledger-item">
+					<view class="item-header">
+						<view class="item-left">
+							<view class="document-icon">
+								<up-icon name="file-text" size="16" color="#ffffff"></up-icon>
+							</view>
+							<text class="item-id">{{ item.salesContractNo }}</text>
+						</view>
+					</view>
+					<up-divider></up-divider>
+					<view class="item-details">
+						<view class="detail-row">
+							<text class="detail-label">瀹㈡埛鍚嶇О</text>
+							<text class="detail-value">{{ item.customerName }}</text>
+						</view>
+						<view class="detail-row">
+							<text class="detail-label">瀹㈡埛鍚堝悓鍙�</text>
+							<text class="detail-value">{{ item.customerContractNo }}</text>
+						</view>
+						<view class="detail-row">
+							<text class="detail-label">椤圭洰</text>
+							<text class="detail-value">{{ item.projectName }}</text>
+						</view>
+						<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.invoiceNo || '-' }}</text>
+						</view>
+						<view class="detail-row">
+							<text class="detail-label">鍙戠エ閲戦(鍏�)</text>
+							<text class="detail-value highlight">{{ formatAmount(item.invoiceTotal) }}</text>
+						</view>
+						<view class="detail-row">
+							<text class="detail-label">绋庣巼(%)</text>
+							<text class="detail-value">{{ item.taxRate }}</text>
+						</view>
+						<view class="detail-row">
+							<text class="detail-label">褰曞叆浜�</text>
+							<text class="detail-value">{{ item.invoicePerson }}</text>
+						</view>
+						<view class="detail-row">
+							<text class="detail-label">褰曞叆鏃ユ湡</text>
+							<text class="detail-value">{{ formatDateTime(item.createTime) }}</text>
+						</view>
+						<view class="detail-row">
+							<text class="detail-label">寮�绁ㄦ棩鏈�</text>
+							<text class="detail-value">{{ item.invoiceDate || '-' }}</text>
+						</view>
+					</view>
+					<view class="action-buttons">
+						<van-button
+							type="primary"
+							size="small"
+							class="action-btn"
+							:disabled="item.invoicePerson !== userStore.nickName"
+							@click="openEdit(item)"
+						>
+							缂栬緫
+						</van-button>
+						<van-button
+							type="danger"
+							size="small"
+							class="action-btn"
+							:disabled="item.invoicePerson !== userStore.nickName"
+							@click="handleDelete(item)"
+						>
+							鍒犻櫎
+						</van-button>
+						<van-button
+							type="default"
+							size="small"
+							class="action-btn"
+							v-if="item.invoiceFileName"
+							@click="openFileActions(item.commonFiles || [])"
+						>
+							鏌ョ湅闄勪欢
+						</van-button>
+						<van-button
+							type="primary"
+							size="small"
+							plain
+							class="action-btn"
+							v-else
+							:disabled="item.invoicePerson !== userStore.nickName"
+							@click="openUpload(item)"
+						>
+							涓婁紶
+						</van-button>
+					</view>
+				</view>
+			</view>
+		</view>
+		<view v-else class="no-data">
+			<text>鏆傛棤寮�绁ㄥ彴璐︽暟鎹�</text>
+		</view>
+
+		<!-- 绛涢�夊脊绐� -->
+		<van-popup v-model:show="showFilter" position="bottom" round>
+			<view class="filter-popup">
+				<van-cell-group title="绛涢�夋潯浠�" inset>
+					<van-field
+						label="寮�绁ㄦ棩鏈�"
+						readonly
+						@click="showInvoiceRange = true"
+						:placeholder="invoiceRangeLabel || '璇烽�夋嫨鏃ユ湡鑼冨洿'"
+					/>
+					<van-field
+						label="褰曞叆鏃ユ湡"
+						readonly
+						@click="showCreateDatePicker = true"
+						:placeholder="searchForm.createTimeStart || '璇烽�夋嫨褰曞叆鏃ユ湡'"
+					/>
+					<view class="switch-row">
+						<text class="switch-label">涓嶆樉绀烘湁鍙戠エ琛�</text>
+						<van-switch v-model="searchForm.status" size="20" />
+					</view>
+				</van-cell-group>
+				<view class="filter-actions">
+					<van-button @click="resetFilter">閲嶇疆</van-button>
+					<van-button type="primary" @click="confirmFilter">纭畾</van-button>
+				</view>
+			</view>
+		</van-popup>
+
+		<!-- 鏃ュ巻锛氬紑绁ㄦ棩鏈熻寖鍥� -->
+		<van-popup v-model:show="showInvoiceRange" position="bottom">
+			<van-calendar
+				title="閫夋嫨寮�绁ㄦ棩鏈熻寖鍥�"
+				type="range"
+				color="#2979ff"
+				@confirm="onInvoiceRangeConfirm"
+				@cancel="showInvoiceRange = false"
+			/>
+		</van-popup>
+
+		<!-- 鏃ユ湡锛氬綍鍏ユ棩鏈� -->
+		<van-popup v-model:show="showCreateDatePicker" position="bottom">
+			<van-date-picker
+				v-model="currentCreateDate"
+				title="閫夋嫨褰曞叆鏃ユ湡"
+				@confirm="onCreateDateConfirm"
+				@cancel="showCreateDatePicker = false"
+			/>
+		</van-popup>
+
+		
+
+		<!-- 鍗曡涓婁紶寮圭獥锛堟棤琛ㄥ崟锛� -->
+		<van-popup v-model:show="showUpload" position="bottom" round>
+			<view class="upload-container">
+				<van-cell-group title="涓婁紶闄勪欢锛堜粎鏀寔 pdf锛屾渶澶�10MB锛屾渶澶�10涓級" inset>
+					<van-uploader
+						accept="*"
+						multiple
+						:max-count="10"
+						:after-read="afterReadRowUpload"
+						:before-read="beforeReadPdf"
+					/>
+					<view class="uploaded-list" v-if="fileList.length">
+						<view class="uploaded-item" v-for="(f, idx) in fileList" :key="idx">
+							<text class="file-name">{{ f.name || getFileNameFromUrl(f.url) }}</text>
+							<van-button size="mini" type="danger" plain @click="removeUploaded(idx)">绉婚櫎</van-button>
+						</view>
+					</view>
+				</van-cell-group>
+				<view class="filter-actions">
+					<van-button @click="showUpload = false">鍙栨秷</van-button>
+					<van-button type="primary" @click="confirmUpload">纭</van-button>
+				</view>
+			</view>
+		</van-popup>
+
+		<!-- 闄勪欢鍒楄〃閫夋嫨 -->
+		<van-action-sheet v-model:show="showFileSheet" :actions="fileActions" cancel-text="鍙栨秷" close-on-click-action @select="onSelectFile" />
+	</view>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from 'vue'
+import dayjs from 'dayjs'
+import { showToast, showLoadingToast, closeToast } from 'vant'
+import useUserStore from '@/store/modules/user'
+import { getToken } from '@/utils/auth'
+import config from '@/config.js'
+import {
+	registrationProductPage,
+	commitFile,
+	delInvoiceLedgerByRegProductId
+} from '@/api/salesManagement/invoiceLedger.js'
+
+const userStore = useUserStore()
+
+// 鍒楄〃涓庢煡璇�
+const ledgerList = ref([])
+const total = ref(0)
+const page = reactive({ current: -1, size: -1 })
+const searchForm = reactive({
+	searchText: '',
+	status: false,
+	createTimeStart: ''
+})
+
+// 椤堕儴浜や簰
+const showFilter = ref(false)
+const showInvoiceRange = ref(false)
+const showCreateDatePicker = ref(false)
+const invoiceRangeLabel = ref('')
+const currentCreateDate = ref([new Date().getFullYear(), new Date().getMonth() + 1, new Date().getDate()])
+
+const currentId = ref('')
+const fileList = ref([]) // 琛屼笂浼犳垨閫氱敤涓婁紶鍒楄〃
+
+// 琛屼笂浼犲脊绐�
+const showUpload = ref(false)
+
+// 闄勪欢鏌ョ湅
+const showFileSheet = ref(false)
+const fileActions = ref([])
+let currentFilesToOpen = []
+
+const formatAmount = (val) => {
+	if (val === undefined || val === null || val === '') return '0.00'
+	const num = Number(val)
+	if (Number.isNaN(num)) return '0.00'
+	return num.toFixed(2)
+}
+const formatDateTime = (val) => {
+	if (!val) return ''
+	return dayjs(val).format('YYYY-MM-DD HH:mm:ss')
+}
+
+const goBack = () => {
+	uni.navigateBack()
+}
+
+const handleQuery = () => {
+	getList()
+}
+
+const getList = async () => {
+	try {
+		showLoadingToast({ message: '鍔犺浇涓�...' })
+		const { invoiceDate, ...rest } = searchForm
+		const res = await registrationProductPage({ ...rest, ...page })
+		// 鍏煎涓嶅悓杩斿洖缁撴瀯
+		const records = res?.data?.records || res?.records || res?.data || []
+		const totalVal = res?.data?.total || res?.total || records.length || 0
+		ledgerList.value = records
+		total.value = totalVal
+		closeToast()
+	} catch (e) {
+		closeToast()
+		showToast('鑾峰彇鍒楄〃澶辫触')
+	}
+}
+
+// 绛涢�夐�昏緫
+const resetFilter = () => {
+	searchForm.searchText = ''
+	searchForm.status = false
+	const start = dayjs().startOf('month').format('YYYY-MM-DD')
+	const end = dayjs().endOf('month').format('YYYY-MM-DD')
+	searchForm.invoiceDate = [start, end]
+	searchForm.invoiceDateStart = start
+	searchForm.invoiceDateEnd = end
+	searchForm.createTimeStart = ''
+	invoiceRangeLabel.value = ''
+}
+const confirmFilter = () => {
+	showFilter.value = false
+	getList()
+}
+const onInvoiceRangeConfirm = (e) => {
+	// e 涓� [start, end] 鐨� Date 瀵硅薄鎴栧瓧绗︿覆锛寀ni-app 涓� Vant Calendar 杩斿洖鏃堕棿鎴虫暟缁�
+	try {
+		let start, end
+		if (Array.isArray(e)) {
+			const [s, ed] = e
+			start = dayjs(s).format('YYYY-MM-DD')
+			end = dayjs(ed).format('YYYY-MM-DD')
+		} else if (e && e.detail && Array.isArray(e.detail)) {
+			const [s, ed] = e.detail
+			start = dayjs(s).format('YYYY-MM-DD')
+			end = dayjs(ed).format('YYYY-MM-DD')
+		}
+		searchForm.invoiceDateStart = start
+		searchForm.invoiceDateEnd = end
+		invoiceRangeLabel.value = `${start} 鑷� ${end}`
+		showInvoiceRange.value = false
+	} catch (err) {
+		showInvoiceRange.value = false
+	}
+}
+const onCreateDateConfirm = ({ selectedValues }) => {
+	try {
+		searchForm.createTimeStart = selectedValues.join('-')
+		currentCreateDate.value = selectedValues
+		showCreateDatePicker.value = false
+	} catch (err) {
+		showCreateDatePicker.value = false
+	}
+}
+
+// 缂栬緫閫昏緫鏀逛负璺宠浆鏂伴〉闈�
+const openEdit = (row) => {
+	try {
+		uni.setStorageSync('invoiceLedgerEditRow', JSON.stringify(row))
+		uni.navigateTo({ url: '/pages/sales/invoiceLedger/detail' })
+	} catch (e) {
+		showToast('璺宠浆澶辫触')
+	}
+}
+
+// 鍒犻櫎
+const handleDelete = (row) => {
+	uni.showModal({
+		title: '鍒犻櫎纭',
+		content: '璇ュ彂绁ㄥ彴璐﹀皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�',
+		success: async (res) => {
+			if (res.confirm) {
+				try {
+					showLoadingToast({ message: '澶勭悊涓�...' })
+					await delInvoiceLedgerByRegProductId(row.id)
+					closeToast()
+					showToast('鍒犻櫎鎴愬姛')
+					getList()
+				} catch (e) {
+					closeToast()
+					showToast('鍒犻櫎澶辫触锛岃閲嶈瘯')
+				}
+			}
+		}
+	})
+}
+
+// 琛屼笂浼�
+const openUpload = (row) => {
+	currentId.value = row.id
+	fileList.value = []
+	showUpload.value = true
+}
+const confirmUpload = async () => {
+	try {
+		const payload = { fileList: fileList.value, id: currentId.value }
+		showLoadingToast({ message: '鎻愪氦涓�...' })
+		await commitFile(payload)
+		closeToast()
+		showToast('鎻愪氦鎴愬姛')
+		showUpload.value = false
+		fileList.value = []
+		currentId.value = ''
+		getList()
+	} catch (e) {
+		closeToast()
+		showToast('鎻愪氦澶辫触锛岃閲嶈瘯')
+	}
+}
+
+// 涓婁紶鐩稿叧
+const beforeReadPdf = (file) => {
+	// 鍏煎澶氭枃浠�
+	const files = Array.isArray(file) ? file : [file]
+	for (const f of files) {
+		const sizeOk = f.size <= 10 * 1024 * 1024
+		const ext = (f.name || '').split('.').pop()?.toLowerCase()
+		if (ext !== 'pdf') {
+			showToast('浠呮敮鎸乸df鏂囦欢')
+			return false
+		}
+		if (!sizeOk) {
+			showToast('涓婁紶鏂囦欢澶у皬涓嶈兘瓒呰繃10MB')
+			return false
+		}
+	}
+	return true
+}
+
+const uploadSingleFile = async (fileObj) => {
+	return new Promise((resolve, reject) => {
+		showLoadingToast({ message: '姝e湪涓婁紶...' })
+		uni.uploadFile({
+			url: config.baseUrl + '/invoiceLedger/uploadFile',
+			filePath: fileObj.url || fileObj.file?.path || fileObj.tempFilePath,
+			name: 'file',
+			header: { Authorization: 'Bearer ' + getToken() },
+			success: (res) => {
+				closeToast()
+				try {
+					const data = JSON.parse(res.data || '{}')
+					if (data.code === 200) {
+						resolve(data.data)
+					} else {
+						reject(new Error(data.msg || '涓婁紶澶辫触'))
+					}
+				} catch (err) {
+					reject(err)
+				}
+			},
+			fail: (err) => {
+				closeToast()
+				reject(err)
+			}
+		})
+	})
+}
+
+const afterReadEditUpload = async (file) => {
+	try {
+		const files = Array.isArray(file) ? file : file?.file ? [file] : [file]
+		for (const f of files) {
+			const uploaded = await uploadSingleFile(f)
+			fileList.value.push(uploaded)
+		}
+		showToast('涓婁紶鎴愬姛')
+	} catch (e) {
+		showToast('涓婁紶澶辫触')
+	}
+}
+
+const afterReadRowUpload = async (file) => {
+	try {
+		const files = Array.isArray(file) ? file : file?.file ? [file] : [file]
+		for (const f of files) {
+			const uploaded = await uploadSingleFile(f)
+			fileList.value.push(uploaded)
+		}
+		showToast('涓婁紶鎴愬姛')
+	} catch (e) {
+		showToast('涓婁紶澶辫触')
+	}
+}
+
+const removeUploaded = (index) => {
+	fileList.value.splice(index, 1)
+}
+
+const getFileNameFromUrl = (url) => {
+	try {
+		if (!url) return ''
+		return decodeURIComponent(url.split('/').pop())
+	} catch (e) {
+		return url
+	}
+}
+
+// 闄勪欢鏌ョ湅
+const openFileActions = (commonFiles) => {
+	currentFilesToOpen = commonFiles || []
+	fileActions.value = (commonFiles || []).map((f, idx) => ({ name: getFileNameFromUrl(f.url || ''), index: idx }))
+	showFileSheet.value = true
+}
+const onSelectFile = async (action) => {
+	try {
+		const item = currentFilesToOpen[action.index]
+		if (!item || !item.url) return
+		showLoadingToast({ message: '涓嬭浇涓�...' })
+		uni.downloadFile({
+			url: item.url,
+			success: (res) => {
+				closeToast()
+				if (res.statusCode === 200) {
+					uni.openDocument({ filePath: res.tempFilePath })
+				} else {
+					showToast('涓嬭浇澶辫触')
+				}
+			},
+			fail: () => {
+				closeToast()
+				showToast('涓嬭浇澶辫触')
+			}
+		})
+	} catch (e) {
+		closeToast()
+		showToast('鎵撳紑澶辫触')
+	}
+}
+
+onMounted(() => {
+	getList()
+})
+</script>
+
+<style scoped lang="scss">
+.u-divider {
+	margin: 0 !important;
+}
+.sales-account {
+	min-height: 100vh;
+	background: #f8f9fa;
+	position: relative;
+}
+
+.search-filter-section {
+	padding: 10px 20px;
+	background: #ffffff;
+}
+
+.search-bar {
+	display: flex;
+	align-items: center;
+	gap: 12px;
+}
+
+.search-input {
+	flex: 1;
+	background: #f5f5f5;
+	border-radius: 24px;
+	padding: 10px 16px;
+	display: flex;
+	align-items: center;
+	gap: 8px;
+}
+
+.search-text {
+	flex: 1;
+	font-size: 14px;
+	color: #333;
+	background: transparent;
+	border: none;
+	outline: none;
+}
+
+.search-text::placeholder {
+	color: #999;
+}
+
+.filter-button {
+	width: 40px;
+	height: 40px;
+	border-radius: 8px;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+}
+
+.ledger-list {
+	padding: 20px;
+}
+
+.ledger-item {
+	background: #ffffff;
+	border-radius: 12px;
+	margin-bottom: 16px;
+	overflow: hidden;
+	box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+	padding: 0 16px;
+}
+
+.item-header {
+	padding: 16px 0;
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+}
+
+.item-left {
+	display: flex;
+	align-items: center;
+	gap: 8px;
+}
+
+.document-icon {
+	width: 24px;
+	height: 24px;
+	background: #2979ff;
+	border-radius: 4px;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+}
+
+.item-id {
+	font-size: 14px;
+	color: #333;
+	font-weight: 500;
+}
+
+.item-details {
+	padding: 16px 0;
+}
+
+.detail-row {
+	display: flex;
+	align-items: flex-end;
+	justify-content: space-between;
+	margin-bottom: 8px;
+
+	&:last-child {
+		margin-bottom: 0;
+	}
+}
+
+.detail-label {
+	font-size: 12px;
+	color: #777777;
+	min-width: 60px;
+}
+
+.detail-value {
+	font-size: 12px;
+	color: #000000;
+	text-align: right;
+	flex: 1;
+	margin-left: 16px;
+}
+
+.detail-value.highlight {
+	color: #2979ff;
+	font-weight: 500;
+}
+
+.no-data {
+	padding: 40px 0;
+	text-align: center;
+	color: #999;
+}
+
+.action-buttons {
+	display: flex;
+	gap: 12px;
+	padding: 0 0 16px 0;
+	justify-content: space-between;
+}
+
+.action-btn {
+	flex: 1;
+}
+
+.filter-popup {
+	padding: 12px 12px 20px;
+}
+
+.switch-row {
+	padding: 12px 16px;
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+}
+
+.switch-label {
+	font-size: 14px;
+	color: #333;
+}
+
+.filter-actions {
+	display: flex;
+	gap: 12px;
+	padding: 12px 16px 16px;
+	justify-content: space-between;
+}
+
+.edit-container {
+	padding-bottom: 5rem;
+}
+
+.uploaded-list {
+	padding: 8px 16px 0 16px;
+}
+
+.uploaded-item {
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+	padding: 8px 0;
+	border-bottom: 1px solid #f5f5f5;
+}
+
+.file-name {
+	font-size: 12px;
+	color: #333;
+	margin-right: 8px;
+	flex: 1;
+	overflow: hidden;
+	text-overflow: ellipsis;
+	white-space: nowrap;
+}
+
+.tip-text {
+	padding: 4px 16px 0 16px;
+	font-size: 12px;
+	color: #888;
+}
+
+.footer-btns {
+	position: fixed;
+	left: 0;
+	right: 0;
+	bottom: 0;
+	background: #fff;
+	display: flex;
+	justify-content: space-around;
+	align-items: center;
+	padding: 0.75rem 0;
+	box-shadow: 0 -0.125rem 0.5rem rgba(0,0,0,0.05);
+	z-index: 1000;
+}
+.cancel-btn {
+	font-weight: 400;
+	font-size: 1rem;
+	color: #FFFFFF;
+	width: 6.375rem;
+	background: #C7C9CC;
+	box-shadow: 0 0.25rem 0.625rem 0 rgba(3,88,185,0.2);
+	border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
+}
+.save-btn {
+	font-weight: 400;
+	font-size: 1rem;
+	color: #FFFFFF;
+	width: 14rem;
+	background: linear-gradient( 140deg, #00BAFF 0%, #006CFB 100%);
+	box-shadow: 0 0.25rem 0.625rem 0 rgba(3,88,185,0.2);
+	border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
+}
+</style>
\ No newline at end of file
diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts
index f9f8629..db4dc32 100644
--- a/src/store/modules/user.ts
+++ b/src/store/modules/user.ts
@@ -2,6 +2,7 @@
 import { getToken, setToken, removeToken } from "@/utils/auth";
 import defAva from "@/static/images/profile.jpg";
 import { defineStore } from "pinia";
+import config from '@/config.js'
 
 export interface LoginForm {
   username: string;
@@ -46,7 +47,7 @@
           .then((res: any) => {
               const user = res.user
               let avatar = user.avatar || ""
-              avatar = import.meta.env.VITE_APP_BASE_API + '/profile/' + avatar
+              avatar = config.baseUrl + '/profile/' + avatar
               if (res.roles && res.roles.length > 0) { // 楠岃瘉杩斿洖鐨剅oles鏄惁鏄竴涓潪绌烘暟缁�
                   this.roles = res.roles
                   this.permissions = res.permissions

--
Gitblit v1.9.3