更新样式

This commit is contained in:
2026-07-21 11:07:33 +08:00
parent a78666177a
commit dd1e55aea3
4 changed files with 88 additions and 26 deletions
+20
View File
@@ -20,6 +20,7 @@
"marked": "^16.4.1",
"os": "^0.1.2",
"pinia": "^3.0.3",
"v-viewer": "^3.0.11",
"vue": "^3.5.22",
"vue-img-cutter": "^3.0.7",
"vue-router": "^4.6.3",
@@ -7079,6 +7080,25 @@
"uuid": "dist/bin/uuid"
}
},
"node_modules/v-viewer": {
"version": "3.0.11",
"resolved": "https://registry.npmmirror.com/v-viewer/-/v-viewer-3.0.11.tgz",
"integrity": "sha512-E8LOdAxhzuktt4HB3PswVCccQ1Q1sYHYnLsS6zaJISpb5EvmAFs5sYNfXnDLFxVb5DQ82v4ZlGxkYlseXwWRJw==",
"license": "MIT",
"dependencies": {
"lodash": "^4.17.21",
"viewerjs": "^1.9.0"
},
"peerDependencies": {
"vue": "^3.0.0"
}
},
"node_modules/viewerjs": {
"version": "1.11.8",
"resolved": "https://registry.npmmirror.com/viewerjs/-/viewerjs-1.11.8.tgz",
"integrity": "sha512-PLO477UwNThzMek7EQhr7+YONfbgFKj+E6J/+Vr+USzY6cN1WNGQcft0mlIepQXATquvDmH1dOx7aokNyOx/vw==",
"license": "MIT"
},
"node_modules/vite": {
"version": "7.3.1",
"resolved": "https://registry.npmmirror.com/vite/-/vite-7.3.1.tgz",
+1
View File
@@ -21,6 +21,7 @@
"marked": "^16.4.1",
"os": "^0.1.2",
"pinia": "^3.0.3",
"v-viewer": "^3.0.11",
"vue": "^3.5.22",
"vue-img-cutter": "^3.0.7",
"vue-router": "^4.6.3",
+3
View File
@@ -5,6 +5,8 @@ import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import 'element-plus/dist/index.css'
// 导入 Element Plus 暗黑模式样式
import 'element-plus/theme-chalk/dark/css-vars.css'
import 'viewerjs/dist/viewer.css'
import VueViewer from 'v-viewer'
import '@/assets/less/index.less'
import '@/assets/css/all.min.css'
import '@/assets/js/all.min.js'
@@ -27,6 +29,7 @@ for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.use(pinia)
app.use(router)
app.use(VueViewer)
// 初始化主题(必须在挂载前执行)
// initTheme()
@@ -91,6 +91,15 @@
/>
<i v-else class="fa-regular fa-file-pdf receipt-thumb-pdf"></i>
<span>{{ receipt.name }}</span>
<el-button
v-if="isDraft"
link
type="danger"
class="receipt-delete-btn"
@click.stop="removeReceipt(receipt)"
>
<i class="fa-solid fa-trash"></i>
</el-button>
</button>
</aside>
@@ -98,17 +107,15 @@
<el-image
v-if="isReceiptImage"
:src="receiptPreviewUrl"
:preview-src-list="[receiptPreviewUrl]"
fit="contain"
class="receipt-image"
preview-teleported
/>
<iframe v-else :src="receiptPreviewUrl" title="票据 PDF 预览" class="receipt-pdf"></iframe>
</main>
</div>
</el-dialog>
<h4 class="detail-title">审批记录</h4>
<h4 class="detail-title" style="margin-top: 20px;">审批记录</h4>
<el-timeline v-if="records.length">
<el-timeline-item
v-for="record in records"
@@ -162,20 +169,24 @@ const receiptPreviewUrl = ref('')
const receiptPreviewType = ref('')
const receiptPreviewItemId = ref(0)
const receiptList = computed(() =>
(props.detail?.items || [])
.filter(item => item.receipt_url)
.map(item => {
const url = resolveReceiptUrl(item.receipt_url)
const isImage = /\.(jpe?g|png|gif|bmp|webp)(\?|#|$)/i.test(item.receipt_url || '')
return {
id: item.id,
url,
isImage,
name: item.expense_type || `票据 ${item.id}`
}
const receiptList = computed(() => {
const items = props.detail?.items || []
const list = []
items.forEach(item => {
if (!item.receipt_url) return
const urls = item.receipt_url.split(',').filter(Boolean)
urls.forEach((url, index) => {
list.push({
id: `${item.id}-${index}`,
originalId: item.id, // 记录原始明细ID,用于删除时定位
url: resolveReceiptUrl(url),
isImage: /\.(jpe?g|png|gif|bmp|webp)(\?|#|$)/i.test(url || ''),
name: `${item.expense_type || '票据'} ${index + 1}`
})
})
})
return list
})
)
const isReceiptImage = computed(() => receiptPreviewType.value.startsWith('image/'))
@@ -192,10 +203,18 @@ const selectReceipt = receipt => {
}
const openReceiptPreview = row => {
const receipt = receiptList.value.find(item => item.id === row.id)
if (!receipt) return
selectReceipt(receipt)
// 查找列表项,注意这里的 row.id 可能是原始记录ID,或者是多票据生成出的唯一ID
// 如果是从表格点击查看,row 对应的是明细项,其本身可能包含多个票据
// 我们直接找到该明细对应的第一张票据并打开预览窗口
const firstReceipt = receiptList.value.find(item => item.originalId === row.id)
if (!firstReceipt) {
ElMessage.warning('暂无票据可查看')
return
}
receiptPreviewVisible.value = true
selectReceipt(firstReceipt)
}
const openAddItem = () => {
@@ -234,6 +253,11 @@ const uploadInvoice = async (row, file) => {
throw new Error(uploadResponse?.msg || '发票上传失败')
}
const newUrl = uploadResponse?.data?.url || uploadResponse?.url || ''
// 获取当前已有的 URL 列表
const existingUrls = row.receipt_url ? row.receipt_url.split(',').filter(Boolean) : []
existingUrls.push(newUrl)
await updateReimbursementItem(props.detail.id, row.id, {
expense_type: row.expense_type,
expense_date: row.expense_date,
@@ -242,7 +266,7 @@ const uploadInvoice = async (row, file) => {
unit_price: row.unit_price || 0,
description: row.description || '',
receipt_flag: 1,
receipt_url: uploadResponse?.data?.url || uploadResponse?.url || ''
receipt_url: existingUrls.join(',')
})
ElMessage.success('发票上传成功')
emit('changed', props.detail.id)
@@ -253,14 +277,26 @@ const uploadInvoice = async (row, file) => {
}
}
const removeItem = async row => {
const removeReceipt = async (receipt) => {
try {
await ElMessageBox.confirm(`确认删除“${row.expense_type}”这条费用明细吗?`, '删除确认', { type: 'warning' })
await deleteReimbursementItem(props.detail.id, row.id)
ElMessage.success('费用明细已删除')
await ElMessageBox.confirm('确认删除该票据吗?', '删除确认', { type: 'warning' })
// 通过原始 ID 查找对应的明细行
const row = props.detail.items.find(item => item.id === receipt.originalId)
if (!row) return
// 获取并过滤掉要删除的 URL
const existingUrls = row.receipt_url ? row.receipt_url.split(',').filter(Boolean) : []
const updatedUrls = existingUrls.filter(url => resolveReceiptUrl(url) !== receipt.url)
await updateReimbursementItem(props.detail.id, row.id, {
...row,
receipt_flag: updatedUrls.length > 0 ? 1 : 0,
receipt_url: updatedUrls.join(',')
})
ElMessage.success('票据已移除')
emit('changed', props.detail.id)
} catch (error) {
if (error !== 'cancel' && error !== 'close') ElMessage.error(error?.message || '删除费用明细失败')
if (error !== 'cancel' && error !== 'close') ElMessage.error('移除票据失败')
}
}
@@ -304,13 +340,14 @@ const formatDateTime = value => value ? new Date(value).toLocaleString('zh-CN',
}
.receipt-list {
flex: 0 0 180px;
flex: 0 0 200px;
display: flex;
flex-direction: column;
gap: 10px;
padding-right: 12px;
border-right: 1px solid var(--el-border-color-light);
overflow-y: auto;
max-height: 620px;
}
.receipt-thumb {
@@ -370,6 +407,7 @@ const formatDateTime = value => value ? new Date(value).toLocaleString('zh-CN',
max-width: 100%;
max-height: 620px;
cursor: zoom-in;
pointer-events: auto; /* 确保点击事件可以传递 */
}
.receipt-pdf {