149 lines
12 KiB
Vue
149 lines
12 KiB
Vue
<template>
|
||
<div>
|
||
<div class="n-layout-page-header"><n-card :bordered="false" title="订单列表">手动派单模式由客服匹配订单,会员确认时校验余额;自动派单模式仅匹配余额内订单并直接完成。</n-card></div>
|
||
<n-card :bordered="false" class="proCard">
|
||
<BasicForm ref="searchFormRef" @register="register" @submit="reloadTable" @reset="reloadTable" />
|
||
<BasicTable ref="actionRef" :columns="columns" :request="loadDataTable" :row-key="(row)=>row.id" :action-column="actionColumn" :scroll-x="scrollX" :resize-height-offset="-10000">
|
||
<template #tableTitle><n-space align="center"><n-switch v-model:value="autoRefresh"/><span>自动刷新</span><span v-if="autoRefresh">({{ refreshCountdown }}S)</span></n-space></template>
|
||
</BasicTable>
|
||
</n-card>
|
||
<n-modal v-model:show="showMatch" preset="card" title="匹配订单" class="match-order-modal" :mask-closable="false">
|
||
<n-spin :show="matchLoading">
|
||
<n-form label-placement="left" :label-width="90">
|
||
<n-form-item label="用户名称"><n-input :value="matchMember.username" disabled /></n-form-item>
|
||
<n-form-item label="账号余额"><n-input :value="yuanPlain(matchMember.availableAmount)" disabled /></n-form-item>
|
||
<n-form-item label="冻结金额"><n-input :value="yuanPlain(matchMember.frozenAmount)" disabled /></n-form-item>
|
||
<n-form-item label="选择商品">
|
||
<n-space vertical class="match-product-area">
|
||
<n-space align="center" wrap>
|
||
<span>商品名称</span><n-input v-model:value="productFilter.name" placeholder="商品名称" clearable class="filter-name" />
|
||
<span>商品价格</span><n-input-number v-model:value="productFilter.priceMin" :min="0" placeholder="最低价" clearable class="filter-price" />
|
||
<span>-</span><n-input-number v-model:value="productFilter.priceMax" :min="0" placeholder="最高价" clearable class="filter-price" />
|
||
<n-button type="primary" @click="searchProducts">筛选商品</n-button>
|
||
<n-button @click="clearProductFilter">清理缓存</n-button>
|
||
</n-space>
|
||
<n-data-table
|
||
:columns="productColumns"
|
||
:data="products"
|
||
:loading="productLoading"
|
||
:row-key="(row) => row.id"
|
||
:max-height="410"
|
||
striped
|
||
/>
|
||
<n-pagination
|
||
v-if="productPager.total > productPager.pageSize"
|
||
v-model:page="productPager.page"
|
||
:page-size="productPager.pageSize"
|
||
:item-count="productPager.total"
|
||
@update:page="loadProducts"
|
||
/>
|
||
</n-space>
|
||
</n-form-item>
|
||
<n-form-item label="商品单价">
|
||
<n-input :value="selectedProduct ? yuanPlain(selectedProduct.priceAmount) : ''" disabled placeholder="请先选择商品" />
|
||
</n-form-item>
|
||
<n-form-item label="商品数量">
|
||
<n-input-number v-model:value="matchForm.quantity" :min="1" :max="999" />
|
||
</n-form-item>
|
||
</n-form>
|
||
</n-spin>
|
||
<template #action>
|
||
<n-space>
|
||
<n-button @click="showMatch=false">取消</n-button>
|
||
<n-button type="primary" :loading="matchSubmitting" @click="confirmMatch">确定</n-button>
|
||
</n-space>
|
||
</template>
|
||
</n-modal>
|
||
<n-modal v-model:show="showAmount" preset="card" title="更改金额" class="amount-modal" :mask-closable="false">
|
||
<n-form label-placement="left" :label-width="90">
|
||
<n-form-item label="用户名称"><n-input :value="amountMember.username" disabled /></n-form-item>
|
||
<n-form-item label="账号余额"><n-input :value="yuanPlain(amountMember.availableAmount)" disabled /></n-form-item>
|
||
<n-form-item label="更改金额"><n-input-number v-model:value="amountForm.amount" :min="0.01" :precision="2" class="w-full" /></n-form-item>
|
||
<n-form-item label="更改数量"><n-input-number v-model:value="amountForm.quantity" :min="1" :max="999" class="w-full" /></n-form-item>
|
||
</n-form>
|
||
<template #action><n-space><n-button @click="showAmount=false">取消</n-button><n-button type="primary" :loading="amountSubmitting" @click="confirmAmount">确定</n-button></n-space></template>
|
||
</n-modal>
|
||
<n-modal v-model:show="showDetail" preset="card" title="订单详情" class="max-w-900px">
|
||
<n-descriptions v-if="detail" bordered label-placement="left" :column="2">
|
||
<n-descriptions-item label="订单号">{{ detail.orderNo }}</n-descriptions-item><n-descriptions-item label="会员">{{ detail.username }} / {{ detail.mobile }}</n-descriptions-item>
|
||
<n-descriptions-item label="商品/任务">{{ detail.productName || '-' }}</n-descriptions-item><n-descriptions-item label="规则等级ID">{{ detail.memberLevelId }}</n-descriptions-item>
|
||
<n-descriptions-item label="订单金额">{{ yuan(detail.totalAmount) }}</n-descriptions-item><n-descriptions-item label="佣金">{{ yuan(detail.commissionAmount) }}</n-descriptions-item>
|
||
<n-descriptions-item label="规则快照" :span="2"><n-code :code="JSON.stringify(detail.ruleSnapshot || {}, null, 2)" language="json" word-wrap /></n-descriptions-item>
|
||
</n-descriptions>
|
||
</n-modal>
|
||
</div>
|
||
</template>
|
||
<script lang="ts" setup>
|
||
import { computed, h, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue';
|
||
import { useRouter } from 'vue-router';
|
||
import { NButton, NImage, NTag, useDialog, useMessage } from 'naive-ui';
|
||
import { BasicTable, TableAction } from '@/components/Table';import { BasicForm,useForm } from '@/components/Form';import { adaTableScrollX } from '@/utils/hotgo';
|
||
import { TradeOrderForcePay,TradeOrderFreeze,TradeOrderList,TradeOrderMatch,TradeOrderMatchProducts,TradeOrderUpdateAmount,TradeOrderView } from '@/api/trade';import { columns,schemas,loadOptions } from './model';
|
||
const message=useMessage();const dialog=useDialog();const router=useRouter();const actionRef=ref(),searchFormRef=ref<any>({}),showDetail=ref(false),showMatch=ref(false),showAmount=ref(false),detail=ref<any>(),current=ref<any>(),autoRefresh=ref(false),refreshCountdown=ref(10);let timer:any;
|
||
const [register]=useForm({gridProps:{cols:'1 s:1 m:2 l:3 xl:5'},labelWidth:80,schemas});const loadDataTable=(res)=>TradeOrderList({...searchFormRef.value?.formModel,...res});const reloadTable=()=>actionRef.value?.reload();const yuan=(v)=>`¥${((v||0)/100).toFixed(2)}`;
|
||
const yuanPlain=(v)=>((v||0)/100).toFixed(2);
|
||
const matchLoading=ref(false),productLoading=ref(false),matchSubmitting=ref(false),products=ref<any[]>([]),selectedProduct=ref<any>();
|
||
const matchMember=reactive({id:0,username:'',availableAmount:0,frozenAmount:0});
|
||
const matchForm=reactive({id:0,productId:0,quantity:1});
|
||
const amountMember=reactive({username:'',availableAmount:0});
|
||
const amountForm=reactive<{id:number;amount:number|null;quantity:number}>({id:0,amount:null,quantity:1});
|
||
const amountSubmitting=ref(false);
|
||
const productFilter=reactive<{name:string;priceMin:number|null;priceMax:number|null}>({name:'',priceMin:null,priceMax:null});
|
||
const productPager=reactive({page:1,pageSize:10,total:0});
|
||
const productColumns=[
|
||
{title:'图片',key:'coverUrl',width:90,render:(row)=>h(NImage,{width:54,height:54,objectFit:'cover',src:row.coverUrl,previewDisabled:true})},
|
||
{title:'商品名称',key:'name',minWidth:300,ellipsis:{tooltip:true}},
|
||
{title:'商品单价',key:'priceAmount',width:130,sorter:'default',render:(row)=>yuanPlain(row.priceAmount)},
|
||
{title:'操作',key:'action',width:100,render:(row)=>selectedProduct.value?.id===row.id?h(NTag,{type:'success',bordered:false},{default:()=> '已选择'}):h(NButton,{size:'small',type:'primary',onClick:()=>selectProduct(row)},{default:()=> '选择'})},
|
||
];
|
||
async function openMatch(row){
|
||
current.value=row;Object.assign(matchMember,{id:row.memberId,username:row.username,availableAmount:row.availableAmount,frozenAmount:row.frozenAmount||0});
|
||
Object.assign(matchForm,{id:row.id,productId:0,quantity:1});Object.assign(productFilter,{name:'',priceMin:null,priceMax:null});selectedProduct.value=undefined;productPager.page=1;showMatch.value=true;await loadProducts();
|
||
}
|
||
async function loadProducts(){
|
||
productLoading.value=true;
|
||
try{const res:any=await TradeOrderMatchProducts({page:productPager.page,pageSize:productPager.pageSize,name:productFilter.name,priceMin:productFilter.priceMin,priceMax:productFilter.priceMax});products.value=res.list||[];productPager.total=res.totalCount||0}finally{productLoading.value=false}
|
||
}
|
||
function searchProducts(){productPager.page=1;loadProducts()}
|
||
function clearProductFilter(){Object.assign(productFilter,{name:'',priceMin:null,priceMax:null});productPager.page=1;selectedProduct.value=undefined;matchForm.productId=0;loadProducts();message.success('商品列表已刷新')}
|
||
function selectProduct(row){selectedProduct.value=row;matchForm.productId=row.id}
|
||
async function confirmMatch(){
|
||
if(!matchForm.productId){message.error('请先选择商品');return}
|
||
matchSubmitting.value=true;try{await TradeOrderMatch(matchForm);message.success('订单匹配成功');showMatch.value=false;reloadTable()}finally{matchSubmitting.value=false}
|
||
}
|
||
async function view(row){detail.value=await TradeOrderView({id:row.id});showDetail.value=true}
|
||
function openAmount(row){Object.assign(amountMember,{username:row.username,availableAmount:row.availableAmount});Object.assign(amountForm,{id:row.id,amount:Number(row.totalAmount||0)/100,quantity:row.quantity||1});showAmount.value=true}
|
||
async function confirmAmount(){if(!amountForm.amount||amountForm.amount<=0){message.error('请输入正确金额');return}amountSubmitting.value=true;try{await TradeOrderUpdateAmount({id:amountForm.id,amount:amountForm.amount.toFixed(2),quantity:amountForm.quantity});message.success('订单金额已更改');showAmount.value=false;reloadTable()}finally{amountSubmitting.value=false}}
|
||
function forcePay(row){dialog.warning({title:'强制付款',content:`确认对订单 ${row.orderNo} 执行强制付款吗?允许付款后余额为负数。`,positiveText:'确认付款',negativeText:'取消',onPositiveClick:async()=>{await TradeOrderForcePay({id:row.id});message.success('强制付款成功');reloadTable()}})}
|
||
function freezeOrder(row){dialog.warning({title:'冻结订单',content:`确认冻结订单 ${row.orderNo} 吗?冻结后会员需要联系客服处理。`,positiveText:'确认冻结',negativeText:'取消',onPositiveClick:async()=>{await TradeOrderFreeze({id:row.id});message.success('订单已冻结');reloadTable()}})}
|
||
const actionColumn=reactive({width:300,title:'操作',key:'action',fixed:'right',render:(row)=>{
|
||
const actions:any[]=[];
|
||
if(row.canMatch)actions.push({label:'匹配订单',type:'primary',onClick:()=>openMatch(row)});
|
||
if(row.canChangeAmount)actions.push({label:'更改金额',type:'primary',onClick:()=>openAmount(row)});
|
||
if(row.canForcePay)actions.push({label:'强制付款',type:'primary',onClick:()=>forcePay(row)});
|
||
if(row.canFreeze)actions.push({label:'冻结订单',type:'warning',onClick:()=>freezeOrder(row)});
|
||
const hasManualActions=row.canChangeAmount||row.canForcePay||row.canFreeze;
|
||
if(!hasManualActions&&row.orderType===2&&row.orderStatus===2&&row.canReview)actions.push({label:'审核任务',type:'warning',onClick:()=>router.push({name:'task_submission_list',query:{tradeOrderId:String(row.id)}})});
|
||
else if(!hasManualActions&&row.orderType===2&&row.orderStatus===2)actions.push({label:'等待会员付款',disabled:true,title:'等待会员完成付款'});
|
||
if(!hasManualActions)actions.push({label:'详情',onClick:()=>view(row)});
|
||
return h(TableAction as any,{style:'button',actions});
|
||
}});const scrollX=computed(()=>adaTableScrollX(columns,actionColumn.width));
|
||
watch(autoRefresh,(enabled)=>{
|
||
clearInterval(timer);refreshCountdown.value=10;
|
||
if(!enabled)return;
|
||
timer=setInterval(()=>{
|
||
refreshCountdown.value-=1;
|
||
if(refreshCountdown.value>0)return;
|
||
reloadTable();refreshCountdown.value=10;
|
||
},1000);
|
||
});
|
||
onBeforeUnmount(()=>clearInterval(timer));onMounted(loadOptions);
|
||
</script>
|
||
<style lang="less" scoped>
|
||
:deep(.match-order-modal) { width: min(1040px, 94vw); }
|
||
:deep(.amount-modal) { width: min(620px, 92vw); }
|
||
.match-product-area { width: 100%; }
|
||
.filter-name { width: 180px; }
|
||
.filter-price { width: 130px; }
|
||
</style>
|