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