feat: 积分商城-供应商
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>供应商管理后台</title></head><body><div id="app"></div><script type="module" src="/src/main.ts"></script></body></html>
|
||||
Generated
+2697
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "supplied-admin",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc --noEmit && vite build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
"ele-admin-plus": "^1.6.0",
|
||||
"element-plus": "^2.9.1",
|
||||
"pinia": "^3.0.1",
|
||||
"vue": "^3.5.13",
|
||||
"vue-router": "^4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"sass-embedded": "^1.103.1",
|
||||
"typescript": "^5.7.2",
|
||||
"vite": "^6.0.5",
|
||||
"vue-tsc": "^2.2.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<template><RouterView /></template>
|
||||
@@ -0,0 +1,16 @@
|
||||
const API_BASE = import.meta.env.VITE_SUPPLIED_ADMIN_API_BASE || 'http://127.0.0.1:8888/api/admin'
|
||||
|
||||
const normalizeBase = (base: string) => base.replace(/\/$/, '')
|
||||
|
||||
export const postAdmin = async <T>(name: string, body: Record<string, unknown>): Promise<T> => {
|
||||
const response = await fetch(`${normalizeBase(API_BASE)}/${name}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body)
|
||||
})
|
||||
const payload = await response.json().catch(() => ({}))
|
||||
if (!response.ok || payload.success === false) {
|
||||
throw new Error(payload.error?.message || payload.msg || '请求失败')
|
||||
}
|
||||
return payload.data as T
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<section class="page-card"><div class="page-title"><div><h2>{{ title }}</h2><p>{{ description }}</p></div></div><el-form :inline="true" class="query-form"><el-form-item label="供应商 ID"><el-input v-model="supplierId" placeholder="请输入供应商 ID" clearable /></el-form-item><el-form-item><el-button type="primary" :loading="loading" @click="loadRecords">查询</el-button><el-button @click="reset">重置</el-button></el-form-item></el-form><el-table v-loading="loading" :data="records" border><el-table-column prop="serviceOrderId" label="售后单号" min-width="170" /><el-table-column prop="serviceTypeText" label="售后类型" width="110" /><el-table-column prop="serviceStateText" label="状态" width="130" /><el-table-column prop="reason" label="原因" min-width="160" /><el-table-column prop="applyTime" label="申请时间" min-width="170" /><el-table-column label="操作" min-width="160" fixed="right"><template #default="{ row }"><el-button link type="primary" @click="openDetail(row)">详情</el-button><el-button v-if="scene === 'audit'" link type="success" @click="audit(row, 1)">通过</el-button><el-button v-if="scene === 'audit'" link type="danger" @click="audit(row, 2)">拒绝</el-button><el-button v-if="canReceive(row)" link type="success" @click="receive(row)">确认收货</el-button><el-button v-if="canShip(row)" link type="primary" @click="openShipDialog(row)">换货发货</el-button></template></el-table-column></el-table><el-empty v-if="!loading && queried && !records.length" description="暂无数据" /><el-dialog v-model="detailVisible" title="售后详情" width="620px"><pre class="detail-json">{{ JSON.stringify(detail, null, 2) }}</pre></el-dialog><el-dialog v-model="shipVisible" title="换货发货" width="460px"><el-form label-width="90px"><el-form-item label="快递公司"><el-input v-model="shipForm.logisticsCompany" /></el-form-item><el-form-item label="快递单号"><el-input v-model="shipForm.logisticsNo" /></el-form-item></el-form><template #footer><el-button @click="shipVisible = false">取消</el-button><el-button type="primary" @click="shipExchange">确认发货</el-button></template></el-dialog></section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { postAdmin } from '@/api/admin'
|
||||
const props = defineProps<{ scene: 'audit' | 'process' | 'history'; title: string; description: string }>()
|
||||
type AfterSale = { serviceOrderId: string; serviceState?: number; serviceType?: number; isSigned?: boolean; serviceTypeText?: string; serviceStateText?: string; reason?: string; applyTime?: string }
|
||||
const supplierId = ref(''); const records = ref<AfterSale[]>([]); const loading = ref(false); const queried = ref(false); const detailVisible = ref(false); const detail = ref<unknown>({}); const shipVisible = ref(false); const current = ref<AfterSale>(); const shipForm = ref({ logisticsCompany: '', logisticsNo: '' })
|
||||
const loadRecords = async () => { if (!supplierId.value.trim()) return ElMessage.warning('请输入供应商 ID'); loading.value = true; try { const data = await postAdmin<{ afterSaleItems?: AfterSale[] }>('merchant-after-sale-list', { supplierId: supplierId.value.trim(), scene: props.scene }); records.value = data.afterSaleItems || []; queried.value = true } catch (error) { ElMessage.error(error instanceof Error ? error.message : '售后查询失败') } finally { loading.value = false } }
|
||||
const reset = () => { supplierId.value = ''; records.value = []; queried.value = false }
|
||||
const openDetail = async (row: AfterSale) => { try { detail.value = await postAdmin('merchant-after-sale-detail', { supplierId: supplierId.value.trim(), serviceOrderId: row.serviceOrderId }); detailVisible.value = true } catch (error) { ElMessage.error(error instanceof Error ? error.message : '售后详情查询失败') } }
|
||||
const audit = async (row: AfterSale, auditStatus: number) => { try { await ElMessageBox.confirm(auditStatus === 1 ? '确认通过该售后申请?' : '确认拒绝该售后申请?', '售后审核'); await postAdmin('merchant-after-sale-audit', { supplierId: supplierId.value.trim(), serviceOrderId: row.serviceOrderId, auditStatus, auditRemark: auditStatus === 1 ? '审核通过' : '商家拒绝' }); ElMessage.success('处理成功'); await loadRecords() } catch (error) { if (error !== 'cancel') ElMessage.error(error instanceof Error ? error.message : '处理失败') } }
|
||||
const canReceive = (row: AfterSale) => props.scene === 'process' && Number(row.serviceState) === 3
|
||||
const canShip = (row: AfterSale) => props.scene === 'process' && Number(row.serviceType) === 5 && (Number(row.serviceState) === 4 || (row.isSigned === false && Number(row.serviceState) === 1))
|
||||
const receive = async (row: AfterSale) => { try { await ElMessageBox.confirm('确认已收到寄回商品?', '售后处理'); await postAdmin('merchant-after-sale-receive', { supplierId: supplierId.value.trim(), serviceOrderId: row.serviceOrderId }); ElMessage.success('已确认收货'); await loadRecords() } catch (error) { if (error !== 'cancel') ElMessage.error(error instanceof Error ? error.message : '操作失败') } }
|
||||
const openShipDialog = (row: AfterSale) => { current.value = row; shipForm.value = { logisticsCompany: '', logisticsNo: '' }; shipVisible.value = true }
|
||||
const shipExchange = async () => { if (!current.value || !shipForm.value.logisticsCompany || !shipForm.value.logisticsNo) return ElMessage.warning('请填写完整发货信息'); try { await postAdmin('merchant-after-sale-ship', { supplierId: supplierId.value.trim(), serviceOrderId: current.value.serviceOrderId, ...shipForm.value }); ElMessage.success('换货已发货'); shipVisible.value = false; await loadRecords() } catch (error) { ElMessage.error(error instanceof Error ? error.message : '发货失败') } }
|
||||
</script>
|
||||
|
||||
<style scoped>.detail-json { max-height: 420px; overflow: auto; padding: 12px; border-radius: 4px; background: #f5f7fa; white-space: pre-wrap; word-break: break-all; }</style>
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<el-container class="admin-layout">
|
||||
<el-aside :width="appStore.sidebarCollapsed ? '64px' : '224px'" class="sidebar">
|
||||
<div class="brand"><el-icon><ShoppingBag /></el-icon><span v-show="!appStore.sidebarCollapsed">供应商管理</span></div>
|
||||
<el-menu :collapse="appStore.sidebarCollapsed" :default-active="$route.path" background-color="#001529" text-color="#bfcbd9" active-text-color="#ffffff" router>
|
||||
<el-menu-item index="/reconciliation"><el-icon><Tickets /></el-icon><template #title>订单对账</template></el-menu-item>
|
||||
<el-sub-menu index="/orders"><template #title><el-icon><List /></el-icon><span>订单管理</span></template><el-menu-item index="/orders/list">订单列表</el-menu-item><el-menu-item index="/orders/after-sales/audit">售后审核</el-menu-item><el-menu-item index="/orders/after-sales/process">售后处理</el-menu-item><el-menu-item index="/orders/after-sales/history">售后记录</el-menu-item></el-sub-menu>
|
||||
<el-menu-item index="/products"><el-icon><Goods /></el-icon><template #title>商品管理</template></el-menu-item>
|
||||
</el-menu>
|
||||
</el-aside>
|
||||
<el-container><el-header class="header"><el-button text @click="appStore.toggleSidebar"><el-icon :size="18"><Expand v-if="appStore.sidebarCollapsed" /><Fold v-else /></el-icon></el-button><el-breadcrumb separator="/"><el-breadcrumb-item>供应商管理</el-breadcrumb-item><el-breadcrumb-item>{{ String($route.meta.title || '') }}</el-breadcrumb-item></el-breadcrumb><div class="header-user">管理员</div></el-header><el-main class="main"><RouterView /></el-main></el-container>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Expand, Fold, Goods, List, ShoppingBag, Tickets } from '@element-plus/icons-vue'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
const appStore = useAppStore()
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.admin-layout { min-height: 100vh; }.sidebar { overflow: hidden; transition: width .2s; background: #001529; }.brand { display: flex; align-items: center; gap: 10px; height: 56px; padding: 0 20px; color: #fff; font-size: 16px; font-weight: 600; white-space: nowrap; }.sidebar :deep(.el-menu) { border-right: 0; }.header { display: flex; align-items: center; gap: 16px; height: 56px; border-bottom: 1px solid #ebeef5; background: #fff; }.header-user { margin-left: auto; color: #606266; }.main { min-height: calc(100vh - 56px); padding: 20px; background: #f5f7fa; }
|
||||
</style>
|
||||
@@ -0,0 +1,10 @@
|
||||
import { createApp } from 'vue'
|
||||
import ElementPlus from 'element-plus'
|
||||
import ElePage from 'ele-admin-plus/es/ele-page/index.js'
|
||||
import 'element-plus/dist/index.css'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import { pinia } from './stores'
|
||||
import './styles/index.scss'
|
||||
|
||||
createApp(App).component('ElePage', ElePage).use(pinia).use(router).use(ElementPlus).mount('#app')
|
||||
@@ -0,0 +1,14 @@
|
||||
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'
|
||||
import AdminLayout from '@/layouts/AdminLayout.vue'
|
||||
|
||||
const routes: RouteRecordRaw[] = [{ path: '/', component: AdminLayout, redirect: '/reconciliation', children: [
|
||||
{ path: 'reconciliation', name: 'Reconciliation', component: () => import('@/views/reconciliation/index.vue'), meta: { title: '订单对账' } },
|
||||
{ path: 'orders', redirect: '/orders/list' },
|
||||
{ path: 'orders/list', name: 'Orders', component: () => import('@/views/orders/index.vue'), meta: { title: '订单列表' } },
|
||||
{ path: 'orders/after-sales/audit', name: 'AfterSaleAudit', component: () => import('@/views/after-sales/audit.vue'), meta: { title: '售后审核' } },
|
||||
{ path: 'orders/after-sales/process', name: 'AfterSaleProcess', component: () => import('@/views/after-sales/process.vue'), meta: { title: '售后处理' } },
|
||||
{ path: 'orders/after-sales/history', name: 'AfterSaleHistory', component: () => import('@/views/after-sales/history.vue'), meta: { title: '售后记录' } },
|
||||
{ path: 'products', name: 'Products', component: () => import('@/views/products/index.vue'), meta: { title: '商品管理' } }
|
||||
] }]
|
||||
|
||||
export default createRouter({ history: createWebHistory(), routes })
|
||||
@@ -0,0 +1,2 @@
|
||||
import { defineStore } from 'pinia'
|
||||
export const useAppStore = defineStore('app', { state: () => ({ sidebarCollapsed: false }), actions: { toggleSidebar() { this.sidebarCollapsed = !this.sidebarCollapsed } } })
|
||||
@@ -0,0 +1,2 @@
|
||||
import { createPinia } from 'pinia'
|
||||
export const pinia = createPinia()
|
||||
@@ -0,0 +1 @@
|
||||
html, body, #app { min-width: 1180px; min-height: 100%; margin: 0; } body { color: #303133; font-family: "Microsoft YaHei", Arial, sans-serif; }.page-card { min-height: calc(100vh - 96px); padding: 24px; border-radius: 8px; background: #fff; }.page-title { display: flex; align-items: center; justify-content: space-between; margin-bottom: 26px; }.page-title h2 { margin: 0 0 8px; font-size: 20px; }.page-title p { margin: 0; color: #909399; font-size: 14px; }.query-form { padding: 18px 18px 2px; margin-bottom: 20px; border-radius: 6px; background: #f8fafc; }
|
||||
@@ -0,0 +1,2 @@
|
||||
<template><MerchantAfterSaleTable scene="audit" title="售后审核" description="审核用户提交的退货、换货申请。" /></template>
|
||||
<script setup lang="ts">import MerchantAfterSaleTable from '@/components/MerchantAfterSaleTable.vue'</script>
|
||||
@@ -0,0 +1,2 @@
|
||||
<template><MerchantAfterSaleTable scene="history" title="售后记录" description="查看已完成或已关闭的售后记录。" /></template>
|
||||
<script setup lang="ts">import MerchantAfterSaleTable from '@/components/MerchantAfterSaleTable.vue'</script>
|
||||
@@ -0,0 +1,2 @@
|
||||
<template><MerchantAfterSaleTable scene="process" title="售后处理" description="处理已审核售后,包括确认收货和换货发货。" /></template>
|
||||
<script setup lang="ts">import MerchantAfterSaleTable from '@/components/MerchantAfterSaleTable.vue'</script>
|
||||
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<section class="page-card">
|
||||
<div class="page-title"><div><h2>订单列表</h2><p>查询订单并对待发货订单确认发货。</p></div></div>
|
||||
<el-form :inline="true" class="query-form"><el-form-item label="供应商"><el-select v-model="query.supplierId" placeholder="请选择供应商" filterable clearable><el-option v-for="supplier in suppliers" :key="supplier.supplierId" :label="`${supplier.supplierName}(${supplier.supplierId})`" :value="supplier.supplierId" /></el-select></el-form-item><el-form-item label="订单号"><el-input v-model="query.orderId" placeholder="订单编号或子订单号" clearable /></el-form-item><el-form-item label="工号"><el-input v-model="query.userId" placeholder="请输入工号" clearable /></el-form-item><el-form-item label="订单状态"><el-select v-model="query.orderState" placeholder="全部" clearable><el-option v-for="status in orderStates" :key="status.value" :label="status.label" :value="status.value" /></el-select></el-form-item><el-form-item><el-button type="primary" :loading="loading" @click="loadOrders">查询</el-button><el-button @click="reset">重置</el-button></el-form-item></el-form>
|
||||
<el-table v-loading="loading" :data="orders" border><el-table-column prop="orderNumberId" label="订单编号" min-width="160" /><el-table-column prop="orderId" label="子订单号" min-width="180" /><el-table-column prop="userId" label="工号" min-width="130" /><el-table-column label="订单状态" width="110"><template #default="{ row }">{{ stateText(row.orderState) }}</template></el-table-column><el-table-column prop="createTime" label="下单时间" min-width="170" /><el-table-column label="商品" min-width="180"><template #default="{ row }">{{ productNames(row) }}</template></el-table-column><el-table-column label="操作" width="120" fixed="right"><template #default="{ row }"><el-button v-if="row.orderState === 1" link type="primary" @click="openShipDialog(row)">确认发货</el-button><span v-else>-</span></template></el-table-column></el-table>
|
||||
<el-empty v-if="!loading && queried && !orders.length" description="暂无订单" />
|
||||
<el-dialog v-model="shipDialogVisible" title="确认发货" width="460px"><el-form label-width="90px"><el-form-item label="快递公司"><el-input v-model="shipForm.crrgBsnNm" placeholder="请输入快递公司" /></el-form-item><el-form-item label="快递单号"><el-input v-model="shipForm.logisticsOrderId" placeholder="请输入快递单号" /></el-form-item></el-form><template #footer><el-button @click="shipDialogVisible = false">取消</el-button><el-button type="primary" :loading="submitting" @click="submitShip">确认发货</el-button></template></el-dialog>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { postAdmin } from '@/api/admin'
|
||||
|
||||
type OrderProduct = { name?: string }
|
||||
type OrderItem = { orderId: string; orderNumberId?: string; userId?: string; orderState: number; createTime?: string; products?: OrderProduct[] }
|
||||
type SupplierItem = { supplierId: string; supplierName: string }
|
||||
const orderStates = [{ label: '待付款', value: 0 }, { label: '待发货', value: 1 }, { label: '已发货', value: 2 }, { label: '已取消', value: 3 }, { label: '已签收', value: 4 }, { label: '已完成', value: 5 }, { label: '换货', value: 6 }, { label: '已退款', value: 7 }]
|
||||
const query = reactive<{ supplierId: string; orderId: string; userId: string; orderState?: number }>({ supplierId: '', orderId: '', userId: '' })
|
||||
const suppliers = ref<SupplierItem[]>([]); const orders = ref<OrderItem[]>([]); const loading = ref(false); const queried = ref(false); const submitting = ref(false); const shipDialogVisible = ref(false); const currentOrder = ref<OrderItem>(); const shipForm = ref({ crrgBsnNm: '', logisticsOrderId: '' })
|
||||
const stateText = (state: number) => orderStates.find(item => item.value === state)?.label || '未知'
|
||||
const loadSuppliers = async () => { try { const data = await postAdmin<{ suppliers?: SupplierItem[] }>('merchant-supplier-list', {}); suppliers.value = data.suppliers || [] } catch (error) { ElMessage.error(error instanceof Error ? error.message : '供应商加载失败') } }
|
||||
const loadOrders = async () => { if (!query.supplierId) return ElMessage.warning('请选择供应商'); loading.value = true; try { const data = await postAdmin<{ orderItems?: OrderItem[] }>('merchant-order-list', query); orders.value = data.orderItems || []; queried.value = true } catch (error) { ElMessage.error(error instanceof Error ? error.message : '订单查询失败') } finally { loading.value = false } }
|
||||
const reset = () => { query.supplierId = ''; query.orderId = ''; query.userId = ''; query.orderState = undefined; orders.value = []; queried.value = false }
|
||||
const productNames = (order: OrderItem) => order.products?.map(item => item.name).filter(Boolean).join('、') || '-'
|
||||
const openShipDialog = (order: OrderItem) => { currentOrder.value = order; shipForm.value = { crrgBsnNm: '', logisticsOrderId: '' }; shipDialogVisible.value = true }
|
||||
const submitShip = async () => { if (!currentOrder.value || !shipForm.value.crrgBsnNm || !shipForm.value.logisticsOrderId) return ElMessage.warning('请填写完整发货信息'); submitting.value = true; try { await postAdmin('merchant-order-ship', { supplierId: query.supplierId, orderId: currentOrder.value.orderId, userId: currentOrder.value.userId, ...shipForm.value }); ElMessage.success('发货成功'); shipDialogVisible.value = false; await loadOrders() } catch (error) { ElMessage.error(error instanceof Error ? error.message : '发货失败') } finally { submitting.value = false } }
|
||||
onMounted(loadSuppliers)
|
||||
</script>
|
||||
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<section class="page-card">
|
||||
<div class="page-title"><div><h2>商品管理</h2><p>管理供应商商品的展示、库存和上下架状态。</p></div><el-button type="primary" @click="openCreate">新增商品</el-button></div>
|
||||
<el-form inline class="query-form" @submit.prevent><el-form-item label="商品"><el-input v-model="query.keyword" placeholder="商品名称或 SKU" clearable /></el-form-item><el-form-item label="分类"><el-select v-model="query.categoryId" placeholder="全部" clearable><el-option v-for="item in categories" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item><el-form-item><el-button type="primary" :loading="loading" @click="load">查询</el-button><el-button @click="reset">重置</el-button></el-form-item></el-form>
|
||||
<el-table :data="products" v-loading="loading" border><el-table-column prop="sku" label="SKU" min-width="140" /><el-table-column prop="name" label="商品名称" min-width="180" /><el-table-column prop="category" label="分类" width="120" /><el-table-column prop="price" label="售价" width="100" /><el-table-column prop="stock" label="库存" width="90" /><el-table-column prop="sortOrder" label="排序" width="80" /><el-table-column label="状态" width="90"><template #default="{ row }"><el-tag :type="row.enabled ? 'success' : 'info'">{{ row.enabled ? '上架' : '下架' }}</el-tag></template></el-table-column><el-table-column label="操作" width="130" fixed="right"><template #default="{ row }"><el-button link type="primary" @click="openEdit(row)">编辑</el-button><el-button link type="danger" @click="remove(row)">删除</el-button></template></el-table-column></el-table>
|
||||
<el-empty v-if="loaded && !products.length" description="暂无商品" />
|
||||
<el-dialog v-model="visible" :title="form.id ? '编辑商品' : '新增商品'" width="680px" destroy-on-close><el-form :model="form" label-width="90px"><el-row :gutter="16"><el-col :span="12"><el-form-item label="商品SKU" required><el-input v-model="form.sku" /></el-form-item></el-col><el-col :span="12"><el-form-item label="商品名称" required><el-input v-model="form.name" /></el-form-item></el-col><el-col :span="12"><el-form-item label="售价" required><el-input-number v-model="form.price" :min="0" :precision="2" /></el-form-item></el-col><el-col :span="12"><el-form-item label="原价"><el-input-number v-model="form.originPrice" :min="0" :precision="2" /></el-form-item></el-col><el-col :span="12"><el-form-item label="库存"><el-input-number v-model="form.stock" :min="0" :precision="0" /></el-form-item></el-col><el-col :span="12"><el-form-item label="排序"><el-input-number v-model="form.sortOrder" :min="0" :precision="0" /></el-form-item></el-col><el-col :span="12"><el-form-item label="分类"><el-select v-model="form.categoryId" @change="setCategory"><el-option v-for="item in categories" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item></el-col><el-col :span="12"><el-form-item label="状态"><el-switch v-model="form.enabled" active-text="上架" inactive-text="下架" /></el-form-item></el-col></el-row><el-form-item label="图片地址"><el-input v-model="form.imageUrl" /></el-form-item><el-form-item label="商品描述"><el-input v-model="form.description" type="textarea" :rows="3" /></el-form-item></el-form><template #footer><el-button @click="visible = false">取消</el-button><el-button type="primary" :loading="saving" @click="save">保存</el-button></template></el-dialog>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { postAdmin } from '@/api/admin'
|
||||
|
||||
type Category = { id: string; name: string }
|
||||
type Product = { id: number; sku: string; name: string; categoryId: string; category: string; price: string; originPrice: string; stock: number; sortOrder: number; enabled: boolean; imageUrl: string; description: string }
|
||||
type ProductForm = { id?: number; sku: string; name: string; price: number; originPrice?: number; stock: number; sortOrder: number; categoryId: string; category: string; imageUrl: string; description: string; enabled: boolean }
|
||||
const query = reactive({ keyword: '', categoryId: '' })
|
||||
const categories = ref<Category[]>([]); const products = ref<Product[]>([]); const loading = ref(false); const loaded = ref(false); const visible = ref(false); const saving = ref(false)
|
||||
const emptyForm = (): ProductForm => ({ sku: '', name: '', price: 0, stock: 0, sortOrder: 0, categoryId: '', category: '', imageUrl: '', description: '', enabled: true })
|
||||
const form = reactive<ProductForm>(emptyForm())
|
||||
const setForm = (value: ProductForm) => Object.assign(form, value)
|
||||
const load = async () => { loading.value = true; try { const data = await postAdmin<{ categories: Category[]; products: Product[] }>('merchant-product-list', query); categories.value = data.categories || []; products.value = data.products || []; loaded.value = true } catch (error) { ElMessage.error(error instanceof Error ? error.message : '商品查询失败') } finally { loading.value = false } }
|
||||
const reset = () => { query.keyword = ''; query.categoryId = ''; load() }
|
||||
const openCreate = () => { setForm(emptyForm()); visible.value = true }
|
||||
const openEdit = (product: Product) => { setForm({ id: product.id, sku: product.sku, name: product.name, price: Number(product.price), originPrice: product.originPrice ? Number(product.originPrice) : undefined, stock: product.stock, sortOrder: product.sortOrder, categoryId: product.categoryId, category: product.category, imageUrl: product.imageUrl, description: product.description, enabled: product.enabled }); visible.value = true }
|
||||
const setCategory = (categoryId: string) => { form.category = categories.value.find(item => item.id === categoryId)?.name || '' }
|
||||
const save = async () => { saving.value = true; try { await postAdmin('merchant-product-save', form); ElMessage.success('保存成功'); visible.value = false; await load() } catch (error) { ElMessage.error(error instanceof Error ? error.message : '保存失败') } finally { saving.value = false } }
|
||||
const remove = async (product: Product) => { try { await ElMessageBox.confirm(`确认删除商品“${product.name}”?`, '删除商品', { type: 'warning' }); await postAdmin('merchant-product-delete', { id: product.id }); ElMessage.success('删除成功'); await load() } catch (error) { if (error !== 'cancel' && error !== 'close') ElMessage.error(error instanceof Error ? error.message : '删除失败') } }
|
||||
onMounted(load)
|
||||
</script>
|
||||
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<ele-page>
|
||||
<section class="page-card">
|
||||
<div class="page-title"><div><h2>订单对账</h2><p>查看 ZHGH 回传的子订单对账结果,并处理异常订单。</p></div></div>
|
||||
<el-form inline class="query-form" @submit.prevent>
|
||||
<el-form-item label="供应商"><el-input v-model="query.supplierId" placeholder="供应商 ID" clearable /></el-form-item>
|
||||
<el-form-item label="子订单"><el-input v-model="query.orderId" placeholder="子订单 ID" clearable /></el-form-item>
|
||||
<el-form-item label="对账状态"><el-select v-model="query.reconciliationStatus" clearable placeholder="全部"><el-option label="未对账" :value="0" /><el-option label="对账成功" :value="1" /><el-option label="对账异常" :value="2" /></el-select></el-form-item>
|
||||
<el-form-item><el-button type="primary" :loading="loading" @click="load">查询</el-button><el-button @click="reset">重置</el-button></el-form-item>
|
||||
</el-form>
|
||||
<el-table :data="items" v-loading="loading" border>
|
||||
<el-table-column prop="orderId" label="子订单ID" min-width="180" />
|
||||
<el-table-column prop="mainOrderId" label="主订单ID" min-width="180" />
|
||||
<el-table-column prop="reconciliationStatus" label="对账状态" width="110"><template #default="{ row }"><el-tag :type="statusType(row.reconciliationStatus)">{{ statusText(row.reconciliationStatus) }}</el-tag></template></el-table-column>
|
||||
<el-table-column prop="diffReason" label="差异说明" min-width="220" show-overflow-tooltip />
|
||||
<el-table-column label="平台金额" min-width="120"><template #default="{ row }">{{ row.platformTotalPrice || '-' }}</template></el-table-column>
|
||||
<el-table-column label="供应商金额" min-width="120"><template #default="{ row }">{{ row.supplierTotalPrice || '-' }}</template></el-table-column>
|
||||
<el-table-column prop="reconciliationBatchNo" label="对账批次" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column prop="reconciliationTime" label="对账时间" min-width="170" />
|
||||
<el-table-column label="操作" width="90" fixed="right"><template #default="{ row }"><el-button link type="primary" @click="openEdit(row)">处理</el-button></template></el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="loaded && !items.length" description="暂无对账结果" />
|
||||
</section>
|
||||
<el-dialog v-model="dialogVisible" title="处理对账结果" width="460px">
|
||||
<el-form label-width="90px"><el-form-item label="子订单"><span>{{ editing?.orderId }}</span></el-form-item><el-form-item label="对账状态"><el-select v-model="form.reconciliationStatus"><el-option label="未对账" :value="0" /><el-option label="对账成功" :value="1" /><el-option label="对账异常" :value="2" /></el-select></el-form-item><el-form-item label="差异类型"><el-input-number v-model="form.diffType" :min="0" :precision="0" /></el-form-item><el-form-item label="差异说明"><el-input v-model="form.diffReason" type="textarea" :rows="3" /></el-form-item></el-form>
|
||||
<template #footer><el-button @click="dialogVisible = false">取消</el-button><el-button type="primary" :loading="saving" @click="save">保存</el-button></template>
|
||||
</el-dialog>
|
||||
</ele-page>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { postAdmin } from '@/api/admin'
|
||||
|
||||
type ReconciliationItem = { supplierId: string; orderId: string; mainOrderId: string; reconciliationStatus: number; diffType?: number; diffReason: string; platformTotalPrice: string; supplierTotalPrice: string; reconciliationBatchNo: string; reconciliationTime: string }
|
||||
const query = reactive<{ supplierId: string; orderId: string; reconciliationStatus?: number }>({ supplierId: '', orderId: '' })
|
||||
const items = ref<ReconciliationItem[]>([])
|
||||
const loading = ref(false)
|
||||
const loaded = ref(false)
|
||||
const dialogVisible = ref(false)
|
||||
const saving = ref(false)
|
||||
const editing = ref<ReconciliationItem>()
|
||||
const form = reactive<{ reconciliationStatus: number; diffType?: number; diffReason: string }>({ reconciliationStatus: 0, diffReason: '' })
|
||||
const statusText = (status: number) => ['未对账', '对账成功', '对账异常'][status] || '未知'
|
||||
const statusType = (status: number) => status === 1 ? 'success' : status === 2 ? 'danger' : 'info'
|
||||
|
||||
const load = async () => {
|
||||
if (!query.supplierId.trim()) return ElMessage.warning('请输入供应商 ID')
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await postAdmin<{ reconciliationItems: ReconciliationItem[] }>('merchant-reconciliation-list', query)
|
||||
items.value = data.reconciliationItems || []
|
||||
loaded.value = true
|
||||
} catch (error) { ElMessage.error(error instanceof Error ? error.message : '查询失败') } finally { loading.value = false }
|
||||
}
|
||||
const reset = () => { query.supplierId = ''; query.orderId = ''; query.reconciliationStatus = undefined; items.value = []; loaded.value = false }
|
||||
const openEdit = (row: ReconciliationItem) => { editing.value = row; form.reconciliationStatus = row.reconciliationStatus; form.diffType = row.diffType; form.diffReason = row.diffReason || ''; dialogVisible.value = true }
|
||||
const save = async () => {
|
||||
if (!editing.value) return
|
||||
saving.value = true
|
||||
try {
|
||||
await postAdmin('merchant-reconciliation-update', { supplierId: editing.value.supplierId, orderId: editing.value.orderId, reconciliationStatus: form.reconciliationStatus, diffType: form.diffType, diffReason: form.diffReason })
|
||||
ElMessage.success('保存成功'); dialogVisible.value = false; await load()
|
||||
} catch (error) { ElMessage.error(error instanceof Error ? error.message : '保存失败') } finally { saving.value = false }
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"compilerOptions": { "target": "ES2020", "module": "ESNext", "moduleResolution": "Bundler", "strict": true, "jsx": "preserve", "resolveJsonModule": true, "isolatedModules": true, "esModuleInterop": true, "lib": ["ES2020", "DOM"], "baseUrl": ".", "paths": { "@/*": ["src/*"] } },
|
||||
"include": ["src/**/*.ts", "src/**/*.vue", "vite.config.ts"]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
export default defineConfig({ plugins: [vue()], resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) } } })
|
||||
Reference in New Issue
Block a user