mes-vue/src/views/biz/order/index.vue
2026-05-25 15:46:29 +08:00

457 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="page-container">
<n-card>
<!-- 搜索表单 -->
<div class="search-form">
<n-form inline :model="searchForm" label-placement="left">
<n-form-item label="订单编号">
<n-input v-model:value="searchForm.orderNo" placeholder="请输入订单编号" clearable />
</n-form-item>
<n-form-item>
<n-space>
<n-button type="primary" @click="handleSearch">
<template #icon><n-icon><SearchOutline /></n-icon></template>
搜索
</n-button>
<n-button @click="handleReset">
<template #icon><n-icon><RefreshOutline /></n-icon></template>
重置
</n-button>
</n-space>
</n-form-item>
</n-form>
</div>
<!-- 工具栏 -->
<!-- <div class="table-toolbar">
<n-space>
<n-button type="primary" @click="handleAdd">
<template #icon><n-icon><AddOutline /></n-icon></template>
新增
</n-button>
<n-button @click="importModalVisible = true">
<template #icon><n-icon><CloudUploadOutline /></n-icon></template>
导入
</n-button>
<n-button @click="handleExport">
<template #icon><n-icon><DownloadOutline /></n-icon></template>
导出{{ selectedIds.length > 0 ? `(${selectedIds.length})` : '' }}
</n-button>
<n-button type="error" :disabled="selectedIds.length === 0" @click="handleBatchDelete">
<template #icon><n-icon><TrashOutline /></n-icon></template>
删除
</n-button>
</n-space>
</div> -->
<!-- 表格 -->
<n-data-table
:columns="columns"
:data="tableData"
:loading="loading"
:pagination="pagination"
:row-key="(row) => row.id"
:scroll-x="1200"
@update:page="handlePageChange"
@update:page-size="handlePageSizeChange"
@update:checked-row-keys="handleCheck"
/>
</n-card>
<!-- 新增/编辑弹窗 -->
<n-modal v-model:show="modalVisible" preset="card" :title="modalTitle" style="width: 600px">
<n-form ref="formRef" :model="formData" :rules="formRules" label-placement="left" label-width="100px">
<n-form-item label="订单编号" path="orderNo">
<n-input v-model:value="formData.orderNo" placeholder="请输入订单编号" />
</n-form-item>
<n-form-item label="购买人ID" path="userId">
<n-input v-model:value="formData.userId" placeholder="请输入购买人ID" />
</n-form-item>
<n-form-item label="商品id" path="productId">
<n-input v-model:value="formData.productId" placeholder="请输入商品id" />
</n-form-item>
<n-form-item label="购买份额" path="quota">
<n-input v-model:value="formData.quota" placeholder="请输入购买份额" />
</n-form-item>
<n-form-item label="总价" path="totalPrice">
<n-input v-model:value="formData.totalPrice" placeholder="请输入总价" />
</n-form-item>
<n-form-item label="单价" path="unitPrice">
<n-input v-model:value="formData.unitPrice" placeholder="请输入单价" />
</n-form-item>
<n-form-item label="购买状态" path="payStatus">
<n-select v-model:value="formData.payStatus" placeholder="请选择购买状态" :options="payStatusOptions" />
</n-form-item>
</n-form>
<template #footer>
<n-space justify="end">
<n-button @click="modalVisible = false">取消</n-button>
<n-button type="primary" @click="handleSubmit">确定</n-button>
</n-space>
</template>
</n-modal>
<!-- 导入弹窗 -->
<n-modal v-model:show="importModalVisible" preset="card" title="导入订单" style="width: 500px">
<n-space vertical>
<n-alert type="info">
<template #header>导入说明</template>
<ul style="margin: 0; padding-left: 16px; line-height: 1.8">
<li>请先下载导入模板,按模板格式填写数据</li>
<li>支持 .xlsx 或 .xls 格式</li>
</ul>
</n-alert>
<n-space>
<n-button type="primary" @click="handleDownloadTemplate">
<template #icon><n-icon><DownloadOutline /></n-icon></template>
下载模板
</n-button>
</n-space>
<n-upload :max="1" accept=".xlsx,.xls" :show-file-list="true" :custom-request="handleImportUpload">
<n-upload-dragger>
<div style="margin-bottom: 12px">
<n-icon size="48" :depth="3"><CloudUploadOutline /></n-icon>
</div>
<n-text style="font-size: 16px">点击或拖拽文件到此处上传</n-text>
<n-p depth="3" style="margin: 8px 0 0 0">支持 .xlsx 或 .xls 格式</n-p>
</n-upload-dragger>
</n-upload>
</n-space>
<template #footer>
<n-button @click="importModalVisible = false">关闭</n-button>
</template>
</n-modal>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, h, onMounted } from 'vue'
import { NButton, NSpace, NSelect ,NIcon, NUpload, useMessage, useDialog, type DataTableColumns, type UploadCustomRequestOptions } from 'naive-ui'
import { SearchOutline, RefreshOutline, AddOutline, TrashOutline, CreateOutline, CloudUploadOutline, DownloadOutline } from '@vicons/ionicons5'
import { orderApi, type Order } from '@/api/order'
import { dictDataApi } from '@/api/org'
const message = useMessage()
const dialog = useDialog()
// 搜索表单
const searchForm = reactive({
orderNo: null as string | null,
})
// 表格数据
const tableData = ref<Order[]>([])
const loading = ref(false)
const selectedIds = ref<number[]>([])
const pagination = reactive({
page: 1,
pageSize: 10,
itemCount: 0,
showSizePicker: true,
pageSizes: [10, 20, 50]
})
// 弹窗
const modalVisible = ref(false)
const modalTitle = ref('')
const importModalVisible = ref(false)
const formRef = ref()
const defaultFormData: Order = {
orderNo: '',
userId: undefined,
productId: undefined,
quota: undefined,
totalPrice: undefined,
unitPrice: undefined,
payStatus: undefined,
}
const formData = reactive<Order>({ ...defaultFormData })
// 字典选项(下拉框/单选框/复选框关联字典时使用)
const payStatusOptions = ref<{ label: string; value: any }[]>([])
// 表单校验规则
const formRules = {
}
// 表格列
const columns: DataTableColumns<Order> = [
// { type: 'selection' },
// { title: 'id', key: 'id' },
{ title: '订单编号', key: 'orderNo' },
{ title: '购买人', key: 'userName' },
{ title: '商品', key: 'productName' },
{ title: '购买份额', key: 'quota' },
{ title: '总价', key: 'totalPrice' },
{ title: '创建时间', key: 'createTime' },
// { title: '修改时间', key: 'updateTime'},
// { title: '创建人', key: 'createBy' },
// { title: '更新人', key: 'updateBy' },
{ title: '购买结果', key: ' payPhotoUrl',
render(row) {
return row.payPhotoUrl ? h('img', {
src: row.payPhotoUrl,
style: { width: '60px', height: '60px', objectFit: 'cover', borderRadius: '4px' }
}) : '-'
}
},
{ title: '购买状态', key: 'payStatus',
render(row) {
return h(NSelect,{
// 绑定当前行的status值双向绑定核心
value: row.payStatus,
// 选中值变化时,更新当前行数据
'onUpdate:value': (val) => {
row.payStatus = val
// 可在这里触发接口保存、校验等逻辑
Object.assign(formData, row)
handleAudit();
},
options: payStatusOptions.value, // 绑定下拉选项
disabled: row.payStatus !== 1
})
}
},
{
title: '操作',
key: 'actions',
width: 140,
fixed: 'right',
render(row) {
return h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'nowrap' } }, [
// h(NButton, { size: 'small', quaternary: true, onClick: () => handleEdit(row) }, {
// default: () => [h(NIcon, null, { default: () => h(CreateOutline) }), ' 编辑']
// }),
h(NButton, { size: 'small', quaternary: true, type: 'error', onClick: () => handleDelete(row), disabled: row.payStatus === 2 }, {
default: () => [h(NIcon, null, { default: () => h(TrashOutline) }), ' 删除']
})
])
}
}
]
// 加载数据
async function loadData() {
loading.value = true
try {
const res = await orderApi.page({
page: pagination.page,
pageSize: pagination.pageSize,
orderNo: searchForm.orderNo || undefined,
})
tableData.value = res.list
pagination.itemCount = res.total
} finally {
loading.value = false
}
}
// 搜索
function handleSearch() {
pagination.page = 1
loadData()
}
// 重置
function handleReset() {
searchForm.orderNo = null
handleSearch()
}
// 分页
function handlePageChange(page: number) {
pagination.page = page
loadData()
}
function handlePageSizeChange(pageSize: number) {
pagination.pageSize = pageSize
pagination.page = 1
loadData()
}
// 选择
function handleCheck(keys: Array<string | number>) {
selectedIds.value = keys as number[]
}
// 新增
function handleAdd() {
modalTitle.value = '新增订单'
Object.assign(formData, defaultFormData)
modalVisible.value = true
}
// 编辑
function handleEdit(row: Order) {
modalTitle.value = '编辑订单'
Object.assign(formData, row)
if (formData.createTime && typeof formData.createTime === 'string') {
formData.createTime = new Date(formData.createTime.replace(' ', 'T')).getTime()
}
if (formData.updateTime && typeof formData.updateTime === 'string') {
formData.updateTime = new Date(formData.updateTime.replace(' ', 'T')).getTime()
}
modalVisible.value = true
}
async function handleAudit() {
try {
const submitData = { ...formData } as Order
if (typeof submitData.createTime === 'number') {
submitData.createTime = new Date(submitData.createTime).toISOString().slice(0, 19).replace('T', ' ')
}
if (typeof submitData.updateTime === 'number') {
submitData.updateTime = new Date(submitData.updateTime).toISOString().slice(0, 19).replace('T', ' ')
}
await orderApi.audit(submitData)
message.success('修改成功')
loadData()
} catch (error) {
// 错误已在拦截器处理
}
}
// 提交
async function handleSubmit() {
await formRef.value?.validate()
try {
const submitData = { ...formData } as Order
if (typeof submitData.createTime === 'number') {
submitData.createTime = new Date(submitData.createTime).toISOString().slice(0, 19).replace('T', ' ')
}
if (typeof submitData.updateTime === 'number') {
submitData.updateTime = new Date(submitData.updateTime).toISOString().slice(0, 19).replace('T', ' ')
}
if (submitData.id) {
await orderApi.update(submitData)
message.success('修改成功')
} else {
await orderApi.create(submitData)
message.success('新增成功')
}
modalVisible.value = false
loadData()
} catch (error) {
// 错误已在拦截器处理
}
}
// 删除
function handleDelete(row: Order) {
dialog.warning({
title: '提示',
content: '确定要删除该记录吗?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: async () => {
try {
await orderApi.delete([row.id!])
message.success('删除成功')
loadData()
} catch (error) {
// 错误已在拦截器处理
}
}
})
}
// 批量删除
function handleBatchDelete() {
dialog.warning({
title: '提示',
content: `确定要删除选中的 ${selectedIds.value.length} 条记录吗?`,
positiveText: '确定',
negativeText: '取消',
onPositiveClick: async () => {
try {
await orderApi.delete(selectedIds.value)
message.success('删除成功')
selectedIds.value = []
loadData()
} catch (error) {
// 错误已在拦截器处理
}
}
})
}
// 导出
async function handleExport() {
try {
const params: Record<string, any> = {}
if (selectedIds.value.length > 0) params.ids = selectedIds.value
if (searchForm.orderNo != null) params.orderNo = searchForm.orderNo
const blob = await orderApi.export(params)
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = '订单数据.xlsx'
link.click()
window.URL.revokeObjectURL(url)
} catch (error) {
// 错误已在拦截器处理
}
}
// 下载导入模板
async function handleDownloadTemplate() {
try {
const blob = await orderApi.downloadTemplate()
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = '订单导入模板.xlsx'
link.click()
window.URL.revokeObjectURL(url)
} catch (error) {
// 错误已在拦截器处理
}
}
// 导入上传
async function handleImportUpload({ file }: UploadCustomRequestOptions) {
if (!file.file) return
try {
const result = await orderApi.importData(file.file)
if (result.fail > 0) {
dialog.warning({
title: '导入结果',
content: `成功: ${result.success} 条,失败: ${result.fail}\n错误信息: ${(result.errors || []).join('\n') || '无'}`,
positiveText: '确定'
})
} else {
message.success(`导入成功,共 ${result.success} 条数据`)
importModalVisible.value = false
}
loadData()
} catch (error) {
// 错误已在拦截器处理
}
}
// 加载字典选项
async function loadDictOptions() {
try {
const data = await dictDataApi.listByType('pay_status')
payStatusOptions.value = data.map(d => ({ label: d.dictLabel, value: (Number(d.dictValue) || d.dictValue) }))
} catch {}
}
onMounted(() => {
loadData()
loadDictOptions()
})
</script>
<style scoped>
.search-form {
margin-bottom: 16px;
}
.table-toolbar {
margin-bottom: 16px;
}
</style>