feat: 积分商城-供应商-对账-发票3
This commit is contained in:
@@ -1,14 +1,23 @@
|
||||
import { clearAdminSession, readAdminSession } from '@/utils/auth'
|
||||
|
||||
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 token = readAdminSession()?.token
|
||||
const response = await fetch(`${normalizeBase(API_BASE)}/${name}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
headers: { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) },
|
||||
body: JSON.stringify(body)
|
||||
})
|
||||
const payload = await response.json().catch(() => ({}))
|
||||
if (response.status === 401 && name !== 'login') {
|
||||
clearAdminSession()
|
||||
if (name !== 'admin-session' && window.location.pathname !== '/login') {
|
||||
window.location.replace(`/login?redirect=${encodeURIComponent(window.location.pathname + window.location.search)}`)
|
||||
}
|
||||
}
|
||||
if (!response.ok || payload.success === false) {
|
||||
throw new Error(payload.error?.message || payload.msg || '请求失败')
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<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>
|
||||
<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><template #empty><el-empty :image-size="80" description="暂无售后记录" /></template></el-table><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">
|
||||
|
||||
@@ -2,22 +2,70 @@
|
||||
<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 :collapse="appStore.sidebarCollapsed" :default-active="$route.path" background-color="#152238" text-color="#afbed3" 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><el-header class="header"><el-button text aria-label="切换侧栏" @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"><span class="user-avatar" aria-hidden="true">{{ (currentUser?.nickname || currentUser?.username || '管').slice(0, 1) }}</span><span>{{ currentUser?.nickname || currentUser?.username }}</span><el-button text type="primary" :loading="loggingOut" @click="logout">退出登录</el-button></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'
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { postAdmin } from '@/api/admin'
|
||||
import { clearAdminSession, readAdminSession } from '@/utils/auth'
|
||||
const appStore = useAppStore()
|
||||
const router = useRouter()
|
||||
const currentUser = readAdminSession()?.user
|
||||
const loggingOut = ref(false)
|
||||
const logout = async () => {
|
||||
if (loggingOut.value) return
|
||||
loggingOut.value = true
|
||||
try {
|
||||
await postAdmin('admin-logout', {})
|
||||
clearAdminSession()
|
||||
await router.replace('/login')
|
||||
} catch (error) {
|
||||
ElMessage.error(error instanceof Error ? error.message : '退出失败,请稍后重试')
|
||||
} finally {
|
||||
loggingOut.value = false
|
||||
}
|
||||
}
|
||||
</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; }
|
||||
.admin-layout { min-height: 100vh; }
|
||||
.sidebar { position: sticky; top: 0; height: 100vh; flex-shrink: 0; overflow-x: hidden; overflow-y: auto; background: #152238; transition: width .2s ease; }
|
||||
.brand { display: flex; align-items: center; gap: 12px; height: 80px; padding: 0 20px; color: #f5f8ff; font-size: 17px; font-weight: 600; letter-spacing: .5px; white-space: nowrap; }
|
||||
.brand > .el-icon { width: 32px; height: 32px; flex-shrink: 0; border-radius: 9px; background: #2a66d8; color: #fff; font-size: 21px; }
|
||||
.sidebar :deep(.el-menu) { border-right: 0; background: transparent; }
|
||||
.sidebar > :deep(.el-menu) { padding: 8px; }
|
||||
.sidebar :deep(.el-menu-item), .sidebar :deep(.el-sub-menu__title) { height: 46px; margin-bottom: 5px; border-radius: 7px; line-height: 46px; font-size: 14px; }
|
||||
.sidebar :deep(.el-menu-item:hover), .sidebar :deep(.el-sub-menu__title:hover) { color: #fff; background: #21334e; }
|
||||
.sidebar :deep(.el-menu-item.is-active) { background: #2862cb; color: #fff; box-shadow: 0 4px 12px rgba(0, 0, 0, .12); }
|
||||
.sidebar :deep(.el-sub-menu .el-menu-item) { min-width: 0; font-size: 13px; }
|
||||
.sidebar :deep(.el-menu--collapse) { width: 64px; }
|
||||
.sidebar :deep(.el-menu--collapse .el-menu-item), .sidebar :deep(.el-menu--collapse .el-sub-menu__title) { padding: 0 12px !important; }
|
||||
.admin-layout > .el-container { min-width: 0; }
|
||||
.header { position: sticky; z-index: 20; top: 0; display: flex; align-items: center; gap: 18px; height: 64px; padding: 0 28px; border-bottom: 1px solid #e7ecf3; background: #fff; }
|
||||
.header > .el-button { width: 34px; height: 34px; padding: 0; color: #69778e; background: #f4f6fa; }
|
||||
.header :deep(.el-breadcrumb) { font-size: 13px; }
|
||||
.header-user { display: flex; align-items: center; gap: 12px; margin-left: auto; color: #526078; font-size: 13px; white-space: nowrap; }
|
||||
.user-avatar { display: grid; width: 32px; height: 32px; place-items: center; border: 1px solid #dce7fb; border-radius: 50%; background: #edf3fd; color: #2463d4; font-weight: 600; }
|
||||
.header-user > .el-button { margin-left: 4px; font-size: 12px; }
|
||||
.main { min-width: 0; min-height: calc(100vh - 64px); padding: 24px; background: #f3f5f9; }
|
||||
.main :deep(.ele-page) { padding: 0; }
|
||||
@media (max-width: 1100px) { .header { padding: 0 20px; } .main { padding: 18px; } }
|
||||
@media (max-width: 767px) {
|
||||
.header { gap: 10px; padding: 0 12px; }
|
||||
.header :deep(.el-breadcrumb) { display: none; }
|
||||
.header-user { gap: 8px; }
|
||||
.main { padding: 12px; }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'
|
||||
import AdminLayout from '@/layouts/AdminLayout.vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { postAdmin } from '@/api/admin'
|
||||
import { clearAdminSession, readAdminSession } from '@/utils/auth'
|
||||
|
||||
const routes: RouteRecordRaw[] = [{ path: '/', component: AdminLayout, redirect: '/reconciliation', children: [
|
||||
const routes: RouteRecordRaw[] = [{ path: '/login', name: 'Login', component: () => import('@/views/login/index.vue'), meta: { title: '登录' } }, { 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: '订单列表' } },
|
||||
@@ -11,4 +14,20 @@ const routes: RouteRecordRaw[] = [{ path: '/', component: AdminLayout, redirect:
|
||||
{ path: 'products', name: 'Products', component: () => import('@/views/products/index.vue'), meta: { title: '商品管理' } }
|
||||
] }]
|
||||
|
||||
export default createRouter({ history: createWebHistory(), routes })
|
||||
const router = createRouter({ history: createWebHistory(), routes })
|
||||
|
||||
router.beforeEach(async to => {
|
||||
if (to.name === 'Login') return true
|
||||
if (readAdminSession()) {
|
||||
try {
|
||||
await postAdmin('admin-session', {})
|
||||
return true
|
||||
} catch (error) {
|
||||
clearAdminSession()
|
||||
ElMessage.error(error instanceof Error ? error.message : '登录状态校验失败,请重新登录')
|
||||
}
|
||||
}
|
||||
return { name: 'Login', query: { redirect: to.fullPath } }
|
||||
})
|
||||
|
||||
export default router
|
||||
|
||||
@@ -1 +1,97 @@
|
||||
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; }
|
||||
:root {
|
||||
--el-color-primary: #2463d4;
|
||||
--el-color-primary-light-3: #668fdf;
|
||||
--el-color-primary-light-5: #91b1ea;
|
||||
--el-color-primary-light-7: #bed0f2;
|
||||
--el-color-primary-light-8: #d3e0f7;
|
||||
--el-color-primary-light-9: #edf3fd;
|
||||
--el-color-primary-dark-2: #1d4faa;
|
||||
--el-border-radius-base: 7px;
|
||||
--el-border-color: #dce3ec;
|
||||
--el-border-color-light: #e9eef5;
|
||||
--el-text-color-primary: #202e45;
|
||||
--el-text-color-regular: #526078;
|
||||
--el-text-color-secondary: #78859a;
|
||||
--el-font-family: "Microsoft YaHei", "PingFang SC", Arial, sans-serif;
|
||||
}
|
||||
|
||||
html, body, #app { min-width: 360px; min-height: 100%; margin: 0; }
|
||||
body { color: var(--el-text-color-primary); font-family: var(--el-font-family); background: #f3f5f9; -webkit-font-smoothing: antialiased; }
|
||||
*, *::before, *::after { box-sizing: border-box; }
|
||||
button, input, textarea { font: inherit; }
|
||||
:focus-visible { outline: 2px solid #2463d4; outline-offset: 3px; }
|
||||
|
||||
.page-card {
|
||||
min-width: 0;
|
||||
min-height: calc(100vh - 112px);
|
||||
padding: 28px;
|
||||
border: 1px solid #e5eaf2;
|
||||
border-radius: 14px;
|
||||
background: #fff;
|
||||
box-shadow: 0 3px 16px rgba(30, 50, 85, .035);
|
||||
}
|
||||
.page-title { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 16px; margin-bottom: 24px; }
|
||||
.page-title h2 { margin: 0 0 8px; color: #202e45; font-size: 22px; font-weight: 600; letter-spacing: .5px; }
|
||||
.page-title p { margin: 0; color: #78859a; font-size: 13px; line-height: 1.7; }
|
||||
.query-form.el-form--inline { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 16px 20px; padding: 20px; margin-bottom: 24px; border: 1px solid #e9eef5; border-radius: 10px; background: #f8fafc; }
|
||||
.query-form.el-form--inline .el-form-item { display: flex; flex-direction: column; align-items: stretch; margin: 0; }
|
||||
.query-form .el-form-item__label { justify-content: flex-start; height: auto; padding: 0 0 8px; color: #526078; font-size: 13px; line-height: 18px; }
|
||||
.query-form .el-form-item__content { min-height: 36px; }
|
||||
.query-form .el-input, .query-form .el-select { width: 208px; max-width: 100%; }
|
||||
.query-form .el-button { min-width: 76px; height: 36px; }
|
||||
.el-input__wrapper, .el-select__wrapper { min-height: 36px; }
|
||||
.el-textarea__inner { padding: 10px 12px; line-height: 1.6; }
|
||||
.page-title > .el-button { height: 36px; padding: 0 18px; }
|
||||
.el-button { font-weight: 500; }
|
||||
.el-button:not(.is-link):not(.is-text) { border-radius: 7px; }
|
||||
|
||||
.page-card .el-table {
|
||||
--el-table-border-color: #e9eef5;
|
||||
--el-table-header-bg-color: #f7f9fc;
|
||||
--el-table-header-text-color: #526078;
|
||||
--el-table-row-hover-bg-color: #f3f7ff;
|
||||
overflow: hidden;
|
||||
border-radius: 9px;
|
||||
color: #3f4d63;
|
||||
font-size: 13px;
|
||||
}
|
||||
.page-card .el-table th.el-table__cell { padding: 13px 0; font-size: 12px; font-weight: 600; }
|
||||
.page-card .el-table td.el-table__cell { padding: 15px 0; }
|
||||
.page-card .el-table .cell { padding-right: 16px; padding-left: 16px; line-height: 22px; }
|
||||
.page-card .el-table .el-button.is-link { font-size: 13px; }
|
||||
.page-card .el-table .el-table__empty-block { min-height: 240px; }
|
||||
.page-card .el-table .el-table__empty-text { width: 100%; line-height: normal; }
|
||||
.page-card .el-empty { padding: 36px 0; }
|
||||
.el-empty__description p { color: #8793a5; font-size: 13px; }
|
||||
.page-card .el-tag { height: 25px; padding: 0 9px; border-color: transparent; border-radius: 5px; font-size: 12px; font-weight: 500; }
|
||||
.page-card .el-pagination { justify-content: flex-end; flex-wrap: wrap; gap: 8px; padding-top: 20px; margin-top: 20px; border-top: 1px solid #edf0f5; font-variant-numeric: tabular-nums; }
|
||||
.page-card .el-pagination .el-pager li { border-radius: 6px; }
|
||||
.page-card .el-pagination .el-pager li.is-active { color: #fff; background: var(--el-color-primary); }
|
||||
|
||||
.el-dialog { max-width: calc(100vw - 32px); padding: 0; overflow: hidden; border-radius: 14px; box-shadow: 0 24px 80px rgba(15, 30, 55, .18); }
|
||||
.el-dialog__header { padding: 22px 52px 20px 24px; margin: 0; border-bottom: 1px solid #edf0f5; }
|
||||
.el-dialog__title { color: #202e45; font-size: 17px; font-weight: 600; }
|
||||
.el-dialog__headerbtn { top: 9px; right: 8px; }
|
||||
.el-dialog__body { max-height: 65vh; padding: 24px; overflow-y: auto; }
|
||||
.el-dialog__footer { padding: 16px 24px; border-top: 1px solid #edf0f5; background: #fafbfd; }
|
||||
.el-dialog .el-form-item { margin-bottom: 22px; }
|
||||
.el-dialog .el-form-item:last-child { margin-bottom: 0; }
|
||||
.el-dialog .el-select, .el-dialog .el-input-number { width: 100%; }
|
||||
.el-dialog__footer .el-button { min-width: 80px; height: 36px; }
|
||||
.el-message-box { max-width: calc(100vw - 32px); padding: 20px; border-radius: 12px; }
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.page-card { padding: 22px; }
|
||||
.query-form .el-input, .query-form .el-select { width: 180px; }
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.page-card { min-height: calc(100vh - 88px); padding: 16px; border-radius: 10px; }
|
||||
.page-title h2 { font-size: 19px; }
|
||||
.query-form.el-form--inline { gap: 14px; padding: 14px; }
|
||||
.query-form.el-form--inline .el-form-item { width: 100%; }
|
||||
.query-form .el-input, .query-form .el-select { width: 100% !important; }
|
||||
.el-dialog__body { padding: 18px; }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*, *::before, *::after { scroll-behavior: auto !important; transition-duration: 0s !important; animation-duration: 0s !important; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
export type AdminUser = { id: number; username: string; nickname: string }
|
||||
export type AdminSession = { token: string; expiresAt: string; user: AdminUser }
|
||||
const STORAGE_KEY = 'supplied-admin-session'
|
||||
|
||||
export const clearAdminSession = () => sessionStorage.removeItem(STORAGE_KEY)
|
||||
export const saveAdminSession = (session: AdminSession) => sessionStorage.setItem(STORAGE_KEY, JSON.stringify(session))
|
||||
export const readAdminSession = (): AdminSession | null => {
|
||||
try {
|
||||
const session = JSON.parse(sessionStorage.getItem(STORAGE_KEY) || 'null')
|
||||
if (session?.token && session?.user && Date.parse(session.expiresAt) > Date.now()) return session
|
||||
} catch { /* 忽略已损坏的本地登录信息。 */ }
|
||||
clearAdminSession()
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<main class="login-page">
|
||||
<section class="login-intro">
|
||||
<div class="login-brand"><el-icon><ShoppingBag /></el-icon>供应商管理</div>
|
||||
<h1>高效管理,让服务更进一步</h1>
|
||||
<p>商品、订单、售后与对账,集中处理每一项业务。</p>
|
||||
<div class="business-tags"><span>商品管理</span><span>订单处理</span><span>售后服务</span><span>订单对账</span></div>
|
||||
</section>
|
||||
<section class="login-card">
|
||||
<h2>账号登录</h2>
|
||||
<p class="login-hint">欢迎回来,请登录供应商管理平台</p>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-position="top" size="large" @submit.prevent="submit">
|
||||
<el-form-item label="账号" prop="username">
|
||||
<el-input v-model="form.username" :prefix-icon="User" placeholder="请输入账号" autocomplete="username" :maxlength="64" />
|
||||
</el-form-item>
|
||||
<el-form-item label="密码" prop="password">
|
||||
<el-input v-model="form.password" :prefix-icon="Lock" type="password" show-password placeholder="请输入密码" autocomplete="current-password" :maxlength="128" />
|
||||
</el-form-item>
|
||||
<el-alert v-if="errorMessage" :title="errorMessage" type="error" show-icon :closable="false" class="login-error" />
|
||||
<el-button class="login-submit" type="primary" native-type="submit" :loading="submitting">{{ submitting ? '正在登录' : '登录' }}</el-button>
|
||||
</el-form>
|
||||
</section>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { Lock, ShoppingBag, User } from '@element-plus/icons-vue'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { postAdmin } from '@/api/admin'
|
||||
import { saveAdminSession, type AdminSession } from '@/utils/auth'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const formRef = ref<FormInstance>()
|
||||
const form = reactive({ username: '', password: '' })
|
||||
const submitting = ref(false)
|
||||
const errorMessage = ref('')
|
||||
const rules: FormRules = {
|
||||
username: [{ required: true, whitespace: true, message: '请输入账号', trigger: 'blur' }],
|
||||
password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
|
||||
}
|
||||
const submit = async () => {
|
||||
if (submitting.value || !await formRef.value?.validate().catch(() => false)) return
|
||||
submitting.value = true
|
||||
errorMessage.value = ''
|
||||
try {
|
||||
const session = await postAdmin<AdminSession>('login', { username: form.username.trim(), password: form.password })
|
||||
saveAdminSession(session)
|
||||
form.password = ''
|
||||
const target = typeof route.query.redirect === 'string' ? route.query.redirect : ''
|
||||
const resolved = target.startsWith('/') && !target.startsWith('//') && !target.includes(String.fromCharCode(92)) ? router.resolve(target) : null
|
||||
await router.replace(resolved?.matched.length && resolved.name !== 'Login' ? target : '/reconciliation')
|
||||
} catch (error) {
|
||||
errorMessage.value = error instanceof Error ? error.message : '登录失败,请稍后重试'
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.login-page { display: flex; align-items: center; justify-content: center; gap: clamp(48px, 7vw, 120px); min-height: 100vh; padding: 60px 40px; background: linear-gradient(120deg, #eaf0fa 0%, #f6f8fc 55%, #eef2f8 100%); }
|
||||
.login-intro { max-width: 480px; color: #1c304e; }
|
||||
.login-brand { display: flex; align-items: center; gap: 12px; margin-bottom: 64px; font-size: 22px; font-weight: 600; letter-spacing: 1px; }
|
||||
.login-brand .el-icon { width: 48px; height: 48px; border-radius: 13px; background: #2463d4; color: #fff; font-size: 27px; box-shadow: 0 8px 20px rgba(36, 99, 212, .18); }
|
||||
.login-intro h1 { max-width: 410px; margin: 0 0 20px; font-size: 38px; font-weight: 600; line-height: 1.55; letter-spacing: 1px; }
|
||||
.login-intro p { color: #738199; font-size: 14px; line-height: 1.9; }
|
||||
.business-tags { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 30px; }
|
||||
.business-tags span { padding: 8px 13px; border: 1px solid #dce5f2; border-radius: 6px; color: #536985; background: rgba(255, 255, 255, .65); font-size: 12px; }
|
||||
.login-card { width: 420px; flex-shrink: 0; padding: 42px 38px; border: 1px solid #e4eaf3; border-radius: 18px; background: #fff; box-shadow: 0 18px 60px rgba(38, 60, 98, .08); }
|
||||
.login-card h2 { margin: 0 0 12px; color: #202e45; font-size: 25px; font-weight: 600; }
|
||||
.login-hint { margin: 0 0 32px; color: #8793a5; font-size: 13px; line-height: 1.7; }
|
||||
.login-card :deep(.el-form-item) { margin-bottom: 24px; }
|
||||
.login-card :deep(.el-form-item__label) { color: #526078; font-size: 13px; }
|
||||
.login-card :deep(.el-input__wrapper) { min-height: 44px; padding: 0 14px; }
|
||||
.login-submit { width: 100%; height: 44px; margin-top: 10px; letter-spacing: 2px; }
|
||||
.login-error { margin-bottom: 16px; }
|
||||
@media (max-width: 960px) { .login-page { gap: 36px; padding: 40px 24px; } .login-intro h1 { font-size: 30px; } .login-card { width: 380px; padding: 34px 28px; } }
|
||||
@media (max-width: 700px) { .login-page { flex-direction: column; gap: 26px; padding: 32px 20px; } .login-intro { width: 100%; max-width: 420px; } .login-brand { margin-bottom: 0; font-size: 20px; } .login-intro h1, .login-intro p, .business-tags { display: none; } .login-card { width: 100%; max-width: 420px; } }
|
||||
</style>
|
||||
@@ -1,21 +1,32 @@
|
||||
<template>
|
||||
<section class="page-card">
|
||||
<div class="page-title"><div><h2>订单列表</h2><p>查询订单并对待发货订单确认发货。</p></div></div>
|
||||
<div class="page-title"><div><h2>订单列表</h2><p>查询订单并对待发货订单确认发货。</p></div><el-button type="primary" :loading="batchShipping" :disabled="loading || submitting || shipDialogVisible || Boolean(repushingOrderId) || !pendingShipOrders.length" @click="quickShipAll">一键发货</el-button></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 style="width: 160px"><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="searchOrders">查询</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="240" fixed="right"><template #default="{ row }"><el-button v-if="row.orderState === 1" link type="primary" @click="openShipDialog(row)">确认发货</el-button><span v-else>-</span><el-button v-if="row.orderState === 1" link type="warning" :loading="repushingOrderId === row.orderId" :disabled="Boolean(repushingOrderId)" @click="repushSplit(row)">重新推送拆单</el-button></template></el-table-column></el-table>
|
||||
<el-pagination v-model:current-page="query.pageNumber" :page-size="query.pageSize" :total="totalCount" layout="total, prev, pager, next, jumper" style="margin-top: 16px; justify-content: flex-end" @current-change="loadOrders" />
|
||||
<el-empty v-if="!loading && queried && !orders.length" description="暂无订单" />
|
||||
<el-table v-loading="loading" :data="orders" border><el-table-column prop="orderNumberId" label="订单编号" min-width="200" show-overflow-tooltip /><el-table-column label="用户昵称" min-width="150" show-overflow-tooltip><template #default="{ row }">{{ row.userNickname || '-' }}</template></el-table-column><el-table-column label="商品信息" min-width="300"><template #default="{ row }">
|
||||
<div v-for="(product, index) in row.products || []" :key="product.id || index" class="order-product">
|
||||
<el-image class="order-product__image" :src="product.image" fit="cover" :preview-src-list="product.image ? [product.image] : []" preview-teleported>
|
||||
<template #error><span class="order-product__placeholder">暂无图片</span></template>
|
||||
</el-image>
|
||||
<div class="order-product__info">
|
||||
<div class="order-product__name">{{ product.name || '-' }}</div>
|
||||
<div class="order-product__bottom"><span class="order-product__price">{{ formatPrice(product.price) }}</span><span class="order-product__quantity">×{{ product.number ?? '-' }}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<span v-if="!row.products?.length">-</span>
|
||||
</template></el-table-column><el-table-column label="订单金额" width="120" align="right"><template #default="{ row }"><span class="order-amount">{{ formatPrice(row.amount) }}</span></template></el-table-column><el-table-column label="订单状态" width="110"><template #default="{ row }"><el-tag :type="[4, 5].includes(row.orderState) ? 'success' : [0, 1, 6].includes(row.orderState) ? 'warning' : row.orderState === 2 ? 'primary' : 'info'">{{ stateText(row.orderState) }}</el-tag></template></el-table-column><el-table-column prop="createTime" label="下单时间" min-width="170" /><el-table-column label="操作" width="240" fixed="right"><template #default="{ row }"><el-button v-if="row.orderState === 1" link type="primary" :disabled="batchShipping" @click="openShipDialog(row)">确认发货</el-button><span v-else>-</span><el-button v-if="row.orderState === 1" link type="warning" :loading="repushingOrderId === row.orderId" :disabled="batchShipping || Boolean(repushingOrderId)" @click="repushSplit(row)">重新推送拆单</el-button></template></el-table-column><template #empty><el-empty :image-size="80" description="暂无订单" /></template></el-table>
|
||||
<el-pagination v-model:current-page="query.pageNumber" :page-size="query.pageSize" :total="totalCount" layout="total, prev, pager, next, jumper" @current-change="loadOrders" />
|
||||
|
||||
<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 { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { postAdmin } from '@/api/admin'
|
||||
|
||||
type OrderProduct = { name?: string }
|
||||
type OrderItem = { orderId: string; supplierId: string; orderNumberId?: string; userId?: string; orderState: number; createTime?: string; products?: OrderProduct[] }
|
||||
type OrderProduct = { id?: number; name?: string; image?: string; price?: string | number; number?: number }
|
||||
type OrderItem = { orderId: string; supplierId: string; orderNumberId?: string; userId?: string; userNickname?: string; orderState: number; createTime?: string; crrgBsnNm?: string; logisticsOrderId?: string; amount?: string | number; 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; pageNumber: number; pageSize: number }>({ supplierId: '', orderId: '', userId: '', pageNumber: 1, pageSize: 10 })
|
||||
@@ -26,9 +37,39 @@ const loadSuppliers = async () => { try { const data = await postAdmin<{ supplie
|
||||
const loadOrders = async () => { loading.value = true; try { const data = await postAdmin<{ orderItems?: OrderItem[]; totalCount?: number }>('merchant-order-list', query); orders.value = data.orderItems || []; totalCount.value = data.totalCount || 0; queried.value = true } catch (error) { ElMessage.error(error instanceof Error ? error.message : '订单查询失败') } finally { loading.value = false } }
|
||||
const searchOrders = () => { query.pageNumber = 1; return loadOrders() }
|
||||
const reset = () => { query.supplierId = ''; query.orderId = ''; query.userId = ''; query.orderState = undefined; searchOrders() }
|
||||
const productNames = (order: OrderItem) => order.products?.map(item => item.name).filter(Boolean).join('、') || '-'
|
||||
const formatPrice = (value?: string | number) => value !== undefined && value !== null && value !== '' && Number.isFinite(Number(value)) ? `¥${Number(value).toFixed(2)}` : '-'
|
||||
const openShipDialog = (order: OrderItem) => { currentOrder.value = order; shipForm.value = { crrgBsnNm: '韵达快递', logisticsOrderId: `YT${Date.now()}${Math.floor(Math.random() * 10000).toString().padStart(4, '0')}` }; 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: currentOrder.value.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 } }
|
||||
const batchShipping = ref(false)
|
||||
const pendingShipOrders = computed(() => orders.value.filter(order => order.orderState === 1))
|
||||
const quickShipAll = async () => {
|
||||
if (batchShipping.value || loading.value || submitting.value || shipDialogVisible.value || repushingOrderId.value) return
|
||||
const targets = pendingShipOrders.value.map(order => ({ ...order }))
|
||||
if (!targets.length) return ElMessage.warning('当前页没有待发货订单')
|
||||
batchShipping.value = true
|
||||
try {
|
||||
await ElMessageBox.confirm(`将为当前页的 ${targets.length} 个待发货订单提交发货,默认使用韵达快递,并为每笔订单生成独立快递单号。是否继续?`, '确认一键发货', { type: 'warning', confirmButtonText: '确认发货', cancelButtonText: '取消' })
|
||||
const results = await Promise.allSettled(targets.map((order, index) => postAdmin('merchant-order-ship', {
|
||||
supplierId: order.supplierId,
|
||||
userId: order.userId,
|
||||
orderId: order.orderId,
|
||||
crrgBsnNm: order.crrgBsnNm || '韵达快递',
|
||||
logisticsOrderId: order.logisticsOrderId || `YT${Date.now()}${Math.floor(Math.random() * 10000).toString().padStart(4, '0')}${index}`
|
||||
})))
|
||||
const failures = results.filter((result): result is PromiseRejectedResult => result.status === 'rejected')
|
||||
if (failures.length) {
|
||||
const reason = failures[0].reason instanceof Error ? failures[0].reason.message : '请求失败'
|
||||
ElMessage.warning({ message: `发货成功 ${results.length - failures.length} 笔,失败 ${failures.length} 笔:${reason}`, duration: 6000 })
|
||||
} else {
|
||||
ElMessage.success(`已成功发货 ${results.length} 笔订单`)
|
||||
}
|
||||
await loadOrders()
|
||||
} catch (error) {
|
||||
if (error !== 'cancel' && error !== 'close') ElMessage.error(error instanceof Error ? error.message : '批量发货失败')
|
||||
} finally {
|
||||
batchShipping.value = false
|
||||
}
|
||||
}
|
||||
const repushingOrderId = ref('')
|
||||
const repushSplit = async (order: OrderItem) => {
|
||||
if (repushingOrderId.value) return
|
||||
@@ -45,3 +86,15 @@ const repushSplit = async (order: OrderItem) => {
|
||||
}
|
||||
onMounted(() => { loadSuppliers(); loadOrders() })
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.order-product { display: flex; align-items: center; gap: 12px; min-width: 0; }
|
||||
.order-product + .order-product { margin-top: 12px; padding-top: 12px; border-top: 1px solid #edf0f5; }
|
||||
.order-product__image { width: 58px; height: 58px; flex-shrink: 0; overflow: hidden; border-radius: 7px; background: #f3f5f9; }
|
||||
.order-product__placeholder { display: grid; height: 100%; place-items: center; color: #8793a5; font-size: 11px; }
|
||||
.order-product__info { min-width: 0; flex: 1; }
|
||||
.order-product__name { color: #202e45; font-size: 13px; font-weight: 500; overflow-wrap: anywhere; }
|
||||
.order-product__bottom { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 6px; }
|
||||
.order-product__price, .order-amount { color: #ed6a2c; font-weight: 600; font-variant-numeric: tabular-nums; }
|
||||
.order-product__quantity { color: #526078; font-size: 12px; white-space: nowrap; }
|
||||
</style>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<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-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><template #empty><el-empty :image-size="80" description="暂无商品" /></template></el-table>
|
||||
|
||||
<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>
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
<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="暂无对账结果" />
|
||||
<template #empty><el-empty :image-size="80" description="暂无对账结果" /></template></el-table>
|
||||
|
||||
</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>
|
||||
|
||||
Reference in New Issue
Block a user