x
This commit is contained in:
@@ -45,7 +45,7 @@
|
||||
<div class="user-avatar">{{ (store.user.full_name || store.user.username).charAt(0).toUpperCase() }}</div>
|
||||
<span class="user-name">{{ store.user.full_name || store.user.username }}</span>
|
||||
</div>
|
||||
<button class="btn btn-secondary" @click="handleLogout">退出</button>
|
||||
<t-button theme="default" variant="outline" size="small" @click="handleLogout">退出</t-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<router-link to="/login" class="btn btn-primary">登录</router-link>
|
||||
|
||||
@@ -3,12 +3,11 @@ import { createPinia } from 'pinia'
|
||||
import router from './router'
|
||||
import App from './App.vue'
|
||||
import './assets/styles/main.css'
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||
import TDesign from 'tdesign-vue-next'
|
||||
import 'tdesign-vue-next/es/style/index.css'
|
||||
|
||||
const app = createApp(App)
|
||||
app.use(createPinia())
|
||||
app.use(router)
|
||||
app.use(ElementPlus, { locale: zhCn })
|
||||
app.use(TDesign)
|
||||
app.mount('#app')
|
||||
|
||||
@@ -67,7 +67,7 @@ onUnmounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-alert
|
||||
<t-alert
|
||||
v-if="!state.backendDbReady"
|
||||
:title="state.backendDbMessage"
|
||||
type="warning"
|
||||
@@ -76,7 +76,7 @@ onUnmounted(() => {
|
||||
style="margin-bottom: 16px;"
|
||||
/>
|
||||
|
||||
<div v-loading="state.loading">
|
||||
<t-loading :loading="state.loading">
|
||||
<DashboardTab v-if="state.activeTab === 'dashboard'" />
|
||||
<ProductsTab v-else-if="state.activeTab === 'products'" />
|
||||
<MaterialsTab v-else-if="state.activeTab === 'materials'" />
|
||||
@@ -87,7 +87,7 @@ onUnmounted(() => {
|
||||
<CustomersTab v-else-if="state.activeTab === 'customers'" />
|
||||
<FinanceTab v-else-if="state.activeTab === 'finance'" />
|
||||
<MovementsTab v-else-if="state.activeTab === 'movements'" />
|
||||
</div>
|
||||
</t-loading>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
@@ -108,7 +108,7 @@ onUnmounted(() => {
|
||||
.inventory-breadcrumb .sep { margin: 0 var(--space-2); }
|
||||
.inventory-breadcrumb .current { color: var(--text-primary); font-weight: var(--font-medium); }
|
||||
|
||||
.inventory-sidebar-wrapper :deep(.el-menu) {
|
||||
.inventory-sidebar-wrapper :deep(.t-menu) {
|
||||
border-right: none;
|
||||
background: transparent;
|
||||
}
|
||||
@@ -124,7 +124,7 @@ onUnmounted(() => {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
.inventory-sidebar-wrapper :deep(.el-menu-item) {
|
||||
.inventory-sidebar-wrapper :deep(.t-menu__item) {
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
font-size: var(--text-sm);
|
||||
@@ -132,12 +132,12 @@ onUnmounted(() => {
|
||||
border-radius: var(--radius-md);
|
||||
padding: 0 var(--space-4);
|
||||
}
|
||||
.inventory-sidebar-wrapper :deep(.el-menu-item.is-active) {
|
||||
.inventory-sidebar-wrapper :deep(.t-is-active) {
|
||||
background: var(--primary-50);
|
||||
color: var(--primary-600);
|
||||
font-weight: var(--font-medium);
|
||||
}
|
||||
.inventory-sidebar-wrapper :deep(.el-menu-item:hover) {
|
||||
.inventory-sidebar-wrapper :deep(.t-menu__item:hover) {
|
||||
background: var(--bg-tertiary);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -81,57 +81,57 @@ onMounted(() => { loadCustomers() })
|
||||
<template>
|
||||
<div class="customers-tab">
|
||||
<div class="table-header">
|
||||
<el-button type="primary" @click="openCreateCustomer">+ 新增客户</el-button>
|
||||
<t-button type="primary" @click="openCreateCustomer">+ 新增客户</t-button>
|
||||
</div>
|
||||
<el-table :data="state.customers" style="width: 100%" v-loading="state.loading" empty-text="暂无客户数据">
|
||||
<el-table-column prop="code" label="编码" />
|
||||
<el-table-column prop="name" label="名称" />
|
||||
<el-table-column prop="contact_person" label="联系人">
|
||||
<t-table :data="state.customers" style="width: 100%" :loading="state.loading" empty-text="暂无客户数据">
|
||||
<t-table-column prop="code" label="编码" />
|
||||
<t-table-column prop="name" label="名称" />
|
||||
<t-table-column prop="contact_person" label="联系人">
|
||||
<template #default="{ row }">
|
||||
{{ row.contact_person || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="phone" label="电话">
|
||||
</t-table-column>
|
||||
<t-table-column prop="phone" label="电话">
|
||||
<template #default="{ row }">
|
||||
{{ row.phone || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="email" label="邮箱">
|
||||
</t-table-column>
|
||||
<t-table-column prop="email" label="邮箱">
|
||||
<template #default="{ row }">
|
||||
{{ row.email || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="180">
|
||||
</t-table-column>
|
||||
<t-table-column label="操作" width="180">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" @click="editCustomer(row)">编辑</el-button>
|
||||
<el-button size="small" type="danger" @click="deleteCustomer(row.id)">删除</el-button>
|
||||
<t-button size="small" @click="editCustomer(row)">编辑</t-button>
|
||||
<t-button size="small" type="danger" @click="deleteCustomer(row.id)">删除</t-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
|
||||
<el-dialog v-model="showModal" :title="editingItem ? '编辑客户' : '新增客户'" width="520px" :close-on-click-modal="false">
|
||||
<el-form :model="form" label-width="80px">
|
||||
<el-form-item label="名称" required>
|
||||
<el-input v-model="form.name" placeholder="请输入客户名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="联系人">
|
||||
<el-input v-model="form.contact_person" placeholder="请输入联系人" />
|
||||
</el-form-item>
|
||||
<el-form-item label="电话">
|
||||
<el-input v-model="form.phone" placeholder="请输入电话" />
|
||||
</el-form-item>
|
||||
<el-form-item label="邮箱">
|
||||
<el-input v-model="form.email" type="email" placeholder="请输入邮箱" />
|
||||
</el-form-item>
|
||||
<el-form-item label="地址">
|
||||
<el-input v-model="form.address" placeholder="请输入地址" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<t-dialog v-model="showModal" :title="editingItem ? '编辑客户' : '新增客户'" width="520px" :close-on-click-modal="false">
|
||||
<t-form :model="form" label-width="80px">
|
||||
<t-form-item label="名称" required>
|
||||
<t-input v-model="form.name" placeholder="请输入客户名称" />
|
||||
</t-form-item>
|
||||
<t-form-item label="联系人">
|
||||
<t-input v-model="form.contact_person" placeholder="请输入联系人" />
|
||||
</t-form-item>
|
||||
<t-form-item label="电话">
|
||||
<t-input v-model="form.phone" placeholder="请输入电话" />
|
||||
</t-form-item>
|
||||
<t-form-item label="邮箱">
|
||||
<t-input v-model="form.email" type="email" placeholder="请输入邮箱" />
|
||||
</t-form-item>
|
||||
<t-form-item label="地址">
|
||||
<t-input v-model="form.address" placeholder="请输入地址" />
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
<template #footer>
|
||||
<el-button @click="showModal = false">取消</el-button>
|
||||
<el-button type="primary" @click="save">保存</el-button>
|
||||
<t-button @click="showModal = false">取消</t-button>
|
||||
<t-button type="primary" @click="save">保存</t-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</t-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ const { state, formatCurrency } = useInventory()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8" v-for="card in [
|
||||
<t-row :gutter="16">
|
||||
<t-col :span="8" v-for="card in [
|
||||
{ icon: '📦', value: state.dashboard?.product_count || 0, label: '成品数量' },
|
||||
{ icon: '📊', value: state.dashboard?.total_stock || 0, label: '物料库存总量' },
|
||||
{ icon: '💰', value: formatCurrency(state.dashboard?.total_value || 0), label: '库存价值' },
|
||||
@@ -13,7 +13,7 @@ const { state, formatCurrency } = useInventory()
|
||||
{ icon: '👥', value: state.dashboard?.customer_count || 0, label: '客户' },
|
||||
{ icon: '🏪', value: state.dashboard?.warehouse_count || 0, label: '仓库' }
|
||||
]" :key="card.label" style="margin-bottom: 16px">
|
||||
<el-card shadow="hover">
|
||||
<t-card shadow="hover">
|
||||
<div style="display: flex; align-items: center; gap: 12px;">
|
||||
<span style="font-size: 32px;">{{ card.icon }}</span>
|
||||
<div>
|
||||
@@ -21,9 +21,9 @@ const { state, formatCurrency } = useInventory()
|
||||
<div style="color: var(--text-tertiary); font-size: 13px;">{{ card.label }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</t-card>
|
||||
</t-col>
|
||||
</t-row>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -74,192 +74,192 @@ function getTransactionStatusLabel(status: string): string {
|
||||
<div>
|
||||
<div style="margin-bottom: 16px; display: flex; gap: 12px; align-items: center; flex-wrap: wrap;">
|
||||
<span style="font-weight: 600;">统计周期:</span>
|
||||
<el-input-number
|
||||
<t-input-number
|
||||
v-model="periodYear"
|
||||
:min="2020"
|
||||
:max="2099"
|
||||
style="width: 120px;"
|
||||
/>
|
||||
<span>年</span>
|
||||
<el-select v-model="periodQuarter" placeholder="选择季度" style="width: 120px;">
|
||||
<el-option
|
||||
<t-select v-model="periodQuarter" placeholder="选择季度" style="width: 120px;">
|
||||
<t-option
|
||||
v-for="q in quarterOptions"
|
||||
:key="q.value"
|
||||
:label="q.label"
|
||||
:value="q.value"
|
||||
/>
|
||||
</el-select>
|
||||
<el-button type="primary" @click="refreshFinance">刷新统计</el-button>
|
||||
<el-tag type="info" size="large">{{ periodLabel }}</el-tag>
|
||||
</t-select>
|
||||
<t-button type="primary" @click="refreshFinance">刷新统计</t-button>
|
||||
<t-tag type="info" size="large">{{ periodLabel }}</t-tag>
|
||||
</div>
|
||||
|
||||
<el-row :gutter="16" style="margin-bottom: 16px;">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover">
|
||||
<t-row :gutter="16" style="margin-bottom: 16px;">
|
||||
<t-col :span="6">
|
||||
<t-card shadow="hover">
|
||||
<div style="text-align: center;">
|
||||
<div style="color: #909399; font-size: 14px; margin-bottom: 8px;">应收总额</div>
|
||||
<div style="font-size: 24px; font-weight: 700; color: #409eff;">
|
||||
{{ formatCurrency(totalReceivable) }}
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover">
|
||||
</t-card>
|
||||
</t-col>
|
||||
<t-col :span="6">
|
||||
<t-card shadow="hover">
|
||||
<div style="text-align: center;">
|
||||
<div style="color: #909399; font-size: 14px; margin-bottom: 8px;">应付总额</div>
|
||||
<div style="font-size: 24px; font-weight: 700; color: #e6a23c;">
|
||||
{{ formatCurrency(totalPayable) }}
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover">
|
||||
</t-card>
|
||||
</t-col>
|
||||
<t-col :span="6">
|
||||
<t-card shadow="hover">
|
||||
<div style="text-align: center;">
|
||||
<div style="color: #909399; font-size: 14px; margin-bottom: 8px;">周期收款</div>
|
||||
<div style="font-size: 24px; font-weight: 700; color: #67c23a;">
|
||||
{{ formatCurrency(periodReceived) }}
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover">
|
||||
</t-card>
|
||||
</t-col>
|
||||
<t-col :span="6">
|
||||
<t-card shadow="hover">
|
||||
<div style="text-align: center;">
|
||||
<div style="color: #909399; font-size: 14px; margin-bottom: 8px;">周期付款</div>
|
||||
<div style="font-size: 24px; font-weight: 700; color: #f56c6c;">
|
||||
{{ formatCurrency(periodPaid) }}
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</t-card>
|
||||
</t-col>
|
||||
</t-row>
|
||||
|
||||
<el-card style="margin-bottom: 16px;">
|
||||
<t-card style="margin-bottom: 16px;">
|
||||
<template #header>
|
||||
<span style="font-weight: 600;">客户账款(周期)</span>
|
||||
</template>
|
||||
<el-table :data="state.customerFinanceStatement" v-loading="state.loading" stripe size="small">
|
||||
<el-table-column prop="partner_name" label="客户" />
|
||||
<el-table-column prop="order_count" label="订单数" />
|
||||
<el-table-column prop="transaction_count" label="流水数" />
|
||||
<el-table-column label="订单金额">
|
||||
<t-table :data="state.customerFinanceStatement" :loading="state.loading" stripe size="small">
|
||||
<t-table-column prop="partner_name" label="客户" />
|
||||
<t-table-column prop="order_count" label="订单数" />
|
||||
<t-table-column prop="transaction_count" label="流水数" />
|
||||
<t-table-column label="订单金额">
|
||||
<template #default="{ row }">{{ formatCurrency(row.order_amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="订单已收">
|
||||
</t-table-column>
|
||||
<t-table-column label="订单已收">
|
||||
<template #default="{ row }">{{ formatCurrency(row.order_received) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="实收流水">
|
||||
</t-table-column>
|
||||
<t-table-column label="实收流水">
|
||||
<template #default="{ row }">{{ formatCurrency(row.transaction_received) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="应收余额">
|
||||
</t-table-column>
|
||||
<t-table-column label="应收余额">
|
||||
<template #default="{ row }">{{ formatCurrency(row.receivable_balance) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="!state.loading && state.customerFinanceStatement.length === 0" description="暂无数据" />
|
||||
</el-card>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
<t-empty v-if="!state.loading && state.customerFinanceStatement.length === 0" description="暂无数据" />
|
||||
</t-card>
|
||||
|
||||
<el-card style="margin-bottom: 16px;">
|
||||
<t-card style="margin-bottom: 16px;">
|
||||
<template #header>
|
||||
<span style="font-weight: 600;">供应商账款(周期)</span>
|
||||
</template>
|
||||
<el-table :data="state.supplierFinanceStatement" v-loading="state.loading" stripe size="small">
|
||||
<el-table-column prop="partner_name" label="供应商" />
|
||||
<el-table-column prop="order_count" label="订单数" />
|
||||
<el-table-column prop="transaction_count" label="流水数" />
|
||||
<el-table-column label="订单金额">
|
||||
<t-table :data="state.supplierFinanceStatement" :loading="state.loading" stripe size="small">
|
||||
<t-table-column prop="partner_name" label="供应商" />
|
||||
<t-table-column prop="order_count" label="订单数" />
|
||||
<t-table-column prop="transaction_count" label="流水数" />
|
||||
<t-table-column label="订单金额">
|
||||
<template #default="{ row }">{{ formatCurrency(row.order_amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="订单已付">
|
||||
</t-table-column>
|
||||
<t-table-column label="订单已付">
|
||||
<template #default="{ row }">{{ formatCurrency(row.order_paid) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="实付流水">
|
||||
</t-table-column>
|
||||
<t-table-column label="实付流水">
|
||||
<template #default="{ row }">{{ formatCurrency(row.transaction_paid) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="应付余额">
|
||||
</t-table-column>
|
||||
<t-table-column label="应付余额">
|
||||
<template #default="{ row }">{{ formatCurrency(row.payable_balance) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="!state.loading && state.supplierFinanceStatement.length === 0" description="暂无数据" />
|
||||
</el-card>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
<t-empty v-if="!state.loading && state.supplierFinanceStatement.length === 0" description="暂无数据" />
|
||||
</t-card>
|
||||
|
||||
<el-card style="margin-bottom: 16px;">
|
||||
<t-card style="margin-bottom: 16px;">
|
||||
<template #header>
|
||||
<span style="font-weight: 600;">客户-商品追溯(周期)</span>
|
||||
</template>
|
||||
<el-table :data="state.customerProductStatement" v-loading="state.loading" stripe size="small">
|
||||
<el-table-column prop="partner_name" label="客户" />
|
||||
<el-table-column prop="product_sku" label="SKU" />
|
||||
<el-table-column prop="product_name" label="商品" />
|
||||
<el-table-column prop="order_count" label="订单数" />
|
||||
<el-table-column prop="quantity" label="数量" />
|
||||
<el-table-column label="订单金额">
|
||||
<t-table :data="state.customerProductStatement" :loading="state.loading" stripe size="small">
|
||||
<t-table-column prop="partner_name" label="客户" />
|
||||
<t-table-column prop="product_sku" label="SKU" />
|
||||
<t-table-column prop="product_name" label="商品" />
|
||||
<t-table-column prop="order_count" label="订单数" />
|
||||
<t-table-column prop="quantity" label="数量" />
|
||||
<t-table-column label="订单金额">
|
||||
<template #default="{ row }">{{ formatCurrency(row.order_amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="已结款">
|
||||
</t-table-column>
|
||||
<t-table-column label="已结款">
|
||||
<template #default="{ row }">{{ formatCurrency(row.settled_amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="未结款">
|
||||
</t-table-column>
|
||||
<t-table-column label="未结款">
|
||||
<template #default="{ row }">{{ formatCurrency(row.unsettled_amount) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="!state.loading && state.customerProductStatement.length === 0" description="暂无数据" />
|
||||
</el-card>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
<t-empty v-if="!state.loading && state.customerProductStatement.length === 0" description="暂无数据" />
|
||||
</t-card>
|
||||
|
||||
<el-card style="margin-bottom: 16px;">
|
||||
<t-card style="margin-bottom: 16px;">
|
||||
<template #header>
|
||||
<span style="font-weight: 600;">供应商-商品追溯(周期)</span>
|
||||
</template>
|
||||
<el-table :data="state.supplierProductStatement" v-loading="state.loading" stripe size="small">
|
||||
<el-table-column prop="partner_name" label="供应商" />
|
||||
<el-table-column prop="product_sku" label="SKU" />
|
||||
<el-table-column prop="product_name" label="商品" />
|
||||
<el-table-column prop="order_count" label="订单数" />
|
||||
<el-table-column prop="quantity" label="数量" />
|
||||
<el-table-column label="订单金额">
|
||||
<t-table :data="state.supplierProductStatement" :loading="state.loading" stripe size="small">
|
||||
<t-table-column prop="partner_name" label="供应商" />
|
||||
<t-table-column prop="product_sku" label="SKU" />
|
||||
<t-table-column prop="product_name" label="商品" />
|
||||
<t-table-column prop="order_count" label="订单数" />
|
||||
<t-table-column prop="quantity" label="数量" />
|
||||
<t-table-column label="订单金额">
|
||||
<template #default="{ row }">{{ formatCurrency(row.order_amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="已结款">
|
||||
</t-table-column>
|
||||
<t-table-column label="已结款">
|
||||
<template #default="{ row }">{{ formatCurrency(row.settled_amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="未结款">
|
||||
</t-table-column>
|
||||
<t-table-column label="未结款">
|
||||
<template #default="{ row }">{{ formatCurrency(row.unsettled_amount) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="!state.loading && state.supplierProductStatement.length === 0" description="暂无数据" />
|
||||
</el-card>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
<t-empty v-if="!state.loading && state.supplierProductStatement.length === 0" description="暂无数据" />
|
||||
</t-card>
|
||||
|
||||
<el-card>
|
||||
<t-card>
|
||||
<template #header>
|
||||
<span style="font-weight: 600;">最近财务流水</span>
|
||||
</template>
|
||||
<el-table :data="state.financeTransactions" v-loading="state.loading" stripe size="small">
|
||||
<el-table-column prop="order_no" label="单号" />
|
||||
<el-table-column label="类型">
|
||||
<t-table :data="state.financeTransactions" :loading="state.loading" stripe size="small">
|
||||
<t-table-column prop="order_no" label="单号" />
|
||||
<t-table-column label="类型">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getTransactionTypeTag(row.type)" size="small">
|
||||
<t-tag :type="getTransactionTypeTag(row.type)" size="small">
|
||||
{{ getTransactionTypeLabel(row.type) }}
|
||||
</el-tag>
|
||||
</t-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="partner_name" label="往来方" />
|
||||
<el-table-column label="金额">
|
||||
</t-table-column>
|
||||
<t-table-column prop="partner_name" label="往来方" />
|
||||
<t-table-column label="金额">
|
||||
<template #default="{ row }">{{ formatCurrency(row.amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态">
|
||||
</t-table-column>
|
||||
<t-table-column label="状态">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === 'confirmed' ? 'success' : 'warning'" size="small">
|
||||
<t-tag :type="row.status === 'confirmed' ? 'success' : 'warning'" size="small">
|
||||
{{ getTransactionStatusLabel(row.status) }}
|
||||
</el-tag>
|
||||
</t-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="日期">
|
||||
</t-table-column>
|
||||
<t-table-column label="日期">
|
||||
<template #default="{ row }">{{ formatDate(row.transaction_date) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="!state.loading && state.financeTransactions.length === 0" description="暂无数据" />
|
||||
</el-card>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
<t-empty v-if="!state.loading && state.financeTransactions.length === 0" description="暂无数据" />
|
||||
</t-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ const { state, menuGroups, openGroups, toggleGroup, handleMenuClick } = useInven
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-menu
|
||||
<t-menu
|
||||
:default-active="state.activeTab"
|
||||
class="inventory-sidebar"
|
||||
@select="(key: string) => handleMenuClick(key)"
|
||||
@@ -13,17 +13,17 @@ const { state, menuGroups, openGroups, toggleGroup, handleMenuClick } = useInven
|
||||
<div v-for="group in menuGroups" :key="group.key" class="sidebar-group">
|
||||
<div class="sidebar-group-title" @click="toggleGroup(group.key)">
|
||||
<span>{{ group.title }}</span>
|
||||
<el-icon><component :is="openGroups[group.key] ? 'ArrowDown' : 'ArrowRight'" /></el-icon>
|
||||
<t-icon><component :is="openGroups[group.key] ? 'ArrowDown' : 'ArrowRight'" /></t-icon>
|
||||
</div>
|
||||
<template v-if="openGroups[group.key]">
|
||||
<el-menu-item
|
||||
<t-menu-item
|
||||
v-for="item in group.items"
|
||||
:key="group.key + '-' + item.key"
|
||||
:index="item.key"
|
||||
>
|
||||
{{ item.label }}
|
||||
</el-menu-item>
|
||||
</t-menu-item>
|
||||
</template>
|
||||
</div>
|
||||
</el-menu>
|
||||
</t-menu>
|
||||
</template>
|
||||
|
||||
@@ -87,62 +87,62 @@ onMounted(() => { loadInventory() })
|
||||
<template>
|
||||
<div class="inventory-tab">
|
||||
<div class="table-header">
|
||||
<el-button type="primary" @click="openCreate">+ 新增物料库存</el-button>
|
||||
<t-button type="primary" @click="openCreate">+ 新增物料库存</t-button>
|
||||
</div>
|
||||
<el-table :data="state.inventory" style="width: 100%" v-loading="state.loading" empty-text="暂无库存数据">
|
||||
<el-table-column prop="product_sku" label="SKU" />
|
||||
<el-table-column prop="product_name" label="物料" />
|
||||
<el-table-column prop="warehouse_name" label="仓库" />
|
||||
<el-table-column prop="quantity" label="数量" />
|
||||
<el-table-column prop="available_quantity" label="可用" />
|
||||
<el-table-column label="操作" width="180">
|
||||
<t-table :data="state.inventory" style="width: 100%" :loading="state.loading" empty-text="暂无库存数据">
|
||||
<t-table-column prop="product_sku" label="SKU" />
|
||||
<t-table-column prop="product_name" label="物料" />
|
||||
<t-table-column prop="warehouse_name" label="仓库" />
|
||||
<t-table-column prop="quantity" label="数量" />
|
||||
<t-table-column prop="available_quantity" label="可用" />
|
||||
<t-table-column label="操作" width="180">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" @click="openEdit(row)">编辑</el-button>
|
||||
<el-button size="small" type="danger" @click="deleteItem(row.id)">删除</el-button>
|
||||
<t-button size="small" @click="openEdit(row)">编辑</t-button>
|
||||
<t-button size="small" type="danger" @click="deleteItem(row.id)">删除</t-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
|
||||
<el-dialog v-model="showModal" :title="editingItem ? '编辑物料库存' : '新增物料库存'" width="520px" :close-on-click-modal="false">
|
||||
<el-form :model="form" label-width="80px">
|
||||
<el-form-item label="物料" required>
|
||||
<el-select v-model="form.product_id" placeholder="请选择物料" style="width: 100%">
|
||||
<el-option
|
||||
<t-dialog v-model="showModal" :title="editingItem ? '编辑物料库存' : '新增物料库存'" width="520px" :close-on-click-modal="false">
|
||||
<t-form :model="form" label-width="80px">
|
||||
<t-form-item label="物料" required>
|
||||
<t-select v-model="form.product_id" placeholder="请选择物料" style="width: 100%">
|
||||
<t-option
|
||||
v-for="m in state.materials"
|
||||
:key="m.id"
|
||||
:label="m.name + (m.sku ? ' (' + m.sku + ')' : '')"
|
||||
:value="m.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="仓库" required>
|
||||
<el-select v-model="form.warehouse_id" placeholder="请选择仓库" style="width: 100%">
|
||||
<el-option
|
||||
</t-select>
|
||||
</t-form-item>
|
||||
<t-form-item label="仓库" required>
|
||||
<t-select v-model="form.warehouse_id" placeholder="请选择仓库" style="width: 100%">
|
||||
<t-option
|
||||
v-for="w in state.warehouses"
|
||||
:key="w.id"
|
||||
:label="w.name"
|
||||
:value="w.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="数量" required>
|
||||
<el-input-number v-model="form.quantity" :min="0" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="锁定数量">
|
||||
<el-input-number v-model="form.locked_quantity" :min="0" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="批次号">
|
||||
<el-input v-model="form.batch_no" placeholder="请输入批次号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="库位">
|
||||
<el-input v-model="form.location" placeholder="请输入库位" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</t-select>
|
||||
</t-form-item>
|
||||
<t-form-item label="数量" required>
|
||||
<t-input-number v-model="form.quantity" :min="0" style="width: 100%" />
|
||||
</t-form-item>
|
||||
<t-form-item label="锁定数量">
|
||||
<t-input-number v-model="form.locked_quantity" :min="0" style="width: 100%" />
|
||||
</t-form-item>
|
||||
<t-form-item label="批次号">
|
||||
<t-input v-model="form.batch_no" placeholder="请输入批次号" />
|
||||
</t-form-item>
|
||||
<t-form-item label="库位">
|
||||
<t-input v-model="form.location" placeholder="请输入库位" />
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
<template #footer>
|
||||
<el-button @click="showModal = false">取消</el-button>
|
||||
<el-button type="primary" @click="save">保存</el-button>
|
||||
<t-button @click="showModal = false">取消</t-button>
|
||||
<t-button type="primary" @click="save">保存</t-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</t-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -175,139 +175,139 @@ onMounted(() => { loadMaterials() })
|
||||
<template>
|
||||
<div>
|
||||
<div style="margin-bottom: 12px; display: flex; gap: 8px;">
|
||||
<el-button type="primary" @click="openCreateMaterial">新增物料</el-button>
|
||||
<el-button type="success" @click="openRestockModal">物料补货</el-button>
|
||||
<t-button type="primary" @click="openCreateMaterial">新增物料</t-button>
|
||||
<t-button type="success" @click="openRestockModal">物料补货</t-button>
|
||||
</div>
|
||||
|
||||
<el-table :data="state.materials" v-loading="state.loading" stripe>
|
||||
<el-table-column prop="sku" label="SKU" />
|
||||
<el-table-column prop="name" label="名称" />
|
||||
<el-table-column prop="category" label="分类">
|
||||
<t-table :data="state.materials" :loading="state.loading" stripe>
|
||||
<t-table-column prop="sku" label="SKU" />
|
||||
<t-table-column prop="name" label="名称" />
|
||||
<t-table-column prop="category" label="分类">
|
||||
<template #default="{ row }">{{ row.category || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="unit" label="单位" />
|
||||
<el-table-column label="成本价">
|
||||
</t-table-column>
|
||||
<t-table-column prop="unit" label="单位" />
|
||||
<t-table-column label="成本价">
|
||||
<template #default="{ row }">{{ formatCurrency(row.cost_price) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="min_stock" label="最低库存">
|
||||
</t-table-column>
|
||||
<t-table-column prop="min_stock" label="最低库存">
|
||||
<template #default="{ row }">{{ row.min_stock ?? '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="180">
|
||||
</t-table-column>
|
||||
<t-table-column label="操作" width="180">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" size="small" @click="editMaterial(row)">编辑</el-button>
|
||||
<el-button type="danger" size="small" @click="deleteMaterial(row.id)">删除</el-button>
|
||||
<t-button type="primary" size="small" @click="editMaterial(row)">编辑</t-button>
|
||||
<t-button type="danger" size="small" @click="deleteMaterial(row.id)">删除</t-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="!state.loading && state.materials.length === 0" description="暂无物料数据" />
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
<t-empty v-if="!state.loading && state.materials.length === 0" description="暂无物料数据" />
|
||||
|
||||
<el-dialog
|
||||
v-model="showModal"
|
||||
<t-dialog
|
||||
v-model:visible="showModal"
|
||||
:title="(editingItem ? '编辑' : '新增') + '物料'"
|
||||
width="560px"
|
||||
@closed="closeModal"
|
||||
>
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="类型">
|
||||
<el-input model-value="物料(纳入库存)" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="SKU" required>
|
||||
<el-input v-model="form.sku" placeholder="产品编码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="名称" required>
|
||||
<el-input v-model="form.name" placeholder="产品名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="分类">
|
||||
<el-input v-model="form.category" placeholder="产品分类" />
|
||||
</el-form-item>
|
||||
<el-form-item label="单位">
|
||||
<el-input v-model="form.unit" placeholder="kg" />
|
||||
</el-form-item>
|
||||
<el-form-item label="成本价">
|
||||
<el-input-number v-model="form.cost_price" :precision="2" :min="0" :step="0.01" style="width:100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="最低库存">
|
||||
<el-input-number v-model="form.min_stock" :min="0" style="width:100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="最高库存">
|
||||
<el-input-number v-model="form.max_stock" :min="0" style="width:100%" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<t-form label-width="100px">
|
||||
<t-form-item label="类型">
|
||||
<t-input model-value="物料(纳入库存)" disabled />
|
||||
</t-form-item>
|
||||
<t-form-item label="SKU" required>
|
||||
<t-input v-model="form.sku" placeholder="产品编码" />
|
||||
</t-form-item>
|
||||
<t-form-item label="名称" required>
|
||||
<t-input v-model="form.name" placeholder="产品名称" />
|
||||
</t-form-item>
|
||||
<t-form-item label="分类">
|
||||
<t-input v-model="form.category" placeholder="产品分类" />
|
||||
</t-form-item>
|
||||
<t-form-item label="单位">
|
||||
<t-input v-model="form.unit" placeholder="kg" />
|
||||
</t-form-item>
|
||||
<t-form-item label="成本价">
|
||||
<t-input-number v-model="form.cost_price" :precision="2" :min="0" :step="0.01" style="width:100%" />
|
||||
</t-form-item>
|
||||
<t-form-item label="最低库存">
|
||||
<t-input-number v-model="form.min_stock" :min="0" style="width:100%" />
|
||||
</t-form-item>
|
||||
<t-form-item label="最高库存">
|
||||
<t-input-number v-model="form.max_stock" :min="0" style="width:100%" />
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
<template #footer>
|
||||
<el-button @click="showModal = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveProduct">保存</el-button>
|
||||
<t-button @click="showModal = false">取消</t-button>
|
||||
<t-button type="primary" @click="saveProduct">保存</t-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</t-dialog>
|
||||
|
||||
<el-dialog
|
||||
v-model="showRestockModal"
|
||||
<t-dialog
|
||||
v-model:visible="showRestockModal"
|
||||
title="物料补货"
|
||||
width="900px"
|
||||
@closed="closeRestockModal"
|
||||
>
|
||||
<el-form label-width="100px" style="margin-bottom: 16px;">
|
||||
<el-form-item label="供应商">
|
||||
<el-select v-model="restockSupplierId" placeholder="请选择供应商" style="width:100%">
|
||||
<el-option
|
||||
<t-form label-width="100px" style="margin-bottom: 16px;">
|
||||
<t-form-item label="供应商">
|
||||
<t-select v-model="restockSupplierId" placeholder="请选择供应商" style="width:100%">
|
||||
<t-option
|
||||
v-for="supplier in state.suppliers"
|
||||
:key="supplier.id"
|
||||
:label="supplier.name"
|
||||
:value="supplier.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</t-select>
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
|
||||
<span style="font-weight: 600;">补货物料明细</span>
|
||||
<el-button type="primary" size="small" @click="addRestockItem">+ 添加物料</el-button>
|
||||
<t-button type="primary" size="small" @click="addRestockItem">+ 添加物料</t-button>
|
||||
</div>
|
||||
<el-table :data="restockItems" border size="small">
|
||||
<el-table-column label="物料" min-width="200">
|
||||
<t-table :data="restockItems" border size="small">
|
||||
<t-table-column label="物料" min-width="200">
|
||||
<template #default="{ row: line }">
|
||||
<el-select v-model="line.material_id" placeholder="请选择物料" style="width:100%">
|
||||
<el-option
|
||||
<t-select v-model="line.material_id" placeholder="请选择物料" style="width:100%">
|
||||
<t-option
|
||||
v-for="material in state.materials"
|
||||
:key="material.id"
|
||||
:label="`${material.sku} - ${material.name}`"
|
||||
:value="material.id"
|
||||
/>
|
||||
</el-select>
|
||||
</t-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="数量" width="120">
|
||||
</t-table-column>
|
||||
<t-table-column label="数量" width="120">
|
||||
<template #default="{ row: line }">
|
||||
<el-input-number v-model="line.quantity" :min="1" size="small" style="width:100%" />
|
||||
<t-input-number v-model="line.quantity" :min="1" size="small" style="width:100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单价" width="120">
|
||||
</t-table-column>
|
||||
<t-table-column label="单价" width="120">
|
||||
<template #default="{ row: line }">
|
||||
<el-input-number v-model="line.unit_price" :min="0" :precision="2" :step="0.01" size="small" style="width:100%" />
|
||||
<t-input-number v-model="line.unit_price" :min="0" :precision="2" :step="0.01" size="small" style="width:100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="总价" width="120">
|
||||
</t-table-column>
|
||||
<t-table-column label="总价" width="120">
|
||||
<template #default="{ row: line }">
|
||||
{{ formatCurrency((line.unit_price || 0) * (line.quantity || 0)) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" width="150">
|
||||
</t-table-column>
|
||||
<t-table-column label="备注" width="150">
|
||||
<template #default="{ row: line }">
|
||||
<el-input v-model="line.remark" size="small" placeholder="备注" />
|
||||
<t-input v-model="line.remark" size="small" placeholder="备注" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80">
|
||||
</t-table-column>
|
||||
<t-table-column label="操作" width="80">
|
||||
<template #default="{ $index }">
|
||||
<el-button type="danger" size="small" @click="removeRestockItem($index)">删除</el-button>
|
||||
<t-button type="danger" size="small" @click="removeRestockItem($index)">删除</t-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
<div style="text-align: right; margin-top: 8px; font-weight: 600;">
|
||||
补货总金额:{{ formatCurrency(restockTotalAmount) }}
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="showRestockModal = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveRestock">创建采购订单</el-button>
|
||||
<t-button @click="showRestockModal = false">取消</t-button>
|
||||
<t-button type="primary" @click="saveRestock">创建采购订单</t-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</t-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -16,28 +16,28 @@ onMounted(() => { loadMovements() })
|
||||
|
||||
<template>
|
||||
<div class="movements-tab">
|
||||
<el-table :data="state.movements" style="width: 100%" v-loading="state.loading" empty-text="暂无库存变动记录">
|
||||
<el-table-column label="物料">
|
||||
<t-table :data="state.movements" style="width: 100%" :loading="state.loading" empty-text="暂无库存变动记录">
|
||||
<t-table-column label="物料">
|
||||
<template #default="{ row }">
|
||||
{{ row.product_name }}{{ row.product_sku ? ' (' + row.product_sku + ')' : '' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="类型" width="140">
|
||||
</t-table-column>
|
||||
<t-table-column label="类型" width="140">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getMovementTagType(row.movement_type)" size="small">
|
||||
<t-tag :type="getMovementTagType(row.movement_type)" size="small">
|
||||
{{ getMovementTypeLabel(row.movement_type) }}
|
||||
</el-tag>
|
||||
</t-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="quantity" label="数量" width="80" />
|
||||
<el-table-column prop="before_quantity" label="变动前" width="80" />
|
||||
<el-table-column prop="after_quantity" label="变动后" width="80" />
|
||||
<el-table-column label="时间" width="180">
|
||||
</t-table-column>
|
||||
<t-table-column prop="quantity" label="数量" width="80" />
|
||||
<t-table-column prop="before_quantity" label="变动前" width="80" />
|
||||
<t-table-column prop="after_quantity" label="变动后" width="80" />
|
||||
<t-table-column label="时间" width="180">
|
||||
<template #default="{ row }">
|
||||
{{ formatDateTime(row.created_at) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -124,102 +124,102 @@ onMounted(() => { loadFinishedProducts() })
|
||||
<template>
|
||||
<div>
|
||||
<div style="margin-bottom: 12px;">
|
||||
<el-button type="primary" @click="openCreateProduct">新增成品</el-button>
|
||||
<t-button type="primary" @click="openCreateProduct">新增成品</t-button>
|
||||
</div>
|
||||
|
||||
<el-table :data="state.finishedProducts" v-loading="state.loading" stripe>
|
||||
<el-table-column prop="sku" label="SKU" />
|
||||
<el-table-column prop="name" label="名称" />
|
||||
<el-table-column prop="category" label="分类">
|
||||
<t-table :data="state.finishedProducts" :loading="state.loading" stripe>
|
||||
<t-table-column prop="sku" label="SKU" />
|
||||
<t-table-column prop="name" label="名称" />
|
||||
<t-table-column prop="category" label="分类">
|
||||
<template #default="{ row }">{{ row.category || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="unit" label="单位" />
|
||||
<el-table-column label="成本价">
|
||||
</t-table-column>
|
||||
<t-table-column prop="unit" label="单位" />
|
||||
<t-table-column label="成本价">
|
||||
<template #default="{ row }">{{ formatCurrency(row.cost_price) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="销售价">
|
||||
</t-table-column>
|
||||
<t-table-column label="销售价">
|
||||
<template #default="{ row }">{{ formatCurrency(row.sale_price) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="基础物料成本">
|
||||
</t-table-column>
|
||||
<t-table-column label="基础物料成本">
|
||||
<template #default="{ row }">{{ formatCurrency(row.material_cost || 0) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="180">
|
||||
</t-table-column>
|
||||
<t-table-column label="操作" width="180">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" size="small" @click="editProduct(row)">编辑</el-button>
|
||||
<el-button type="danger" size="small" @click="deleteProduct(row.id)">删除</el-button>
|
||||
<t-button type="primary" size="small" @click="editProduct(row)">编辑</t-button>
|
||||
<t-button type="danger" size="small" @click="deleteProduct(row.id)">删除</t-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="!state.loading && state.finishedProducts.length === 0" description="暂无成品数据" />
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
<t-empty v-if="!state.loading && state.finishedProducts.length === 0" description="暂无成品数据" />
|
||||
|
||||
<el-dialog
|
||||
v-model="showModal"
|
||||
<t-dialog
|
||||
v-model:visible="showModal"
|
||||
:title="(editingItem ? '编辑' : '新增') + '成品'"
|
||||
width="560px"
|
||||
@closed="closeModal"
|
||||
>
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="类型">
|
||||
<el-input model-value="成品(按单生产,不做库存)" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="SKU" required>
|
||||
<el-input v-model="form.sku" placeholder="产品编码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="名称" required>
|
||||
<el-input v-model="form.name" placeholder="产品名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="分类">
|
||||
<el-input v-model="form.category" placeholder="产品分类" />
|
||||
</el-form-item>
|
||||
<el-form-item label="单位">
|
||||
<el-input v-model="form.unit" placeholder="件" />
|
||||
</el-form-item>
|
||||
<el-form-item label="成本价">
|
||||
<el-input-number v-model="form.cost_price" :precision="2" :min="0" :step="0.01" style="width:100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="销售价">
|
||||
<el-input-number v-model="form.sale_price" :precision="2" :min="0" :step="0.01" style="width:100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="说明">
|
||||
<el-input model-value="成品不做库存,成本由下方BOM定义物料构成后自动计算" disabled />
|
||||
</el-form-item>
|
||||
<t-form label-width="100px">
|
||||
<t-form-item label="类型">
|
||||
<t-input model-value="成品(按单生产,不做库存)" disabled />
|
||||
</t-form-item>
|
||||
<t-form-item label="SKU" required>
|
||||
<t-input v-model="form.sku" placeholder="产品编码" />
|
||||
</t-form-item>
|
||||
<t-form-item label="名称" required>
|
||||
<t-input v-model="form.name" placeholder="产品名称" />
|
||||
</t-form-item>
|
||||
<t-form-item label="分类">
|
||||
<t-input v-model="form.category" placeholder="产品分类" />
|
||||
</t-form-item>
|
||||
<t-form-item label="单位">
|
||||
<t-input v-model="form.unit" placeholder="件" />
|
||||
</t-form-item>
|
||||
<t-form-item label="成本价">
|
||||
<t-input-number v-model="form.cost_price" :precision="2" :min="0" :step="0.01" style="width:100%" />
|
||||
</t-form-item>
|
||||
<t-form-item label="销售价">
|
||||
<t-input-number v-model="form.sale_price" :precision="2" :min="0" :step="0.01" style="width:100%" />
|
||||
</t-form-item>
|
||||
<t-form-item label="说明">
|
||||
<t-input model-value="成品不做库存,成本由下方BOM定义物料构成后自动计算" disabled />
|
||||
</t-form-item>
|
||||
|
||||
<template v-if="editingItem">
|
||||
<el-divider content-position="left">BOM物料配置</el-divider>
|
||||
<t-divider content-position="left">BOM物料配置</t-divider>
|
||||
<div style="padding: 0 12px 12px 12px;">
|
||||
<el-button type="primary" size="small" @click="addBomItem" style="margin-bottom:8px">+ 添加物料</el-button>
|
||||
<el-table :data="bomItems" size="small" border>
|
||||
<el-table-column label="物料">
|
||||
<t-button type="primary" size="small" @click="addBomItem" style="margin-bottom:8px">+ 添加物料</t-button>
|
||||
<t-table :data="bomItems" size="small" border>
|
||||
<t-table-column label="物料">
|
||||
<template #default="{ row: bomItem }">
|
||||
<el-select v-model="bomItem.material_id" placeholder="选择物料" style="width:100%">
|
||||
<el-option
|
||||
<t-select v-model="bomItem.material_id" placeholder="选择物料" style="width:100%">
|
||||
<t-option
|
||||
v-for="material in state.materials"
|
||||
:key="material.id"
|
||||
:label="`${material.sku} - ${material.name}`"
|
||||
:value="material.id"
|
||||
/>
|
||||
</el-select>
|
||||
</t-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="数量" width="120">
|
||||
</t-table-column>
|
||||
<t-table-column label="数量" width="120">
|
||||
<template #default="{ row: bomItem }">
|
||||
<el-input-number v-model="bomItem.quantity" :min="0.0001" :step="0.0001" size="small" style="width:100%" />
|
||||
<t-input-number v-model="bomItem.quantity" :min="0.0001" :step="0.0001" size="small" style="width:100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80">
|
||||
</t-table-column>
|
||||
<t-table-column label="操作" width="80">
|
||||
<template #default="{ $index }">
|
||||
<el-button type="danger" size="small" @click="removeBomItem($index)">删除</el-button>
|
||||
<t-button type="danger" size="small" @click="removeBomItem($index)">删除</t-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
</div>
|
||||
</template>
|
||||
</el-form>
|
||||
</t-form>
|
||||
<template #footer>
|
||||
<el-button @click="showModal = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveProduct">保存</el-button>
|
||||
<t-button @click="showModal = false">取消</t-button>
|
||||
<t-button type="primary" @click="saveProduct">保存</t-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</t-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import { DialogPlugin } from 'tdesign-vue-next'
|
||||
import { useInventory } from '../composables/useInventory'
|
||||
import { apiRequest } from '@/shared/api'
|
||||
import { addNotification, handleApiError } from '@/shared/notification'
|
||||
|
||||
function confirmDialog(header: string, body: string, theme: string, confirmText: string, cancelText: string): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
const dlg = DialogPlugin.confirm({
|
||||
header, body, theme: theme as any,
|
||||
confirmBtn: confirmText, cancelBtn: cancelText,
|
||||
onConfirm: () => { dlg.hide(); resolve(true) },
|
||||
onCancel: () => { dlg.hide(); resolve(false) },
|
||||
onClose: () => { resolve(false) }
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const {
|
||||
state,
|
||||
formatCurrency,
|
||||
@@ -140,11 +152,7 @@ async function savePurchaseOrder() {
|
||||
}
|
||||
|
||||
async function deleteOrder(id: number) {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要删除这个采购订单吗?', '删除确认', {
|
||||
confirmButtonText: '删除', cancelButtonText: '取消', type: 'warning'
|
||||
})
|
||||
} catch { return }
|
||||
if (!await confirmDialog('删除确认', '确定要删除这个采购订单吗?', 'warning', '删除', '取消')) return
|
||||
try {
|
||||
await apiRequest(`/api/purchase-orders/${id}`, { method: 'DELETE' })
|
||||
addNotification('采购订单已删除', 'success')
|
||||
@@ -212,29 +220,15 @@ async function receivePurchaseOrder() {
|
||||
|
||||
async function markAsPaid(orderId: number, receiptStatus: string) {
|
||||
if (receiptStatus !== 'received') {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
'该订单尚未全部收货,确定要提前付款吗?建议先完成收货后再付款。',
|
||||
'非标准流程提醒',
|
||||
{ confirmButtonText: '仍要付款', cancelButtonText: '取消', type: 'warning' }
|
||||
)
|
||||
} catch { return }
|
||||
if (!await confirmDialog('非标准流程提醒', '该订单尚未全部收货,确定要提前付款吗?建议先完成收货后再付款。', 'warning', '仍要付款', '取消')) return
|
||||
} else {
|
||||
try {
|
||||
await ElMessageBox.confirm('确认已向供应商支付该订单款项?', '付款确认', {
|
||||
confirmButtonText: '确认付款', cancelButtonText: '取消', type: 'success'
|
||||
})
|
||||
} catch { return }
|
||||
if (!await confirmDialog('付款确认', '确认已向供应商支付该订单款项?', 'success', '确认付款', '取消')) return
|
||||
}
|
||||
await doUpdateStatus(orderId, 'paid', '已付款')
|
||||
}
|
||||
|
||||
async function cancelOrder(orderId: number) {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要作废该订单吗?此操作不可撤销。', '作废确认', {
|
||||
confirmButtonText: '确认作废', cancelButtonText: '取消', type: 'error'
|
||||
})
|
||||
} catch { return }
|
||||
if (!await confirmDialog('作废确认', '确定要作废该订单吗?此操作不可撤销。', 'error', '确认作废', '取消')) return
|
||||
await doUpdateStatus(orderId, 'cancelled', '已作废')
|
||||
}
|
||||
|
||||
@@ -280,223 +274,223 @@ onMounted(() => {
|
||||
<template>
|
||||
<div>
|
||||
<div style="margin-bottom: 12px; display: flex; gap: 12px; align-items: center;">
|
||||
<el-select
|
||||
<t-select
|
||||
v-model="purchaseWarehouseId"
|
||||
placeholder="选择仓库"
|
||||
style="width: 200px;"
|
||||
clearable
|
||||
>
|
||||
<el-option
|
||||
<t-option
|
||||
v-for="warehouse in state.warehouses"
|
||||
:key="warehouse.id"
|
||||
:label="warehouse.name"
|
||||
:value="warehouse.id"
|
||||
/>
|
||||
</el-select>
|
||||
<el-button @click="loadPurchaseOrders">刷新</el-button>
|
||||
<el-button type="primary" @click="openCreateOrder">新增采购订单</el-button>
|
||||
</t-select>
|
||||
<t-button @click="loadPurchaseOrders">刷新</t-button>
|
||||
<t-button type="primary" @click="openCreateOrder">新增采购订单</t-button>
|
||||
</div>
|
||||
|
||||
<el-table :data="state.purchaseOrders" v-loading="state.loading" stripe>
|
||||
<el-table-column prop="order_no" label="采购单" />
|
||||
<el-table-column prop="supplier_name" label="供应商" />
|
||||
<el-table-column label="状态">
|
||||
<t-table :data="state.purchaseOrders" :loading="state.loading" stripe>
|
||||
<t-table-column prop="order_no" label="采购单" />
|
||||
<t-table-column prop="supplier_name" label="供应商" />
|
||||
<t-table-column label="状态">
|
||||
<template #default="{ row }">
|
||||
<div style="display: flex; gap: 4px;">
|
||||
<el-tag
|
||||
<t-tag
|
||||
:type="row.receipt_status === 'received' ? 'success' : row.receipt_status === 'partial_received' ? 'warning' : row.receipt_status === 'cancelled' ? 'danger' : 'info'"
|
||||
size="small"
|
||||
>{{ getReceiptStatusLabel(row.receipt_status) }}</el-tag>
|
||||
<el-tag
|
||||
>{{ getReceiptStatusLabel(row.receipt_status) }}</t-tag>
|
||||
<t-tag
|
||||
:type="row.payment_status === 'paid' ? 'success' : 'info'"
|
||||
size="small"
|
||||
>{{ getPaymentStatusLabel(row.payment_status) }}</el-tag>
|
||||
>{{ getPaymentStatusLabel(row.payment_status) }}</t-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="订单创建">
|
||||
</t-table-column>
|
||||
<t-table-column label="订单创建">
|
||||
<template #default="{ row }">{{ formatDateTime(row.created_at) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="预计到货">
|
||||
</t-table-column>
|
||||
<t-table-column label="预计到货">
|
||||
<template #default="{ row }">{{ formatDate(row.expected_date) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="实际到货">
|
||||
</t-table-column>
|
||||
<t-table-column label="实际到货">
|
||||
<template #default="{ row }">{{ row.received_at ? formatDateTime(row.received_at) : '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="实际付款">
|
||||
</t-table-column>
|
||||
<t-table-column label="实际付款">
|
||||
<template #default="{ row }">{{ row.paid_at ? formatDateTime(row.paid_at) : '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="总金额">
|
||||
</t-table-column>
|
||||
<t-table-column label="总金额">
|
||||
<template #default="{ row }">{{ formatCurrency(row.total_amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="已付款">
|
||||
</t-table-column>
|
||||
<t-table-column label="已付款">
|
||||
<template #default="{ row }">{{ formatCurrency(row.paid_amount || 0) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="280">
|
||||
</t-table-column>
|
||||
<t-table-column label="操作" width="280">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
<t-button
|
||||
type="primary"
|
||||
size="small"
|
||||
:disabled="isPurchaseOrderLocked(row.status)"
|
||||
@click="editOrder(row)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
</t-button>
|
||||
<t-button
|
||||
type="danger"
|
||||
size="small"
|
||||
:disabled="isPurchaseOrderLocked(row.status)"
|
||||
@click="deleteOrder(row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
<el-button
|
||||
</t-button>
|
||||
<t-button
|
||||
v-if="row.receipt_status === 'pending' || row.receipt_status === 'partial_received'"
|
||||
type="success"
|
||||
size="small"
|
||||
@click="openReceiveDialog(row)"
|
||||
>
|
||||
到货入库
|
||||
</el-button>
|
||||
<el-button
|
||||
</t-button>
|
||||
<t-button
|
||||
v-if="row.payment_status !== 'paid' && row.receipt_status !== 'cancelled'"
|
||||
type="warning"
|
||||
size="small"
|
||||
@click="markAsPaid(row.id, row.receipt_status)"
|
||||
>
|
||||
标记已付款
|
||||
</el-button>
|
||||
<el-button
|
||||
</t-button>
|
||||
<t-button
|
||||
v-if="row.payment_status !== 'paid' && row.receipt_status !== 'cancelled'"
|
||||
type="info"
|
||||
size="small"
|
||||
@click="cancelOrder(row.id)"
|
||||
>
|
||||
作废
|
||||
</el-button>
|
||||
</t-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="!state.loading && state.purchaseOrders.length === 0" description="暂无采购订单" />
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
<t-empty v-if="!state.loading && state.purchaseOrders.length === 0" description="暂无采购订单" />
|
||||
|
||||
<el-dialog
|
||||
v-model="showModal"
|
||||
<t-dialog
|
||||
v-model:visible="showModal"
|
||||
:title="(editingItem ? '编辑' : '新增') + '采购订单'"
|
||||
width="800px"
|
||||
@closed="closeModal"
|
||||
>
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="供应商" required>
|
||||
<el-select v-model="form.supplier_id" placeholder="请选择供应商" style="width:100%">
|
||||
<el-option
|
||||
<t-form label-width="100px">
|
||||
<t-form-item label="供应商" required>
|
||||
<t-select v-model="form.supplier_id" placeholder="请选择供应商" style="width:100%">
|
||||
<t-option
|
||||
v-for="supplier in state.suppliers"
|
||||
:key="supplier.id"
|
||||
:label="supplier.name"
|
||||
:value="supplier.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="预计到货">
|
||||
<el-date-picker
|
||||
</t-select>
|
||||
</t-form-item>
|
||||
<t-form-item label="预计到货">
|
||||
<t-date-picker
|
||||
v-model="form.expected_date"
|
||||
type="date"
|
||||
placeholder="选择日期"
|
||||
style="width:100%"
|
||||
value-format="YYYY-MM-DD"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="form.remark" placeholder="备注信息" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</t-form-item>
|
||||
<t-form-item label="备注">
|
||||
<t-input v-model="form.remark" placeholder="备注信息" />
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
|
||||
<span style="font-weight: 600;">物料明细</span>
|
||||
<el-button type="primary" size="small" @click="addOrderItem">+ 添加物料</el-button>
|
||||
<t-button type="primary" size="small" @click="addOrderItem">+ 添加物料</t-button>
|
||||
</div>
|
||||
<el-table :data="orderItems" border size="small">
|
||||
<el-table-column label="物料" min-width="200">
|
||||
<t-table :data="orderItems" border size="small">
|
||||
<t-table-column label="物料" min-width="200">
|
||||
<template #default="{ row: line }">
|
||||
<el-select v-model="line.product_id" placeholder="请选择物料" style="width:100%">
|
||||
<el-option
|
||||
<t-select v-model="line.product_id" placeholder="请选择物料" style="width:100%">
|
||||
<t-option
|
||||
v-for="material in state.materials"
|
||||
:key="material.id"
|
||||
:label="`${material.sku} - ${material.name}`"
|
||||
:value="material.id"
|
||||
/>
|
||||
</el-select>
|
||||
</t-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="数量" width="120">
|
||||
</t-table-column>
|
||||
<t-table-column label="数量" width="120">
|
||||
<template #default="{ row: line }">
|
||||
<el-input-number v-model="line.quantity" :min="0.0001" :step="0.0001" size="small" style="width:100%" />
|
||||
<t-input-number v-model="line.quantity" :min="0.0001" :step="0.0001" size="small" style="width:100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单价" width="120">
|
||||
</t-table-column>
|
||||
<t-table-column label="单价" width="120">
|
||||
<template #default="{ row: line }">
|
||||
<el-input-number
|
||||
<t-input-number
|
||||
:model-value="(state.materials.find((m: any) => m.id === line.product_id)?.cost_price || 0)"
|
||||
disabled
|
||||
size="small"
|
||||
style="width:100%"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="总价" width="120">
|
||||
</t-table-column>
|
||||
<t-table-column label="总价" width="120">
|
||||
<template #default="{ row: line }">
|
||||
{{ formatCurrency((state.materials.find((m: any) => m.id === line.product_id)?.cost_price || 0) * (line.quantity || 0)) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" width="150">
|
||||
</t-table-column>
|
||||
<t-table-column label="备注" width="150">
|
||||
<template #default="{ row: line }">
|
||||
<el-input v-model="line.remark" size="small" placeholder="备注" />
|
||||
<t-input v-model="line.remark" size="small" placeholder="备注" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80">
|
||||
</t-table-column>
|
||||
<t-table-column label="操作" width="80">
|
||||
<template #default="{ $index }">
|
||||
<el-button type="danger" size="small" @click="removeOrderItem($index)">删除</el-button>
|
||||
<t-button type="danger" size="small" @click="removeOrderItem($index)">删除</t-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
<div style="text-align: right; margin-top: 8px; font-weight: 600;">
|
||||
订单总金额:{{ formatCurrency(orderTotalAmount) }}
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="showModal = false">取消</el-button>
|
||||
<el-button type="primary" @click="savePurchaseOrder">保存</el-button>
|
||||
<t-button @click="showModal = false">取消</t-button>
|
||||
<t-button type="primary" @click="savePurchaseOrder">保存</t-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</t-dialog>
|
||||
|
||||
<el-dialog
|
||||
v-model="showReceiveModal"
|
||||
<t-dialog
|
||||
v-model:visible="showReceiveModal"
|
||||
title="采购到货入库"
|
||||
width="700px"
|
||||
@closed="closeReceiveModal"
|
||||
>
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="入库仓库" required>
|
||||
<el-select v-model="receiveWarehouseId" placeholder="请选择仓库" style="width:100%">
|
||||
<el-option
|
||||
<t-form label-width="100px">
|
||||
<t-form-item label="入库仓库" required>
|
||||
<t-select v-model="receiveWarehouseId" placeholder="请选择仓库" style="width:100%">
|
||||
<t-option
|
||||
v-for="warehouse in state.warehouses"
|
||||
:key="warehouse.id"
|
||||
:label="warehouse.name"
|
||||
:value="warehouse.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="receiveRemark" placeholder="入库备注" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table :data="receiveItems" border size="small">
|
||||
<el-table-column prop="material_label" label="物料" />
|
||||
<el-table-column prop="id" label="明细ID" width="80" />
|
||||
<el-table-column label="剩余待入库" width="100">
|
||||
</t-select>
|
||||
</t-form-item>
|
||||
<t-form-item label="备注">
|
||||
<t-input v-model="receiveRemark" placeholder="入库备注" />
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
<t-table :data="receiveItems" border size="small">
|
||||
<t-table-column prop="material_label" label="物料" />
|
||||
<t-table-column prop="id" label="明细ID" width="80" />
|
||||
<t-table-column label="剩余待入库" width="100">
|
||||
<template #default="{ row: item }">{{ item.remaining }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="本次入库" width="140">
|
||||
</t-table-column>
|
||||
<t-table-column label="本次入库" width="140">
|
||||
<template #default="{ row: item }">
|
||||
<el-input-number
|
||||
<t-input-number
|
||||
v-model="item.receive_quantity"
|
||||
:min="0"
|
||||
:max="item.remaining"
|
||||
@@ -504,13 +498,13 @@ onMounted(() => {
|
||||
style="width:100%"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
<template #footer>
|
||||
<el-button @click="showReceiveModal = false">取消</el-button>
|
||||
<el-button type="primary" @click="receivePurchaseOrder">确认入库</el-button>
|
||||
<t-button @click="showReceiveModal = false">取消</t-button>
|
||||
<t-button type="primary" @click="receivePurchaseOrder">确认入库</t-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</t-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import { DialogPlugin } from 'tdesign-vue-next'
|
||||
import { useInventory } from '../composables/useInventory'
|
||||
import { apiRequest } from '@/shared/api'
|
||||
import { addNotification, handleApiError } from '@/shared/notification'
|
||||
|
||||
function confirmDialog(header: string, body: string, theme: string, confirmText: string, cancelText: string): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
const dlg = DialogPlugin.confirm({
|
||||
header, body, theme: theme as any,
|
||||
confirmBtn: confirmText, cancelBtn: cancelText,
|
||||
onConfirm: () => { dlg.hide(); resolve(true) },
|
||||
onCancel: () => { dlg.hide(); resolve(false) },
|
||||
onClose: () => { resolve(false) }
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const {
|
||||
state,
|
||||
formatCurrency,
|
||||
@@ -156,11 +168,7 @@ async function saveSalesOrder() {
|
||||
}
|
||||
|
||||
async function deleteOrder(id: number) {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要删除这个销售订单吗?', '删除确认', {
|
||||
confirmButtonText: '删除', cancelButtonText: '取消', type: 'warning'
|
||||
})
|
||||
} catch { return }
|
||||
if (!await confirmDialog('删除确认', '确定要删除这个销售订单吗?', 'warning', '删除', '取消')) return
|
||||
try {
|
||||
await apiRequest(`/api/sales-orders/${id}`, { method: 'DELETE' })
|
||||
addNotification('销售订单已删除', 'success')
|
||||
@@ -173,39 +181,21 @@ async function deleteOrder(id: number) {
|
||||
}
|
||||
|
||||
async function markDelivered(orderId: number) {
|
||||
try {
|
||||
await ElMessageBox.confirm('确认该订单已交付?', '交付确认', {
|
||||
confirmButtonText: '确认交付', cancelButtonText: '取消', type: 'success'
|
||||
})
|
||||
} catch { return }
|
||||
if (!await confirmDialog('交付确认', '确认该订单已交付?', 'success', '确认交付', '取消')) return
|
||||
await doUpdateStatus(orderId, 'delivered', '已交付')
|
||||
}
|
||||
|
||||
async function markPaid(orderId: number, deliveryStatus: string) {
|
||||
if (deliveryStatus !== 'delivered') {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
'当前订单尚未交付,确定要提前收款吗?建议先确认交付后再收款。',
|
||||
'非标准流程提醒',
|
||||
{ confirmButtonText: '仍要收款', cancelButtonText: '取消', type: 'warning' }
|
||||
)
|
||||
} catch { return }
|
||||
if (!await confirmDialog('非标准流程提醒', '当前订单尚未交付,确定要提前收款吗?建议先确认交付后再收款。', 'warning', '仍要收款', '取消')) return
|
||||
} else {
|
||||
try {
|
||||
await ElMessageBox.confirm('确认已收到该订单款项?', '收款确认', {
|
||||
confirmButtonText: '确认收款', cancelButtonText: '取消', type: 'success'
|
||||
})
|
||||
} catch { return }
|
||||
if (!await confirmDialog('收款确认', '确认已收到该订单款项?', 'success', '确认收款', '取消')) return
|
||||
}
|
||||
await doUpdateStatus(orderId, 'paid', '已收款')
|
||||
}
|
||||
|
||||
async function cancelOrder(orderId: number) {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要作废该订单吗?此操作不可撤销。', '作废确认', {
|
||||
confirmButtonText: '确认作废', cancelButtonText: '取消', type: 'error'
|
||||
})
|
||||
} catch { return }
|
||||
if (!await confirmDialog('作废确认', '确定要作废该订单吗?此操作不可撤销。', 'error', '确认作废', '取消')) return
|
||||
await doUpdateStatus(orderId, 'cancelled', '已作废')
|
||||
}
|
||||
|
||||
@@ -316,146 +306,146 @@ onMounted(() => {
|
||||
<template>
|
||||
<div>
|
||||
<div style="margin-bottom: 12px; display: flex; gap: 12px; align-items: center;">
|
||||
<el-select
|
||||
<t-select
|
||||
v-model="productionWarehouseId"
|
||||
placeholder="选择仓库"
|
||||
style="width: 200px;"
|
||||
clearable
|
||||
>
|
||||
<el-option
|
||||
<t-option
|
||||
v-for="warehouse in state.warehouses"
|
||||
:key="warehouse.id"
|
||||
:label="warehouse.name"
|
||||
:value="warehouse.id"
|
||||
/>
|
||||
</el-select>
|
||||
<el-button @click="loadProductionOrders">刷新</el-button>
|
||||
<el-button type="primary" @click="openCreateOrder">新增销售订单</el-button>
|
||||
</t-select>
|
||||
<t-button @click="loadProductionOrders">刷新</t-button>
|
||||
<t-button type="primary" @click="openCreateOrder">新增销售订单</t-button>
|
||||
</div>
|
||||
|
||||
<el-table :data="state.productionOrders" v-loading="state.loading" stripe>
|
||||
<el-table-column prop="order_no" label="销售单" />
|
||||
<el-table-column prop="customer_name" label="客户" />
|
||||
<el-table-column label="订单金额">
|
||||
<t-table :data="state.productionOrders" :loading="state.loading" stripe>
|
||||
<t-table-column prop="order_no" label="销售单" />
|
||||
<t-table-column prop="customer_name" label="客户" />
|
||||
<t-table-column label="订单金额">
|
||||
<template #default="{ row }">{{ formatCurrency(row.total_amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="交付日期">
|
||||
</t-table-column>
|
||||
<t-table-column label="交付日期">
|
||||
<template #default="{ row }">{{ formatDate(row.delivery_date) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="订单创建">
|
||||
</t-table-column>
|
||||
<t-table-column label="订单创建">
|
||||
<template #default="{ row }">{{ formatDateTime(row.created_at) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="实际交付">
|
||||
</t-table-column>
|
||||
<t-table-column label="实际交付">
|
||||
<template #default="{ row }">{{ row.delivered_at ? formatDateTime(row.delivered_at) : '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="实际收款">
|
||||
</t-table-column>
|
||||
<t-table-column label="实际收款">
|
||||
<template #default="{ row }">{{ row.paid_at ? formatDateTime(row.paid_at) : '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="订单状态" width="160">
|
||||
</t-table-column>
|
||||
<t-table-column label="订单状态" width="160">
|
||||
<template #default="{ row }">
|
||||
<div style="display: flex; gap: 4px;">
|
||||
<el-tag
|
||||
<t-tag
|
||||
:type="row.delivery_status === 'delivered' ? 'warning' : row.delivery_status === 'cancelled' ? 'danger' : 'info'"
|
||||
size="small"
|
||||
>{{ getDeliveryStatusLabel(row.delivery_status) }}</el-tag>
|
||||
<el-tag
|
||||
>{{ getDeliveryStatusLabel(row.delivery_status) }}</t-tag>
|
||||
<t-tag
|
||||
:type="row.payment_status === 'paid' ? 'success' : 'info'"
|
||||
size="small"
|
||||
>{{ getPaymentStatusLabel(row.payment_status) }}</el-tag>
|
||||
>{{ getPaymentStatusLabel(row.payment_status) }}</t-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="280">
|
||||
</t-table-column>
|
||||
<t-table-column label="操作" width="280">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
<t-button
|
||||
v-if="row.delivery_status === 'manufacturing'"
|
||||
type="success" size="small"
|
||||
@click="markDelivered(row.id)"
|
||||
>已交付</el-button>
|
||||
<el-button
|
||||
>已交付</t-button>
|
||||
<t-button
|
||||
v-if="row.payment_status !== 'paid' && row.delivery_status !== 'cancelled'"
|
||||
type="warning" size="small"
|
||||
@click="markPaid(row.id, row.delivery_status)"
|
||||
>已收款</el-button>
|
||||
<el-button
|
||||
>已收款</t-button>
|
||||
<t-button
|
||||
type="primary" size="small"
|
||||
:disabled="row.payment_status === 'paid' || row.delivery_status === 'cancelled'"
|
||||
@click="editOrder(row)"
|
||||
>编辑</el-button>
|
||||
<el-button type="danger" size="small" @click="deleteOrder(row.id)">删除</el-button>
|
||||
<el-button
|
||||
>编辑</t-button>
|
||||
<t-button type="danger" size="small" @click="deleteOrder(row.id)">删除</t-button>
|
||||
<t-button
|
||||
v-if="row.payment_status !== 'paid' && row.delivery_status !== 'cancelled'"
|
||||
type="info" size="small"
|
||||
@click="cancelOrder(row.id)"
|
||||
>作废</el-button>
|
||||
>作废</t-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="!state.loading && state.productionOrders.length === 0" description="暂无销售订单" />
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
<t-empty v-if="!state.loading && state.productionOrders.length === 0" description="暂无销售订单" />
|
||||
|
||||
<el-dialog
|
||||
v-model="showModal"
|
||||
<t-dialog
|
||||
v-model:visible="showModal"
|
||||
:title="(editingItem ? '编辑' : '新增') + '销售订单'"
|
||||
width="900px"
|
||||
@closed="closeModal"
|
||||
>
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="客户" required>
|
||||
<el-select v-model="form.customer_id" placeholder="请选择客户" style="width:100%">
|
||||
<el-option
|
||||
<t-form label-width="100px">
|
||||
<t-form-item label="客户" required>
|
||||
<t-select v-model="form.customer_id" placeholder="请选择客户" style="width:100%">
|
||||
<t-option
|
||||
v-for="customer in state.customers"
|
||||
:key="customer.id"
|
||||
:label="customer.name"
|
||||
:value="customer.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="交付日期">
|
||||
<el-date-picker
|
||||
</t-select>
|
||||
</t-form-item>
|
||||
<t-form-item label="交付日期">
|
||||
<t-date-picker
|
||||
v-model="form.delivery_date"
|
||||
type="date"
|
||||
placeholder="选择日期"
|
||||
style="width:100%"
|
||||
value-format="YYYY-MM-DD"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="form.remark" placeholder="备注信息" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</t-form-item>
|
||||
<t-form-item label="备注">
|
||||
<t-input v-model="form.remark" placeholder="备注信息" />
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
|
||||
<span style="font-weight: 600;">模具明细</span>
|
||||
<div style="display: flex; gap: 8px;">
|
||||
<el-button v-if="editingItem" type="warning" size="small" @click="openConsumptionModal">+ 消耗物料</el-button>
|
||||
<el-button type="primary" size="small" @click="addMoldItem">+ 添加模具</el-button>
|
||||
<t-button v-if="editingItem" type="warning" size="small" @click="openConsumptionModal">+ 消耗物料</t-button>
|
||||
<t-button type="primary" size="small" @click="addMoldItem">+ 添加模具</t-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table :data="moldItems" border size="small">
|
||||
<el-table-column label="模式" width="80">
|
||||
<t-table :data="moldItems" border size="small">
|
||||
<t-table-column label="模式" width="80">
|
||||
<template #default="{ row: line }">
|
||||
<el-select v-model="line.mold_mode" size="small" style="width:100%">
|
||||
<el-option label="新模" value="new" />
|
||||
<el-option label="改模" value="existing" />
|
||||
</el-select>
|
||||
<t-select v-model="line.mold_mode" size="small" style="width:100%">
|
||||
<t-option label="新模" value="new" />
|
||||
<t-option label="改模" value="existing" />
|
||||
</t-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="模具SKU" min-width="120">
|
||||
</t-table-column>
|
||||
<t-table-column label="模具SKU" min-width="120">
|
||||
<template #default="{ row: line }">
|
||||
<el-input v-if="line.mold_mode === 'new'" v-model="line.mold_sku" size="small" placeholder="模具SKU" />
|
||||
<el-select v-else v-model="line.mold_id" placeholder="选择模具" size="small" style="width:100%">
|
||||
<el-option
|
||||
<t-input v-if="line.mold_mode === 'new'" v-model="line.mold_sku" size="small" placeholder="模具SKU" />
|
||||
<t-select v-else v-model="line.mold_id" placeholder="选择模具" size="small" style="width:100%">
|
||||
<t-option
|
||||
v-for="product in state.finishedProducts"
|
||||
:key="product.id"
|
||||
:label="`${product.sku} - ${product.name}`"
|
||||
:value="product.id"
|
||||
/>
|
||||
</el-select>
|
||||
</t-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="模具名称" min-width="120">
|
||||
</t-table-column>
|
||||
<t-table-column label="模具名称" min-width="120">
|
||||
<template #default="{ row: line }">
|
||||
<el-input
|
||||
<t-input
|
||||
v-if="line.mold_mode === 'new'"
|
||||
v-model="line.mold_name"
|
||||
size="small"
|
||||
@@ -465,118 +455,118 @@ onMounted(() => {
|
||||
{{ state.finishedProducts.find((p: any) => p.id === line.mold_id)?.name || '-' }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="数量" width="100">
|
||||
</t-table-column>
|
||||
<t-table-column label="数量" width="100">
|
||||
<template #default="{ row: line }">
|
||||
<el-input-number v-model="line.quantity" :min="1" size="small" style="width:100%" />
|
||||
<t-input-number v-model="line.quantity" :min="1" size="small" style="width:100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单价" width="120">
|
||||
</t-table-column>
|
||||
<t-table-column label="单价" width="120">
|
||||
<template #default="{ row: line }">
|
||||
<el-input-number v-model="line.unit_price" :min="0" :precision="2" :step="0.01" size="small" style="width:100%" />
|
||||
<t-input-number v-model="line.unit_price" :min="0" :precision="2" :step="0.01" size="small" style="width:100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" width="120">
|
||||
</t-table-column>
|
||||
<t-table-column label="备注" width="120">
|
||||
<template #default="{ row: line }">
|
||||
<el-input v-model="line.remark" size="small" placeholder="备注" />
|
||||
<t-input v-model="line.remark" size="small" placeholder="备注" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80">
|
||||
</t-table-column>
|
||||
<t-table-column label="操作" width="80">
|
||||
<template #default="{ $index }">
|
||||
<el-button type="danger" size="small" @click="removeMoldItem($index)">删除</el-button>
|
||||
<t-button type="danger" size="small" @click="removeMoldItem($index)">删除</t-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
<div style="text-align: right; margin-top: 8px; font-weight: 600;">
|
||||
订单总金额:{{ formatCurrency(orderTotalAmount) }}
|
||||
</div>
|
||||
|
||||
<template v-if="editingItem && consumedMaterials.length > 0">
|
||||
<el-divider content-position="left">已消耗物料</el-divider>
|
||||
<el-table :data="consumedMaterials" border size="small">
|
||||
<el-table-column label="物料">
|
||||
<t-divider content-position="left">已消耗物料</t-divider>
|
||||
<t-table :data="consumedMaterials" border size="small">
|
||||
<t-table-column label="物料">
|
||||
<template #default="{ row: item }">{{ item.material_name }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="数量">
|
||||
</t-table-column>
|
||||
<t-table-column label="数量">
|
||||
<template #default="{ row: item }">{{ item.quantity }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单价">
|
||||
</t-table-column>
|
||||
<t-table-column label="单价">
|
||||
<template #default="{ row: item }">{{ formatCurrency(item.unit_price) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注">
|
||||
</t-table-column>
|
||||
<t-table-column label="备注">
|
||||
<template #default="{ row: item }">{{ item.remark || '-' }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="showModal = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveSalesOrder">保存</el-button>
|
||||
<t-button @click="showModal = false">取消</t-button>
|
||||
<t-button type="primary" @click="saveSalesOrder">保存</t-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</t-dialog>
|
||||
|
||||
<el-dialog
|
||||
v-model="showConsumptionModal"
|
||||
<t-dialog
|
||||
v-model:visible="showConsumptionModal"
|
||||
title="物料消耗"
|
||||
width="800px"
|
||||
@closed="closeConsumptionModal"
|
||||
>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
|
||||
<span style="font-weight: 600;">消耗物料明细</span>
|
||||
<el-button type="primary" size="small" @click="addConsumptionItem">+ 添加物料</el-button>
|
||||
<t-button type="primary" size="small" @click="addConsumptionItem">+ 添加物料</t-button>
|
||||
</div>
|
||||
<el-table :data="consumptionItems" border size="small">
|
||||
<el-table-column label="物料" min-width="200">
|
||||
<t-table :data="consumptionItems" border size="small">
|
||||
<t-table-column label="物料" min-width="200">
|
||||
<template #default="{ row: line }">
|
||||
<el-select v-model="line.material_id" placeholder="请选择物料" style="width:100%">
|
||||
<el-option
|
||||
<t-select v-model="line.material_id" placeholder="请选择物料" style="width:100%">
|
||||
<t-option
|
||||
v-for="material in state.materials"
|
||||
:key="material.id"
|
||||
:label="`${material.sku} - ${material.name}`"
|
||||
:value="material.id"
|
||||
/>
|
||||
</el-select>
|
||||
</t-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="数量" width="120">
|
||||
</t-table-column>
|
||||
<t-table-column label="数量" width="120">
|
||||
<template #default="{ row: line }">
|
||||
<el-input-number v-model="line.quantity" :min="0.0001" :step="0.0001" size="small" style="width:100%" />
|
||||
<t-input-number v-model="line.quantity" :min="0.0001" :step="0.0001" size="small" style="width:100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单价" width="120">
|
||||
</t-table-column>
|
||||
<t-table-column label="单价" width="120">
|
||||
<template #default="{ row: line }">
|
||||
<el-input-number
|
||||
<t-input-number
|
||||
:model-value="state.materials.find((m: any) => m.id === line.material_id)?.cost_price || 0"
|
||||
disabled
|
||||
size="small"
|
||||
style="width:100%"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="总价" width="120">
|
||||
</t-table-column>
|
||||
<t-table-column label="总价" width="120">
|
||||
<template #default="{ row: line }">
|
||||
{{ formatCurrency((state.materials.find((m: any) => m.id === line.material_id)?.cost_price || 0) * (line.quantity || 0)) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" width="150">
|
||||
</t-table-column>
|
||||
<t-table-column label="备注" width="150">
|
||||
<template #default="{ row: line }">
|
||||
<el-input v-model="line.remark" size="small" placeholder="备注" />
|
||||
<t-input v-model="line.remark" size="small" placeholder="备注" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80">
|
||||
</t-table-column>
|
||||
<t-table-column label="操作" width="80">
|
||||
<template #default="{ $index }">
|
||||
<el-button type="danger" size="small" @click="removeConsumptionItem($index)">删除</el-button>
|
||||
<t-button type="danger" size="small" @click="removeConsumptionItem($index)">删除</t-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
<div style="text-align: right; margin-top: 8px; font-weight: 600;">
|
||||
消耗总金额:{{ formatCurrency(consumptionTotalAmount) }}
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="showConsumptionModal = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveConsumption">保存消耗</el-button>
|
||||
<t-button @click="showConsumptionModal = false">取消</t-button>
|
||||
<t-button type="primary" @click="saveConsumption">保存消耗</t-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</t-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -81,57 +81,57 @@ onMounted(() => { loadSuppliers() })
|
||||
<template>
|
||||
<div class="suppliers-tab">
|
||||
<div class="table-header">
|
||||
<el-button type="primary" @click="openCreateSupplier">+ 新增供应商</el-button>
|
||||
<t-button type="primary" @click="openCreateSupplier">+ 新增供应商</t-button>
|
||||
</div>
|
||||
<el-table :data="state.suppliers" style="width: 100%" v-loading="state.loading" empty-text="暂无供应商数据">
|
||||
<el-table-column prop="code" label="编码" />
|
||||
<el-table-column prop="name" label="名称" />
|
||||
<el-table-column prop="contact_person" label="联系人">
|
||||
<t-table :data="state.suppliers" style="width: 100%" :loading="state.loading" empty-text="暂无供应商数据">
|
||||
<t-table-column prop="code" label="编码" />
|
||||
<t-table-column prop="name" label="名称" />
|
||||
<t-table-column prop="contact_person" label="联系人">
|
||||
<template #default="{ row }">
|
||||
{{ row.contact_person || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="phone" label="电话">
|
||||
</t-table-column>
|
||||
<t-table-column prop="phone" label="电话">
|
||||
<template #default="{ row }">
|
||||
{{ row.phone || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="email" label="邮箱">
|
||||
</t-table-column>
|
||||
<t-table-column prop="email" label="邮箱">
|
||||
<template #default="{ row }">
|
||||
{{ row.email || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="180">
|
||||
</t-table-column>
|
||||
<t-table-column label="操作" width="180">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" @click="editSupplier(row)">编辑</el-button>
|
||||
<el-button size="small" type="danger" @click="deleteSupplier(row.id)">删除</el-button>
|
||||
<t-button size="small" @click="editSupplier(row)">编辑</t-button>
|
||||
<t-button size="small" type="danger" @click="deleteSupplier(row.id)">删除</t-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
|
||||
<el-dialog v-model="showModal" :title="editingItem ? '编辑供应商' : '新增供应商'" width="520px" :close-on-click-modal="false">
|
||||
<el-form :model="form" label-width="80px">
|
||||
<el-form-item label="名称" required>
|
||||
<el-input v-model="form.name" placeholder="请输入供应商名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="联系人">
|
||||
<el-input v-model="form.contact_person" placeholder="请输入联系人" />
|
||||
</el-form-item>
|
||||
<el-form-item label="电话">
|
||||
<el-input v-model="form.phone" placeholder="请输入电话" />
|
||||
</el-form-item>
|
||||
<el-form-item label="邮箱">
|
||||
<el-input v-model="form.email" type="email" placeholder="请输入邮箱" />
|
||||
</el-form-item>
|
||||
<el-form-item label="地址">
|
||||
<el-input v-model="form.address" placeholder="请输入地址" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<t-dialog v-model="showModal" :title="editingItem ? '编辑供应商' : '新增供应商'" width="520px" :close-on-click-modal="false">
|
||||
<t-form :model="form" label-width="80px">
|
||||
<t-form-item label="名称" required>
|
||||
<t-input v-model="form.name" placeholder="请输入供应商名称" />
|
||||
</t-form-item>
|
||||
<t-form-item label="联系人">
|
||||
<t-input v-model="form.contact_person" placeholder="请输入联系人" />
|
||||
</t-form-item>
|
||||
<t-form-item label="电话">
|
||||
<t-input v-model="form.phone" placeholder="请输入电话" />
|
||||
</t-form-item>
|
||||
<t-form-item label="邮箱">
|
||||
<t-input v-model="form.email" type="email" placeholder="请输入邮箱" />
|
||||
</t-form-item>
|
||||
<t-form-item label="地址">
|
||||
<t-input v-model="form.address" placeholder="请输入地址" />
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
<template #footer>
|
||||
<el-button @click="showModal = false">取消</el-button>
|
||||
<el-button type="primary" @click="save">保存</el-button>
|
||||
<t-button @click="showModal = false">取消</t-button>
|
||||
<t-button type="primary" @click="save">保存</t-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</t-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -6,20 +6,18 @@
|
||||
<h1 class="login-title">Gemold</h1>
|
||||
<p class="login-subtitle">模具制造管理系统</p>
|
||||
</div>
|
||||
<form @submit.prevent="handleLogin">
|
||||
<div class="form-group">
|
||||
<label class="form-label">用户名</label>
|
||||
<input v-model="state.username" type="text" class="form-input" placeholder="请输入用户名" required />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">密码</label>
|
||||
<input v-model="state.password" type="password" class="form-input" placeholder="请输入密码" required />
|
||||
</div>
|
||||
<div class="form-error" v-if="state.error">{{ state.error }}</div>
|
||||
<button type="submit" class="btn btn-primary w-full mt-4" :disabled="state.loading">
|
||||
<t-form @submit="handleLogin">
|
||||
<t-form-item label="用户名">
|
||||
<t-input v-model="state.username" placeholder="请输入用户名" clearable />
|
||||
</t-form-item>
|
||||
<t-form-item label="密码">
|
||||
<t-input v-model="state.password" type="password" placeholder="请输入密码" clearable />
|
||||
</t-form-item>
|
||||
<t-alert v-if="state.error" theme="error" :message="state.error" style="margin-bottom:16px" />
|
||||
<t-button theme="primary" block type="submit" :loading="state.loading">
|
||||
{{ state.loading ? '登录中...' : '登录' }}
|
||||
</button>
|
||||
</form>
|
||||
</t-button>
|
||||
</t-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -60,19 +60,18 @@
|
||||
<div class="upload-side-card">
|
||||
<h2 class="section-title">2. 注塑模参数</h2>
|
||||
<div class="material-panel compact-panel">
|
||||
<div class="form-group">
|
||||
<label class="form-label">产品材料</label>
|
||||
<select v-model="state.selectedMaterial" class="form-select">
|
||||
<option value="ABS">ABS (1.05 g/cm³)</option>
|
||||
<option value="PP">PP (0.90 g/cm³)</option>
|
||||
<option value="PE">PE (0.95 g/cm³)</option>
|
||||
<option value="PC">PC (1.20 g/cm³)</option>
|
||||
<option value="PA">PA (1.14 g/cm³)</option>
|
||||
<option value="POM">POM (1.41 g/cm³)</option>
|
||||
<option value="PMMA">PMMA (1.18 g/cm³)</option>
|
||||
<option value="PBT">PBT (1.31 g/cm³)</option>
|
||||
</select>
|
||||
</div>
|
||||
<t-form-item label="产品材料">
|
||||
<t-select v-model="state.selectedMaterial">
|
||||
<t-option value="ABS" label="ABS (1.05 g/cm³)" />
|
||||
<t-option value="PP" label="PP (0.90 g/cm³)" />
|
||||
<t-option value="PE" label="PE (0.95 g/cm³)" />
|
||||
<t-option value="PC" label="PC (1.20 g/cm³)" />
|
||||
<t-option value="PA" label="PA (1.14 g/cm³)" />
|
||||
<t-option value="POM" label="POM (1.41 g/cm³)" />
|
||||
<t-option value="PMMA" label="PMMA (1.18 g/cm³)" />
|
||||
<t-option value="PBT" label="PBT (1.31 g/cm³)" />
|
||||
</t-select>
|
||||
</t-form-item>
|
||||
|
||||
<details class="advanced-params">
|
||||
<summary>高级工艺参数</summary>
|
||||
@@ -80,37 +79,35 @@
|
||||
<div class="inline-note">默认值适用于多数注塑件;仅在已知工艺约束时再调整。</div>
|
||||
<div class="advanced-param-grid">
|
||||
<div class="param-input-card" v-for="field in moldParamFields" :key="field.key">
|
||||
<label class="form-label">{{ field.label }}</label>
|
||||
<div class="param-input-row">
|
||||
<input
|
||||
type="number"
|
||||
class="form-input param-number-input"
|
||||
<t-form-item :label="field.label">
|
||||
<t-input-number
|
||||
v-model.number="state.moldParams[field.key as keyof typeof state.moldParams]"
|
||||
:min="field.min"
|
||||
:max="field.max"
|
||||
:step="field.step"
|
||||
:suffix="field.unit"
|
||||
/>
|
||||
<span class="param-unit">{{ field.unit }}</span>
|
||||
</div>
|
||||
<div class="param-meta-row">
|
||||
<span class="range-value">默认 {{ field.defaultValue }}{{ field.unit }}</span>
|
||||
<span class="range-value">范围 {{ field.min }} - {{ field.max }}{{ field.unit }}</span>
|
||||
</div>
|
||||
<div class="param-hint">{{ field.hint }}</div>
|
||||
<div class="param-meta-row">
|
||||
<span class="range-value">默认 {{ field.defaultValue }}{{ field.unit }}</span>
|
||||
<span class="range-value">范围 {{ field.min }} - {{ field.max }}{{ field.unit }}</span>
|
||||
</div>
|
||||
<div class="param-hint">{{ field.hint }}</div>
|
||||
</t-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<button
|
||||
<t-button
|
||||
v-if="state.selectedFile"
|
||||
class="btn-primary upload-submit-btn"
|
||||
theme="primary"
|
||||
class="upload-submit-btn"
|
||||
@click="uploadFile"
|
||||
:disabled="state.uploading || state.polling"
|
||||
>
|
||||
{{ state.uploading ? '上传中...' : state.polling ? '分析中...' : '3. 开始注塑模分析' }}
|
||||
</button>
|
||||
</t-button>
|
||||
<div v-else class="inline-note">先选择 STP 文件,再填写材料并开始分析。</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -118,90 +115,89 @@
|
||||
<details v-if="state.history?.files?.length" class="history-collapsible section">
|
||||
<summary class="history-summary">分析历史({{ state.history.files.length }} 个文件)</summary>
|
||||
<div class="table-container">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>文件名</th>
|
||||
<th>上传次数</th>
|
||||
<th>文件大小</th>
|
||||
<th>最新状态</th>
|
||||
<th>最新分析时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="file in state.history.files" :key="file.filename">
|
||||
<tr>
|
||||
<td>{{ file.filename }}</td>
|
||||
<td>
|
||||
<span class="badge badge-info">{{ file.upload_count }} 次</span>
|
||||
</td>
|
||||
<td>{{ formatFileSize(file.file_size) }}</td>
|
||||
<td>
|
||||
<span :class="['badge', file.latest_status === 'completed' ? 'badge-success' : file.latest_status === 'failed' ? 'badge-error' : 'badge-warning']">
|
||||
{{ file.latest_status }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ formatDateTime(file.latest_upload_time) }}</td>
|
||||
<td>
|
||||
<div class="action-buttons">
|
||||
<button v-if="file.latest_status === 'completed'" class="btn-sm btn-primary" @click="viewResult({ task_id: file.latest_task_id })">
|
||||
查看最新
|
||||
</button>
|
||||
<button class="btn-sm btn-icon" @click="toggleFileHistory(file.filename)" :title="state.expandedFiles[file.filename] ? '收起' : '展开历史记录'">
|
||||
<span class="dropdown-icon" :class="{ expanded: state.expandedFiles[file.filename] }">▼</span>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="state.expandedFiles[file.filename]" class="history-detail-row">
|
||||
<td colspan="6">
|
||||
<div class="history-dropdown">
|
||||
<table class="data-table inner-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>上传时间</th>
|
||||
<th>文件大小</th>
|
||||
<th>状态</th>
|
||||
<th>体积 (mm³)</th>
|
||||
<th>表面积 (mm²)</th>
|
||||
<th>重量 (g)</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="record in state.expandedFiles[file.filename]" :key="record.id">
|
||||
<td>{{ formatDateTime(record.upload_time) }}</td>
|
||||
<td>{{ formatFileSize(record.file_size) }}</td>
|
||||
<td>
|
||||
<span :class="['badge', record.status === 'completed' ? 'badge-success' : record.status === 'failed' ? 'badge-error' : 'badge-warning']">
|
||||
{{ record.status }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ record.volume ? formatNumber(record.volume) : '-' }}</td>
|
||||
<td>{{ record.surface_area ? formatNumber(record.surface_area) : '-' }}</td>
|
||||
<td>{{ record.product_weight ? record.product_weight.toFixed(2) : '-' }}</td>
|
||||
<td>
|
||||
<button v-if="record.has_analysis" class="btn-sm btn-primary" @click="viewResult(record)">
|
||||
查看详情
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<t-table
|
||||
row-key="filename"
|
||||
:data="state.history.files"
|
||||
:expanded-row-keys="historyExpandedKeys"
|
||||
@expand-change="onHistoryExpand"
|
||||
>
|
||||
<t-table-column key="filename" title="文件名">
|
||||
<template #cell="{ row }">{{ row.filename }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column key="upload_count" title="上传次数">
|
||||
<template #cell="{ row }">
|
||||
<t-tag theme="primary" variant="light">{{ row.upload_count }} 次</t-tag>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</t-table-column>
|
||||
<t-table-column key="file_size" title="文件大小">
|
||||
<template #cell="{ row }">{{ formatFileSize(row.file_size) }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column key="latest_status" title="最新状态">
|
||||
<template #cell="{ row }">
|
||||
<t-tag :theme="statusTagTheme(row.latest_status)" variant="light">{{ row.latest_status }}</t-tag>
|
||||
</template>
|
||||
</t-table-column>
|
||||
<t-table-column key="latest_upload_time" title="最新分析时间">
|
||||
<template #cell="{ row }">{{ formatDateTime(row.latest_upload_time) }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column key="actions" title="操作">
|
||||
<template #cell="{ row }">
|
||||
<div class="action-buttons">
|
||||
<t-button v-if="row.latest_status === 'completed'" theme="primary" size="small" @click="viewResult({ task_id: row.latest_task_id })">
|
||||
查看最新
|
||||
</t-button>
|
||||
<t-button variant="text" size="small" @click="toggleFileHistory(row.filename)" :title="state.expandedFiles[row.filename] ? '收起' : '展开历史记录'">
|
||||
<template #icon><span class="dropdown-icon" :class="{ expanded: state.expandedFiles[row.filename] }">▼</span></template>
|
||||
</t-button>
|
||||
</div>
|
||||
</template>
|
||||
</t-table-column>
|
||||
<template #expandedRow="{ row }">
|
||||
<div class="history-dropdown">
|
||||
<t-table
|
||||
v-if="state.expandedFiles[row.filename]"
|
||||
row-key="id"
|
||||
:data="state.expandedFiles[row.filename]"
|
||||
>
|
||||
<t-table-column key="upload_time" title="上传时间">
|
||||
<template #cell="{ row: r }">{{ formatDateTime(r.upload_time) }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column key="file_size" title="文件大小">
|
||||
<template #cell="{ row: r }">{{ formatFileSize(r.file_size) }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column key="status" title="状态">
|
||||
<template #cell="{ row: r }">
|
||||
<t-tag :theme="statusTagTheme(r.status)" variant="light">{{ r.status }}</t-tag>
|
||||
</template>
|
||||
</t-table-column>
|
||||
<t-table-column key="volume" title="体积 (mm³)">
|
||||
<template #cell="{ row: r }">{{ r.volume ? formatNumber(r.volume) : '-' }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column key="surface_area" title="表面积 (mm²)">
|
||||
<template #cell="{ row: r }">{{ r.surface_area ? formatNumber(r.surface_area) : '-' }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column key="product_weight" title="重量 (g)">
|
||||
<template #cell="{ row: r }">{{ r.product_weight ? r.product_weight.toFixed(2) : '-' }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column key="detail_actions" title="操作">
|
||||
<template #cell="{ row: r }">
|
||||
<t-button v-if="r.has_analysis" theme="primary" size="small" @click="viewResult(r)">
|
||||
查看详情
|
||||
</t-button>
|
||||
</template>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
<div v-else class="inline-note">加载中...</div>
|
||||
</div>
|
||||
</template>
|
||||
</t-table>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted } from 'vue'
|
||||
import { reactive, ref, computed, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { apiRequest } from '@/shared/api'
|
||||
@@ -273,6 +269,20 @@ const state = reactive({
|
||||
expandedFiles: {} as Record<string, HistoryRecord[] | null>,
|
||||
})
|
||||
|
||||
const historyExpandedKeys = computed(() => {
|
||||
return Object.keys(state.expandedFiles).filter(k => state.expandedFiles[k] !== null)
|
||||
})
|
||||
|
||||
const statusTagTheme = (status: string): 'success' | 'warning' | 'danger' => {
|
||||
if (status === 'completed') return 'success'
|
||||
if (status === 'failed') return 'danger'
|
||||
return 'warning'
|
||||
}
|
||||
|
||||
const onHistoryExpand = async (expandedKeys: (string | number)[]) => {
|
||||
// TDesign expand-change fires when keys change; we rely on toggleFileHistory instead
|
||||
}
|
||||
|
||||
const loadHistory = async () => {
|
||||
try {
|
||||
state.history = await apiRequest<HistoryData>('/api/history')
|
||||
|
||||
@@ -1,27 +1,21 @@
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<div class="page-header">
|
||||
<button class="btn-back" @click="router.back()">
|
||||
<t-button variant="text" @click="router.back()">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>
|
||||
返回
|
||||
</button>
|
||||
</t-button>
|
||||
<h1>分析结果</h1>
|
||||
</div>
|
||||
<div v-if="state.loading" class="loading-state">
|
||||
<div class="spinner"></div>
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
<t-loading :loading="state.loading" text="加载中...">
|
||||
<t-alert v-if="state.error" theme="error" :message="state.error" />
|
||||
|
||||
<div v-else-if="state.error" class="error-state">
|
||||
<p>{{ state.error }}</p>
|
||||
</div>
|
||||
|
||||
<div v-else-if="state.task" class="result-container">
|
||||
<div v-else-if="state.task" class="result-container">
|
||||
<div class="result-header">
|
||||
<h2>{{ state.task.filename }}</h2>
|
||||
<span :class="['badge', state.task.status === 'completed' ? 'badge-success' : 'badge-error']">
|
||||
<t-tag :type="state.task.status === 'completed' ? 'success' : 'danger'">
|
||||
{{ state.task.status }}
|
||||
</span>
|
||||
</t-tag>
|
||||
</div>
|
||||
|
||||
<div class="result-anchor-nav">
|
||||
@@ -39,7 +33,7 @@
|
||||
<div class="result-card result-card-highlight" style="cursor: pointer;" @click="selectScheme(selectedScheme?.scheme_id)">
|
||||
<div class="summary-header">
|
||||
<h3>推荐方案</h3>
|
||||
<span class="badge badge-info">{{ selectedScheme?.title || selectedScheme?.scheme_id || '方案待定' }}</span>
|
||||
<t-tag theme="primary">{{ selectedScheme?.title || selectedScheme?.scheme_id || '方案待定' }}</t-tag>
|
||||
</div>
|
||||
<div class="info-list">
|
||||
<div class="info-item">
|
||||
@@ -72,7 +66,7 @@
|
||||
<div class="result-card result-card-highlight" v-if="candidateSchemes.length > 1" style="cursor: pointer;" @click="selectScheme(candidateSchemes[1]?.scheme_id)">
|
||||
<div class="summary-header">
|
||||
<h3>备选方案</h3>
|
||||
<span class="badge badge-warning">1 个备选</span>
|
||||
<t-tag theme="warning">1 个备选</t-tag>
|
||||
</div>
|
||||
<div class="info-list">
|
||||
<template v-for="scheme in candidateSchemes.slice(1, 2)" :key="'alt-' + scheme.scheme_id">
|
||||
@@ -102,7 +96,7 @@
|
||||
<div class="result-card result-card-highlight" v-else>
|
||||
<div class="summary-header">
|
||||
<h3>方案概览</h3>
|
||||
<span class="badge badge-success">单一方案</span>
|
||||
<t-tag theme="success">单一方案</t-tag>
|
||||
</div>
|
||||
<div class="info-list">
|
||||
<div class="info-item">
|
||||
@@ -114,29 +108,26 @@
|
||||
</div>
|
||||
|
||||
<div v-if="state.task.status === 'completed'" class="export-buttons-bar" style="display: flex; gap: var(--space-2); flex-wrap: wrap; justify-content: center; padding: var(--space-3) 0;">
|
||||
<button class="btn-sm btn-primary" @click="exportCAD('step')" title="导出装配体STEP格式(UG/FreeCAD/SolidWorks通用)">
|
||||
<t-button type="primary" size="small" @click="exportCAD('step')" title="导出装配体STEP格式(UG/FreeCAD/SolidWorks通用)">
|
||||
📦 导出 STEP
|
||||
</button>
|
||||
<button class="btn-sm btn-secondary" @click="exportCAD('iges')" title="导出IGES格式(兼容旧系统)">
|
||||
</t-button>
|
||||
<t-button type="default" size="small" @click="exportCAD('iges')" title="导出IGES格式(兼容旧系统)">
|
||||
导出 IGES
|
||||
</button>
|
||||
<button class="btn-sm btn-secondary" @click="exportCAD('stl')" title="导出STL网格格式(3D打印预览)">
|
||||
</t-button>
|
||||
<t-button type="default" size="small" @click="exportCAD('stl')" title="导出STL网格格式(3D打印预览)">
|
||||
导出 STL
|
||||
</button>
|
||||
<button class="btn-sm btn-secondary" @click="exportCAD('brep')" title="导出BRep格式(FreeCAD原生)">
|
||||
</t-button>
|
||||
<t-button type="default" size="small" @click="exportCAD('brep')" title="导出BRep格式(FreeCAD原生)">
|
||||
导出 BRep
|
||||
</button>
|
||||
</t-button>
|
||||
</div>
|
||||
|
||||
<div id="preview-3d" v-if="selectedHtmlFile" class="viewer-section viewer-section-hero">
|
||||
<div class="summary-header">
|
||||
<h3>3D 预览</h3>
|
||||
<span class="badge badge-info">重点区域</span>
|
||||
</div>
|
||||
<div v-if="state.previewStatus === 'error'" class="inline-alert inline-alert-warning" style="margin-bottom: var(--space-3);">
|
||||
<div class="inline-alert-title">预览加载失败</div>
|
||||
<div class="inline-alert-message">HTML 已生成但加载异常,请检查该链接是否可访问:{{ selectedHtmlFile }}</div>
|
||||
<t-tag theme="primary">重点区域</t-tag>
|
||||
</div>
|
||||
<t-alert v-if="state.previewStatus === 'error'" theme="warning" title="预览加载失败" :message="'HTML 已生成但加载异常,请检查该链接是否可访问:' + selectedHtmlFile" style="margin-bottom: var(--space-3);" />
|
||||
<iframe
|
||||
:key="selectedHtmlFile"
|
||||
:src="selectedHtmlFile"
|
||||
@@ -149,66 +140,66 @@
|
||||
<div id="preview-3d-placeholder" v-else class="viewer-section viewer-section-hero">
|
||||
<div class="summary-header">
|
||||
<h3>3D 预览</h3>
|
||||
<span class="badge badge-warning">未生成</span>
|
||||
</div>
|
||||
<div class="inline-alert inline-alert-warning">
|
||||
<div class="inline-alert-title">预览未生成</div>
|
||||
<div class="inline-alert-message">当前任务没有返回 HTML 预览链接,属于未生成状态。</div>
|
||||
<t-tag theme="warning">未生成</t-tag>
|
||||
</div>
|
||||
<t-alert theme="warning" title="预览未生成" message="当前任务没有返回 HTML 预览链接,属于未生成状态。" />
|
||||
</div>
|
||||
|
||||
<div id="candidate-schemes" v-if="candidateSchemes.length > 1" class="viewer-section diagnostic-panel" style="margin-top: var(--space-3);">
|
||||
<summary>📊 方案对比</summary>
|
||||
<div class="result-card full-width diagnostic-panel-body">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>方案</th>
|
||||
<th>方向</th>
|
||||
<th>位置</th>
|
||||
<th>总分</th>
|
||||
<th>可制造性</th>
|
||||
<th>分型质量</th>
|
||||
<th>倒扣数</th>
|
||||
<th>锁模力</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(scheme, idx) in candidateSchemes.slice(0, 2)" :key="'cmp-' + scheme.scheme_id" :style="state.selectedSchemeId === scheme.scheme_id ? 'background: rgba(var(--primary-rgb), 0.06); font-weight: 600;cursor:pointer' : 'cursor:pointer'" @click="selectScheme(scheme.scheme_id)">
|
||||
<td>
|
||||
<span v-if="idx === 0" style="color: var(--primary-color); font-weight: 600;">★ 推荐方案</span>
|
||||
<span v-else style="color: var(--warning-color); font-weight: 600;">备选方案</span>
|
||||
</td>
|
||||
<td>{{ formatSchemeDirection(scheme) }}</td>
|
||||
<td>{{ scheme.offset_label || '中面' }}</td>
|
||||
<td>{{ formatNumber(scheme.score) }}</td>
|
||||
<td>{{ formatNumber(scheme.score_breakdown?.manufacturability || 0) }}</td>
|
||||
<td>{{ formatNumber(scheme.score_breakdown?.parting_quality || 0) }}</td>
|
||||
<td>{{ scheme.key_info?.quality_considerations?.undercut_count || 0 }}</td>
|
||||
<td>{{ scheme.cavity_data?.manufacturing_info?.estimated_clamping_force || 'N/A' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<t-table
|
||||
:data="candidateSchemes.slice(0, 2)"
|
||||
row-key="scheme_id"
|
||||
stripe
|
||||
hover
|
||||
@row-click="(e: any) => selectScheme(e.row.scheme_id)"
|
||||
>
|
||||
<t-table-column title="方案">
|
||||
<template #default="{ rowIndex }">
|
||||
<span v-if="rowIndex === 0" style="color: var(--primary-color); font-weight: 600;">★ 推荐方案</span>
|
||||
<span v-else style="color: var(--warning-color); font-weight: 600;">备选方案</span>
|
||||
</template>
|
||||
</t-table-column>
|
||||
<t-table-column title="方向">
|
||||
<template #default="{ row }">{{ formatSchemeDirection(row) }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column title="位置">
|
||||
<template #default="{ row }">{{ row.offset_label || '中面' }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column title="总分">
|
||||
<template #default="{ row }">{{ formatNumber(row.score) }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column title="可制造性">
|
||||
<template #default="{ row }">{{ formatNumber(row.score_breakdown?.manufacturability || 0) }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column title="分型质量">
|
||||
<template #default="{ row }">{{ formatNumber(row.score_breakdown?.parting_quality || 0) }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column title="倒扣数">
|
||||
<template #default="{ row }">{{ row.key_info?.quality_considerations?.undercut_count || 0 }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column title="锁模力">
|
||||
<template #default="{ row }">{{ row.cavity_data?.manufacturing_info?.estimated_clamping_force || 'N/A' }}</template>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="ai-side-action" class="viewer-section">
|
||||
<div class="summary-header">
|
||||
<h3>AI 倒扣与抽芯分析</h3>
|
||||
<span
|
||||
<t-tag
|
||||
v-if="sideActionAiAdvice"
|
||||
:class="[
|
||||
'badge',
|
||||
sideActionAiAdvice.status === 'required'
|
||||
? 'badge-error'
|
||||
: sideActionAiAdvice.status === 'not_required'
|
||||
? 'badge-success'
|
||||
: 'badge-warning'
|
||||
]"
|
||||
:type="sideActionAiAdvice.status === 'required'
|
||||
? 'danger'
|
||||
: sideActionAiAdvice.status === 'not_required'
|
||||
? 'success'
|
||||
: 'warning'"
|
||||
>
|
||||
{{ sideActionAiAdvice.statusLabel }}
|
||||
</span>
|
||||
<span v-else class="badge badge-warning">未生成</span>
|
||||
</t-tag>
|
||||
<t-tag v-else theme="warning">未生成</t-tag>
|
||||
</div>
|
||||
|
||||
<div v-if="sideActionAiAdvice" class="result-grid">
|
||||
@@ -283,38 +274,35 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="inline-alert inline-alert-warning">
|
||||
<div class="inline-alert-title">AI 结论未生成</div>
|
||||
<div class="inline-alert-message">当前任务未生成倒扣与抽芯 AI 分析,请检查模型配置或重新运行分析。</div>
|
||||
</div>
|
||||
<t-alert v-else theme="warning" title="AI 结论未生成" message="当前任务未生成倒扣与抽芯 AI 分析,请检查模型配置或重新运行分析。" />
|
||||
</div>
|
||||
|
||||
|
||||
<div id="export-cam" class="viewer-section">
|
||||
<div class="summary-header">
|
||||
<h3>CAM 与 CNC 加工</h3>
|
||||
<span class="badge badge-info">工艺规划</span>
|
||||
<t-tag theme="primary">工艺规划</t-tag>
|
||||
</div>
|
||||
<div class="result-card" style="margin-bottom: var(--space-4);">
|
||||
<h4>CAM 参数</h4>
|
||||
<div class="info-list">
|
||||
<div class="info-item">
|
||||
<span class="info-label">模具钢</span>
|
||||
<select v-model="state.camForm.mold_steel" class="form-select">
|
||||
<option v-for="opt in camSteelOptions" :key="'steel-' + opt.value" :value="opt.value">{{ opt.label }}</option>
|
||||
</select>
|
||||
<t-select v-model="state.camForm.mold_steel" style="width:200px">
|
||||
<t-option v-for="opt in camSteelOptions" :key="'steel-' + opt.value" :value="opt.value" :label="opt.label" />
|
||||
</t-select>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">表面质量</span>
|
||||
<select v-model="state.camForm.surface_quality" class="form-select">
|
||||
<option v-for="opt in camSurfaceOptions" :key="'surf-' + opt.value" :value="opt.value">{{ opt.label }}</option>
|
||||
</select>
|
||||
<t-select v-model="state.camForm.surface_quality" style="width:200px">
|
||||
<t-option v-for="opt in camSurfaceOptions" :key="'surf-' + opt.value" :value="opt.value" :label="opt.label" />
|
||||
</t-select>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">控制器</span>
|
||||
<select v-model="state.camForm.controller" class="form-select">
|
||||
<option v-for="opt in camControllerOptions" :key="'ctrl-' + opt.value" :value="opt.value">{{ opt.label }}</option>
|
||||
</select>
|
||||
<t-select v-model="state.camForm.controller" style="width:200px">
|
||||
<t-option v-for="opt in camControllerOptions" :key="'ctrl-' + opt.value" :value="opt.value" :label="opt.label" />
|
||||
</t-select>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">包含G代码</span>
|
||||
@@ -324,18 +312,12 @@
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn-sm btn-primary" :disabled="state.camLoading" @click="generateCamPlan()" style="margin-top: var(--space-2);" title="基于当前选中方案生成CAM工艺计划">
|
||||
<t-button type="primary" size="small" :disabled="state.camLoading" @click="generateCamPlan()" style="margin-top: var(--space-2);" title="基于当前选中方案生成CAM工艺计划">
|
||||
{{ state.camLoading ? '⏳ 生成中...' : '🔧 生成 CAM 计划' }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="state.camError" class="inline-alert inline-alert-warning">
|
||||
<div class="inline-alert-title">CAM 计划生成失败</div>
|
||||
<div class="inline-alert-message">{{ state.camError }}</div>
|
||||
</div>
|
||||
<div v-else-if="!state.camPlan" class="inline-alert inline-alert-warning">
|
||||
<div class="inline-alert-title">尚未生成 CAM 计划</div>
|
||||
<div class="inline-alert-message">点击上方"生成 CAM 计划"后,将返回工序计划、刀具建议与制造风险提示。</div>
|
||||
</t-button>
|
||||
</div>
|
||||
<t-alert v-if="state.camError" theme="warning" title="CAM 计划生成失败" :message="state.camError" />
|
||||
<t-alert v-else-if="!state.camPlan" theme="warning" title="尚未生成 CAM 计划" message="点击上方"生成 CAM 计划"后,将返回工序计划、刀具建议与制造风险提示。" />
|
||||
<template v-else>
|
||||
<div class="result-grid">
|
||||
<div class="result-card">
|
||||
@@ -375,28 +357,22 @@
|
||||
</div>
|
||||
<div class="result-card full-width">
|
||||
<h4>工序计划</h4>
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>序号</th>
|
||||
<th>工序</th>
|
||||
<th>刀具</th>
|
||||
<th>主轴转速</th>
|
||||
<th>进给率</th>
|
||||
<th>预计时长(min)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="op in (state.camPlan.process_plan || [])" :key="'cam-op-' + op.seq">
|
||||
<td>{{ op.seq }}</td>
|
||||
<td>{{ op.operation }}</td>
|
||||
<td>{{ op.tool_id || 'N/A' }}</td>
|
||||
<td>{{ formatNumber(op.spindle_speed_rpm || 0) }}</td>
|
||||
<td>{{ formatNumber(op.feed_rate_mm_min || 0) }}</td>
|
||||
<td>{{ formatNumber(op.estimated_time_min || 0) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<t-table :data="state.camPlan.process_plan || []" row-key="seq" stripe>
|
||||
<t-table-column colKey="seq" title="序号" />
|
||||
<t-table-column colKey="operation" title="工序" />
|
||||
<t-table-column title="刀具">
|
||||
<template #default="{ row }">{{ row.tool_id || 'N/A' }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column title="主轴转速">
|
||||
<template #default="{ row }">{{ formatNumber(row.spindle_speed_rpm || 0) }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column title="进给率">
|
||||
<template #default="{ row }">{{ formatNumber(row.feed_rate_mm_min || 0) }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column title="预计时长(min)">
|
||||
<template #default="{ row }">{{ formatNumber(row.estimated_time_min || 0) }}</template>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
</div>
|
||||
<div class="result-card full-width">
|
||||
<h4>制造风险提示</h4>
|
||||
@@ -412,9 +388,9 @@
|
||||
<div id="llm-report" class="viewer-section" v-if="hasVisibleDesignReport">
|
||||
<div class="summary-header">
|
||||
<h3>LLM 设计报告</h3>
|
||||
<span class="badge" :class="(designReport?.overall_score || 0) >= 7 ? 'badge-success' : (designReport?.overall_score || 0) >= 5 ? 'badge-warning' : 'badge-error'">
|
||||
<t-tag :type="(designReport?.overall_score || 0) >= 7 ? 'success' : (designReport?.overall_score || 0) >= 5 ? 'warning' : 'danger'">
|
||||
评分 {{ designReport?.overall_score || 'N/A' }} / 10
|
||||
</span>
|
||||
</t-tag>
|
||||
</div>
|
||||
<div v-if="designReport?.overview" class="inline-note" style="margin-bottom: var(--space-4); font-size: 0.95rem; color: var(--text-primary);">
|
||||
{{ designReport?.overview }}
|
||||
@@ -438,18 +414,13 @@
|
||||
</template>
|
||||
|
||||
<template v-else-if="sec.type === 'params_table'">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-for="(h, hi) in sec.headers" :key="'dr-h-' + hi">{{ h }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(row, ri) in sec.rows" :key="'dr-r-' + ri">
|
||||
<td v-for="(cell, ci) in row" :key="'dr-c-' + ci">{{ cell }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<t-table
|
||||
:columns="sec.headers!.map((h: string, i: number) => ({ colKey: 'col_' + i, title: h }))"
|
||||
:data="buildDynamicTableData(sec.rows!)"
|
||||
row-key="_rowId"
|
||||
stripe
|
||||
bordered
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-else-if="sec.type === 'recommendations'">
|
||||
@@ -465,7 +436,8 @@
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t-loading>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -837,7 +809,7 @@ const sideActionAiAdvice = computed(() => {
|
||||
})
|
||||
|
||||
const ruleActionSummary = computed(() => {
|
||||
const summary = selectedScheme.value?.side_actions?.summary || {}
|
||||
const summary: any = selectedScheme.value?.side_actions?.summary || {}
|
||||
return {
|
||||
total_slider_count: summary.total_slider_count ?? 0,
|
||||
total_lifter_count: summary.total_lifter_count ?? 0,
|
||||
@@ -903,6 +875,14 @@ const resultAnchorLinks = [
|
||||
{ id: 'llm-report', label: '设计报告' }
|
||||
]
|
||||
|
||||
const buildDynamicTableData = (rows: unknown[][]) => {
|
||||
return rows.map((row, ri) => {
|
||||
const obj: Record<string, any> = { _rowId: ri }
|
||||
row.forEach((cell, ci) => { obj['col_' + ci] = cell })
|
||||
return obj
|
||||
})
|
||||
}
|
||||
|
||||
const selectScheme = (schemeId: string | undefined) => {
|
||||
if (schemeId) {
|
||||
state.selectedSchemeId = schemeId
|
||||
|
||||
@@ -5,100 +5,86 @@
|
||||
<h1>用户管理</h1>
|
||||
<p>管理系统用户和权限</p>
|
||||
</div>
|
||||
<button class="btn btn-primary" @click="openUserModal()">+ 添加用户</button>
|
||||
<t-button theme="primary" @click="openUserModal()">+ 添加用户</t-button>
|
||||
</div>
|
||||
|
||||
<div v-if="state.loading" class="loading-state">
|
||||
<div class="loading-spinner"></div>
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
<t-loading :loading="state.loading" size="large">
|
||||
<t-table :data="state.users" row-key="id" stripe>
|
||||
<t-table-column colKey="username" title="用户名" />
|
||||
<t-table-column colKey="email" title="邮箱" />
|
||||
<t-table-column colKey="full_name" title="姓名">
|
||||
<template #cell="{ row }">{{ row.full_name || '-' }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column colKey="is_active" title="状态">
|
||||
<template #cell="{ row }">
|
||||
<t-tag :theme="row.is_active ? 'success' : 'danger'">
|
||||
{{ row.is_active ? '正常' : '禁用' }}
|
||||
</t-tag>
|
||||
</template>
|
||||
</t-table-column>
|
||||
<t-table-column colKey="roles" title="角色">
|
||||
<template #cell="{ row }">
|
||||
<t-tag v-for="role in row.roles" :key="role" theme="primary" variant="light" style="margin-right: 4px;">
|
||||
{{ role }}
|
||||
</t-tag>
|
||||
</template>
|
||||
</t-table-column>
|
||||
<t-table-column colKey="created_at" title="注册时间">
|
||||
<template #cell="{ row }">{{ formatDateTime(row.created_at) }}</template>
|
||||
</t-table-column>
|
||||
<t-table-column colKey="actions" title="操作">
|
||||
<template #cell="{ row }">
|
||||
<t-space :size="4">
|
||||
<t-button theme="primary" size="small" @click="openUserModal(row)">编辑</t-button>
|
||||
<t-button theme="warning" size="small" @click="resetPassword(row)">重置密码</t-button>
|
||||
<t-button v-if="row.id !== storeUser?.id" theme="danger" size="small" @click="deleteUser(row)">删除</t-button>
|
||||
</t-space>
|
||||
</template>
|
||||
</t-table-column>
|
||||
</t-table>
|
||||
<t-empty v-if="!state.loading && state.users.length === 0" description="暂无用户数据" />
|
||||
</t-loading>
|
||||
|
||||
<div v-else class="table-container">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>用户名</th>
|
||||
<th>邮箱</th>
|
||||
<th>姓名</th>
|
||||
<th>状态</th>
|
||||
<th>角色</th>
|
||||
<th>注册时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="user in state.users" :key="user.id">
|
||||
<td>{{ user.username }}</td>
|
||||
<td>{{ user.email }}</td>
|
||||
<td>{{ user.full_name || '-' }}</td>
|
||||
<td>
|
||||
<span :class="['badge', user.is_active ? 'badge-success' : 'badge-error']">
|
||||
{{ user.is_active ? '正常' : '禁用' }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span v-for="role in (user as any).roles" :key="role" class="badge badge-info" style="margin-right: 4px;">
|
||||
{{ role }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ formatDateTime(user.created_at) }}</td>
|
||||
<td>
|
||||
<div class="action-buttons" style="display:flex; gap:4px;">
|
||||
<button class="btn btn-sm" style="background:var(--primary-500);color:white;" @click="openUserModal(user)">编辑</button>
|
||||
<button class="btn btn-sm" style="background:#eab308;color:white;" @click="resetPassword(user)">重置密码</button>
|
||||
<button v-if="(user as any).id !== storeUser?.id" class="btn btn-sm" style="background:var(--error);color:white;" @click="deleteUser(user)">删除</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div v-if="state.showUserModal" class="modal-overlay" @click.self="state.showUserModal = false">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2>{{ state.editingUser ? '编辑用户' : '添加用户' }}</h2>
|
||||
<button class="modal-close" @click="state.showUserModal = false">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label class="form-label">用户名</label>
|
||||
<input v-model="state.userForm.username" type="text" class="form-input" :disabled="!!state.editingUser" />
|
||||
<t-dialog
|
||||
v-model:visible="state.showUserModal"
|
||||
:header="state.editingUser ? '编辑用户' : '添加用户'"
|
||||
:close-on-overlay-click="true"
|
||||
width="520px"
|
||||
>
|
||||
<t-form label-width="80px">
|
||||
<t-form-item label="用户名">
|
||||
<t-input v-model="state.userForm.username" :disabled="!!state.editingUser" />
|
||||
</t-form-item>
|
||||
<t-form-item label="邮箱">
|
||||
<t-input v-model="state.userForm.email" type="email" />
|
||||
</t-form-item>
|
||||
<t-form-item v-if="!state.editingUser" label="密码">
|
||||
<t-input v-model="state.userForm.password" type="password" />
|
||||
</t-form-item>
|
||||
<t-form-item label="姓名">
|
||||
<t-input v-model="state.userForm.full_name" />
|
||||
</t-form-item>
|
||||
<t-form-item label="角色">
|
||||
<div style="display: flex; flex-direction: column; gap: 8px;">
|
||||
<label v-for="role in state.roles" :key="role.id" style="display: flex; align-items: center; gap: 6px; cursor: pointer;">
|
||||
<input type="checkbox" :value="role.id" v-model="state.userForm.role_ids" />
|
||||
{{ role.name }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">邮箱</label>
|
||||
<input v-model="state.userForm.email" type="email" class="form-input" />
|
||||
</div>
|
||||
<div class="form-group" v-if="!state.editingUser">
|
||||
<label class="form-label">密码</label>
|
||||
<input v-model="state.userForm.password" type="password" class="form-input" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">姓名</label>
|
||||
<input v-model="state.userForm.full_name" type="text" class="form-input" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">角色</label>
|
||||
<div class="checkbox-group" style="flex-direction: column; gap: 8px;">
|
||||
<label v-for="role in state.roles" :key="(role as any).id" class="checkbox-label">
|
||||
<input type="checkbox" :value="(role as any).id" v-model="state.userForm.role_ids" class="checkbox-input" />
|
||||
{{ (role as any).name }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" @click="state.showUserModal = false">取消</button>
|
||||
<button class="btn btn-primary" @click="saveUser">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
<template #footer>
|
||||
<t-button theme="default" @click="state.showUserModal = false">取消</t-button>
|
||||
<t-button theme="primary" @click="saveUser">保存</t-button>
|
||||
</template>
|
||||
</t-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { DialogPlugin } from 'tdesign-vue-next'
|
||||
import { apiRequest } from '@/shared/api'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { addNotification } from '@/shared/notification'
|
||||
@@ -139,6 +125,21 @@ const state = reactive({
|
||||
},
|
||||
})
|
||||
|
||||
function confirmDialog(header: string, body: string, theme: string, confirmText: string, cancelText: string): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
const dlg = DialogPlugin.confirm({
|
||||
header,
|
||||
body,
|
||||
theme: theme as any,
|
||||
confirmBtn: confirmText,
|
||||
cancelBtn: cancelText,
|
||||
onConfirm: () => { dlg.hide(); resolve(true) },
|
||||
onCancel: () => { dlg.hide(); resolve(false) },
|
||||
onClose: () => { resolve(false) },
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const loadUsers = async () => {
|
||||
try {
|
||||
state.users = await apiRequest<UserItem[]>('/api/auth/users')
|
||||
@@ -217,7 +218,7 @@ const saveUser = async () => {
|
||||
}
|
||||
|
||||
const deleteUser = async (user: UserItem) => {
|
||||
if (!confirm(`确定要删除用户 ${user.username} 吗?`)) return
|
||||
if (!await confirmDialog('删除确认', `确定要删除用户 ${user.username} 吗?`, 'warning', '删除', '取消')) return
|
||||
|
||||
try {
|
||||
await apiRequest(`/api/auth/users/${user.id}`, { method: 'DELETE' })
|
||||
|
||||
Reference in New Issue
Block a user