add: 订单管理的前端页面添加了,但是因为暂时不清楚订单的字段那些,后端也没有相对应的接口。

This commit is contained in:
Maple 2025-08-12 14:43:44 +08:00
parent a790551325
commit 7fa5beee2f
2 changed files with 215 additions and 1 deletions

View File

@ -7,7 +7,69 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
ApprovalAssistant: typeof import('./../components/ApprovalAssistant/index.vue')['default']
ApprovalMessageItem: typeof import('./../components/NotificationCenter/ApprovalMessageItem.vue')['default']
Avatar: typeof import('./../components/Avatar/index.vue')['default']
Breadcrumb: typeof import('./../components/Breadcrumb/index.vue')['default']
CellCopy: typeof import('./../components/CellCopy/index.vue')['default']
Chart: typeof import('./../components/Chart/index.vue')['default']
ColumnSetting: typeof import('./../components/GiTable/src/components/ColumnSetting.vue')['default']
CronForm: typeof import('./../components/GenCron/CronForm/index.vue')['default']
CronModal: typeof import('./../components/GenCron/CronModal/index.vue')['default']
DateRangePicker: typeof import('./../components/DateRangePicker/index.vue')['default']
DayForm: typeof import('./../components/GenCron/CronForm/component/day-form.vue')['default']
FilePreview: typeof import('./../components/FilePreview/index.vue')['default']
GiCellAvatar: typeof import('./../components/GiCell/GiCellAvatar.vue')['default']
GiCellGender: typeof import('./../components/GiCell/GiCellGender.vue')['default']
GiCellStatus: typeof import('./../components/GiCell/GiCellStatus.vue')['default']
GiCellTag: typeof import('./../components/GiCell/GiCellTag.vue')['default']
GiCellTags: typeof import('./../components/GiCell/GiCellTags.vue')['default']
GiCodeView: typeof import('./../components/GiCodeView/index.vue')['default']
GiDot: typeof import('./../components/GiDot/index.tsx')['default']
GiEditTable: typeof import('./../components/GiEditTable/GiEditTable.vue')['default']
GiFooter: typeof import('./../components/GiFooter/index.vue')['default']
GiForm: typeof import('./../components/GiForm/src/GiForm.vue')['default']
GiIconBox: typeof import('./../components/GiIconBox/index.vue')['default']
GiIconSelector: typeof import('./../components/GiIconSelector/index.vue')['default']
GiIframe: typeof import('./../components/GiIframe/index.vue')['default']
GiOption: typeof import('./../components/GiOption/index.vue')['default']
GiOptionItem: typeof import('./../components/GiOptionItem/index.vue')['default']
GiPageLayout: typeof import('./../components/GiPageLayout/index.vue')['default']
GiSpace: typeof import('./../components/GiSpace/index.vue')['default']
GiSplitButton: typeof import('./../components/GiSplitButton/index.vue')['default']
GiSplitPane: typeof import('./../components/GiSplitPane/index.vue')['default']
GiSplitPaneFlexibleBox: typeof import('./../components/GiSplitPane/components/GiSplitPaneFlexibleBox.vue')['default']
GiSvgIcon: typeof import('./../components/GiSvgIcon/index.vue')['default']
GiTable: typeof import('./../components/GiTable/src/GiTable.vue')['default']
GiTag: typeof import('./../components/GiTag/index.tsx')['default']
GiThemeBtn: typeof import('./../components/GiThemeBtn/index.vue')['default']
HourForm: typeof import('./../components/GenCron/CronForm/component/hour-form.vue')['default']
Icon403: typeof import('./../components/icons/Icon403.vue')['default']
Icon404: typeof import('./../components/icons/Icon404.vue')['default']
Icon500: typeof import('./../components/icons/Icon500.vue')['default']
IconBorders: typeof import('./../components/icons/IconBorders.vue')['default']
IconTableSize: typeof import('./../components/icons/IconTableSize.vue')['default']
IconTreeAdd: typeof import('./../components/icons/IconTreeAdd.vue')['default']
IconTreeReduce: typeof import('./../components/icons/IconTreeReduce.vue')['default']
ImageImport: typeof import('./../components/ImageImport/index.vue')['default']
ImageImportWizard: typeof import('./../components/ImageImportWizard/index.vue')['default']
IndustrialImageList: typeof import('./../components/IndustrialImageList/index.vue')['default']
JsonPretty: typeof import('./../components/JsonPretty/index.vue')['default']
MinuteForm: typeof import('./../components/GenCron/CronForm/component/minute-form.vue')['default']
MonthForm: typeof import('./../components/GenCron/CronForm/component/month-form.vue')['default']
NotificationCenter: typeof import('./../components/NotificationCenter/index.vue')['default']
ParentView: typeof import('./../components/ParentView/index.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SecondForm: typeof import('./../components/GenCron/CronForm/component/second-form.vue')['default']
SplitPanel: typeof import('./../components/SplitPanel/index.vue')['default']
TextCopy: typeof import('./../components/TextCopy/index.vue')['default']
TurbineGrid: typeof import('./../components/TurbineGrid/index.vue')['default']
UserSelect: typeof import('./../components/UserSelect/index.vue')['default']
Verify: typeof import('./../components/Verify/index.vue')['default']
VerifyPoints: typeof import('./../components/Verify/Verify/VerifyPoints.vue')['default']
VerifySlide: typeof import('./../components/Verify/Verify/VerifySlide.vue')['default']
WeekForm: typeof import('./../components/GenCron/CronForm/component/week-form.vue')['default']
YearForm: typeof import('./../components/GenCron/CronForm/component/year-form.vue')['default']
}
}

