更新样式
This commit is contained in:
Generated
+20
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user