View File

@ -1,11 +1,163 @@
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import type { TableColumnData } from '@arco-design/web-vue'
//
type OrderStatus = '待支付' | '进行中' | '已完成' | '已取消'
interface OrderItem { id: string; orderNo: string; customer: string; amount: number; status: OrderStatus; createTime: string }
const StatusOptions: { label: string; value: OrderStatus }[] = [
{ label: '待支付', value: '待支付' },
{ label: '进行中', value: '进行中' },
{ label: '已完成', value: '已完成' },
{ label: '已取消', value: '已取消' },
]
//
const allOrders = ref<OrderItem[]>([])
const stats = computed(() => ({
total: allOrders.value.length,
pending: allOrders.value.filter(o=>o.status==='待支付').length,
progress: allOrders.value.filter(o=>o.status==='进行中').length,
done: allOrders.value.filter(o=>o.status==='已完成').length,
cancel: allOrders.value.filter(o=>o.status==='已取消').length,
}))
//
const statCards = computed(() => [
{ key: 'total', title: '订单总数', value: stats.value.total, color: '#2f54eb', iconChar: 'T' },
{ key: 'pending', title: '待支付', value: stats.value.pending, color: '#faad14', iconChar: 'P' },
{ key: 'progress', title: '进行中', value: stats.value.progress, color: '#1677ff', iconChar: 'R' },
{ key: 'done', title: '已完成', value: stats.value.done, color: '#52c41a', iconChar: 'D' },
{ key: 'cancel', title: '已取消', value: stats.value.cancel, color: '#8c8c8c', iconChar: 'C' },
])
//
const searchForm = reactive({ orderNo: '', status: '' as ''|OrderStatus, timeRange: [] as [string,string]|[], page: 1, size: 10 })
const filtered = computed(() => {
let list = allOrders.value.slice()
if (searchForm.orderNo) list = list.filter(o=>o.orderNo.includes(searchForm.orderNo.trim()))
if (searchForm.status) list = list.filter(o=>o.status===searchForm.status)
if (Array.isArray(searchForm.timeRange) && searchForm.timeRange.length===2) {
const [s,e] = searchForm.timeRange; const S=+new Date(s as any), E=+new Date(e as any)
list = list.filter(o=>{ const t=+new Date(o.createTime as any); return t>=S && t<=E })
}
return list
})
const paged = computed(() => {
const start = (searchForm.page-1)*searchForm.size
return filtered.value.slice(start, start+searchForm.size)
})
const pagination = reactive({ current: 1, pageSize: 10, total: 0, showTotal: true, showPageSize: true })
//
const columns: TableColumnData[] = [
{ title: '订单编号', dataIndex: 'orderNo', width: 180 },
{ title: '客户', dataIndex: 'customer', width: 160 },
{ title: '金额', dataIndex: 'amount', width: 120, render: ({record}:any)=>`${(record.amount||0).toLocaleString()}` },
{ title: '状态', dataIndex: 'status', width: 100, slotName: 'status' },
{ title: '下单时间', dataIndex: 'createTime', width: 180 },
{ title: '操作', slotName: 'action', width: 140, fixed: 'right' },
]
//
const loading = ref(false)
const search = () => { pagination.total = filtered.value.length }
const reset = () => { Object.assign(searchForm, { orderNo: '', status: '', timeRange: [], page: 1, size: 10 }); pagination.current=1; pagination.pageSize=10; search() }
const onPageChange = (p:number)=>{ searchForm.page=p; pagination.current=p }
const onPageSizeChange=(s:number)=>{ searchForm.size=s; searchForm.page=1; pagination.pageSize=s; pagination.current=1; search() }
const getStatusColor = (s:OrderStatus)=>({ '待支付':'orange','进行中':'blue','已完成':'green','已取消':'gray' }[s])
//
const showAdd = ref(false)
const newOrder = reactive<{orderNo:string;customer:string;amount:number;status:OrderStatus;createTime:string}>(
{ orderNo:'', customer:'', amount:0, status:'待支付', createTime:'' }
)
const openAdd = ()=>{ Object.assign(newOrder,{ orderNo:'',customer:'',amount:0,status:'待支付',createTime:new Date().toISOString().slice(0,19).replace('T',' ') }); showAdd.value=true }
const submitAdd = ()=>{
const id = Math.random().toString(36).slice(2)
allOrders.value.unshift({ id, ...newOrder })
showAdd.value=false; search()
}
//
const genMock = (n=60)=>{
const pick=<T,>(arr:T[])=>arr[Math.floor(Math.random()*arr.length)]
const now=Date.now()
return Array.from({length:n}).map((_,i)=>{
const offset = Math.floor(Math.random()*60)*86400000
const dt = new Date(now-offset).toISOString().slice(0,19).replace('T',' ')
const st = pick<OrderStatus>(['待支付','进行中','已完成','已取消'])
return { id:`O${i+1}`, orderNo:`NO${100000+i}`, customer:`客户${(i%20)+1}`, amount: Math.floor(Math.random()*50000)+1000, status: st, createTime: dt }
})
}
onMounted(()=>{ allOrders.value = genMock(88); search() })
</script>
<template>
<GiPageLayout>
<!-- 顶部统计美化 + 居中 + 独立容器 -->
<a-row :gutter="12" class="mb-3 stats-row">
<a-col :span="24">
<div class="stats-wrap">
<a-row :gutter="12" justify="center">
<a-col v-for="card in statCards" :key="card.key" :xs="12" :sm="12" :md="6" :lg="4">
<a-card hoverable class="stat-card">
<div class="stat-inner">
<div class="stat-icon" :style="{ background: card.color }">
<span>{{ card.iconChar }}</span>
</div>
<div class="stat-value">{{ card.value }}</div>
<div class="stat-title">{{ card.title }}</div>
</div>
</a-card>
</a-col>
</a-row>
</div>
</a-col>
</a-row>
<!-- 查询表单 + 新增按钮 -->
<GiForm :columns="[
{ field:'orderNo', label:'订单编号', type:'input', props:{placeholder:'支持模糊查询'} },
{ field:'status', label:'订单状态', type:'select', props:{ placeholder:'全部', options: StatusOptions } },
{ field:'timeRange', label:'订单时间', type:'range-picker', props:{ showTime:true, format:'YYYY-MM-DD HH:mm:ss' } },
]" v-model="searchForm" search size="medium" @search="search" @reset="reset">
<template #extra>
<a-button type="primary" @click="openAdd"><icon-plus/> 新增订单</a-button>
</template>
</GiForm>
<!-- 订单列表 -->
<GiTable :data="paged" :columns="columns" :loading="loading" :pagination="pagination"
@page-change="onPageChange" @page-size-change="onPageSizeChange" @refresh="search">
<template #status="{ record }"><a-tag :color="getStatusColor(record.status)">{{ record.status }}</a-tag></template>
<template #action="{ record }"><a-space>
<a-link @click="()=>{}">查看</a-link>
<a-link @click="()=>{}">编辑</a-link>
</a-space></template>
</GiTable>
<!-- 新增订单弹窗本地 -->
<a-modal v-model:visible="showAdd" title="新增订单" :width="520" @before-ok="submitAdd">
<a-form :model="newOrder" layout="vertical">
<a-form-item field="orderNo" label="订单编号"><a-input v-model="newOrder.orderNo" placeholder="NO123456"/></a-form-item>
<a-form-item field="customer" label="客户"><a-input v-model="newOrder.customer"/></a-form-item>
<a-form-item field="amount" label="金额"><a-input-number v-model="newOrder.amount" style="width:100%"/></a-form-item>
<a-form-item field="status" label="状态"><a-select v-model="newOrder.status" :options="StatusOptions"/></a-form-item>
<a-form-item field="createTime" label="下单时间"><a-date-picker v-model="newOrder.createTime" show-time style="width:100%"/></a-form-item>
</a-form>
</a-modal>
</GiPageLayout>
</template>
<style scoped lang="scss">
<style scoped>
.mb-3{ margin-bottom:12px; }
.stats-row{ }
.stats-wrap{ max-width: 1200px; margin: 0 auto; }
.stat-card{ border: 1px solid var(--color-border-2,#f0f0f0); background: linear-gradient(180deg,#ffffff 0%, #fafbff 100%); }
.stat-inner{ display:flex; flex-direction:column; align-items:center; justify-content:center; padding:18px 10px; text-align:center; }
.stat-icon{ width:44px; height:44px; border-radius:12px; color:#fff; display:flex; align-items:center; justify-content:center; font-weight:700; margin-bottom:8px; box-shadow: 0 6px 16px rgba(0,0,0,0.08); }
.stat-value{ font-size:28px; font-weight:700; color:#1d2129; line-height:1.2; }
.stat-title{ margin-top:6px; color:#86909c; }
</style>