方案说明
{{ selectedScheme?.summary || selectedScheme?.reason || '基于当前几何与制造约束自动推荐' }}
分型方向
{{ formatSchemeDirection(selectedScheme) }}
模具结构
{{ selectedScheme?.mold_structure_type === 'two_half_cavity' ? '两板半腔(无独立模芯)' : '型腔 + 模芯' }}
分型面位置
{{ selectedScheme?.offset_label || '中面' }}
型腔数
{{ selectedCavityCount }} 腔
预计成型周期
{{ selectedInjectionSystem?.overall_assessment?.estimated_cycle_time || 'N/A' }} s
方案说明
{{ scheme.summary || scheme.reason || '暂无说明' }}
分型方向
{{ formatSchemeDirection(scheme) }}
模具结构
{{ scheme.mold_structure_type === 'two_half_cavity' ? '两板半腔(无独立模芯)' : '型腔 + 模芯' }}
分型面位置
{{ scheme.offset_label || '中面' }}
方案总分
{{ formatNumber(scheme.score) }}
详细方案说明
{{ selectedScheme?.summary || selectedScheme?.reason || '当前仅生成 1 套可用分模方案,已在推荐方案区域展示核心参数。' }}
任务信息
文件大小
{{ formatFileSize(state.task.file_size) }}
分析时间
{{ formatDateTime(state.task.completed_at) }}
状态
{{ state.task.status }}
几何概览
顶点数
{{ formatNumber(state.task.mesh_summary.vertex_count) }}
面数
{{ formatNumber(state.task.mesh_summary.face_count) }}
边数
{{ formatNumber(state.task.geometry_data?.topology?.edge_count || state.task.geometry_data?.topology?.edges || state.task.mesh_summary?.edge_count || 'N/A') }}
体积
{{ formatNumber(state.task.geometry_data.volume) }} mm³
表面积
{{ formatNumber(state.task.geometry_data.surface_area) }} mm²
📊 方案对比({{ Math.min(candidateSchemes.length, 2) }} 个方案)
| 方案 |
方向 |
位置 |
总分 |
法向匹配 |
可制造性 |
分型质量 |
倒扣数 |
锁模力 |
|
★
{{ scheme.title || scheme.scheme_id }}
|
{{ formatSchemeDirection(scheme) }} |
{{ scheme.offset_label || '中面' }} |
{{ formatNumber(scheme.score) }} |
{{ formatNumber(scheme.normal_alignment_score || 0) }} |
{{ formatNumber(scheme.score_breakdown?.manufacturability || 0) }} |
{{ formatNumber(scheme.score_breakdown?.parting_quality || 0) }} |
{{ scheme.key_info?.quality_considerations?.undercut_count || 0 }} |
{{ scheme.cavity_data?.manufacturing_info?.estimated_clamping_force || 'N/A' }} |
预览加载失败
HTML 已生成但加载异常,请检查该链接是否可访问:{{ selectedHtmlFile }}
预览未生成
当前任务没有返回 HTML 预览链接,属于未生成状态。
AI 结论
判断结果
{{ sideActionAiAdvice.conclusion }}
推荐方案
{{ sideActionAiAdvice.mechanismLabel }}
置信度
{{ Math.round(sideActionAiAdvice.confidence * 100) }}%
分析来源
{{ sideActionAiAdvice.source === 'ai' ? 'AI 模型' : '规则回退' }}
{{ sideActionAiAdvice.summary || '当前任务未返回更多摘要。' }}
AI 结论未生成
当前任务未生成倒扣与抽芯 AI 分析,请检查模型配置或重新运行分析。
CAM 参数
模具钢
表面质量
控制器
包含G代码
CAM 计划生成失败
{{ state.camError }}
尚未生成 CAM 计划
点击上方“生成 CAM 计划”后,将返回工序计划、刀具建议与制造风险提示。
计划摘要
方案ID
{{ state.camPlan.scheme_id || 'N/A' }}
总工序数
{{ state.camPlan.summary?.total_operations || 0 }}
预计总时长
{{ formatNumber(state.camPlan.summary?.total_estimated_time_min || 0) }} min
方案可信度
{{ formatNumber(state.camPlan.confidence?.score || 0) }}
刀具建议
粗加工刀具
{{ state.camPlan.tooling_suggestion?.roughing_tool?.tool_id || 'N/A' }}
精加工刀具
{{ state.camPlan.tooling_suggestion?.finishing_tool?.tool_id || 'N/A' }}
工序计划
| 序号 |
工序 |
刀具 |
主轴转速 |
进给率 |
预计时长(min) |
| {{ op.seq }} |
{{ op.operation }} |
{{ op.tool_id || 'N/A' }} |
{{ formatNumber(op.spindle_speed_rpm || 0) }} |
{{ formatNumber(op.feed_rate_mm_min || 0) }} |
{{ formatNumber(op.estimated_time_min || 0) }} |
{{ designReport.overview }}
{{ sec.heading }}
{{ item.level === 'high' ? '🔴' : item.level === 'medium' ? '🟡' : '🟢' }}
{{ item.content }}
{{ JSON.stringify(sec) }}
`
};
const InventoryView = {
setup() {
const router = useRouter();
const route = useRoute();
const deliveryDateInput = ref(null);
const expectedDateInput = ref(null);
const deliveryDateNativeInput = ref(null);
const expectedDateNativeInput = ref(null);
const state = reactive({
activeTab: 'dashboard',
backendDbReady: true,
backendDbMessage: '',
productCategory: 'finished',
dashboard: null,
financeSummary: null,
financePeriod: {
year: new Date().getFullYear(),
quarter: ''
},
financeTransactions: [],
receivables: [],
payables: [],
customerFinanceStatement: [],
supplierFinanceStatement: [],
customerProductStatement: [],
supplierProductStatement: [],
products: [],
materials: [],
finishedProducts: [],
purchaseOrders: [],
purchaseWarehouseId: null,
purchaseReceiveItems: [],
productionOrders: [],
productionPlan: null,
productionWarehouseId: null,
suppliers: [],
customers: [],
warehouses: [],
inventory: [],
movements: [],
loading: false,
showModal: false,
modalType: '',
editingItem: null,
productBomItems: [],
materialConsumptionItems: [],
showMaterialConsumptionModal: false,
consumedMaterials: [],
restockItems: [],
showRestockModal: false,
form: {}
});
const parseDateTimeLocal = (text) => {
if (!text) return null;
const raw = String(text).trim();
const normalized = raw.replace('T', ' ').slice(0, 16);
const m = normalized.match(/^(\d{4})-(\d{2})-(\d{2})\s(\d{2}):(\d{2})$/);
if (!m) return null;
const year = Number(m[1]);
const month = Number(m[2]);
const day = Number(m[3]);
const hour = Number(m[4]);
const minute = Number(m[5]);
if (!Number.isFinite(year + month + day + hour + minute)) return null;
return new Date(year, month - 1, day, hour, minute, 0);
};
const toPickerValue = (value) => {
if (!value) return '';
const raw = String(value).trim();
if (/^\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}/.test(raw)) return raw.slice(0, 16);
if (raw.includes('T')) return raw.replace('T', ' ').slice(0, 16);
const dt = new Date(raw);
if (!Number.isFinite(dt.getTime())) return '';
const pad = (n) => String(n).padStart(2, '0');
return `${dt.getFullYear()}-${pad(dt.getMonth() + 1)}-${pad(dt.getDate())} ${pad(dt.getHours())}:${pad(dt.getMinutes())}`;
};
const toApiDateTime = (value) => {
if (!value) return null;
const text = String(value).trim();
// 提取日期部分,忽略时间部分
if (text.includes('T')) {
return text.split('T')[0];
}
if (text.includes(' ')) {
return text.split(' ')[0];
}
// 如果是只有日期部分的格式 (yyyy-MM-dd)
if (text.length === 10) {
return text; // 直接返回日期格式,后端 Pydantic 会自动处理
}
// 如果是日期对象
if (value instanceof Date) {
const year = value.getFullYear();
const month = String(value.getMonth() + 1).padStart(2, '0');
const day = String(value.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
return text;
};
const toNativeValue = (value) => {
if (!value) return '';
const text = String(value).trim();
// 如果是只有日期部分的格式 (yyyy-MM-dd)
if (text.length === 10 && !text.includes('T') && !text.includes(' ')) {
return text; // 直接返回日期格式
}
// 处理带时间的格式
const isoText = text.replace(' ', 'T');
return isoText.length >= 16 ? isoText.slice(0, 16) : isoText;
};
const fromNativeValue = (value) => {
if (!value) return '';
const text = String(value).trim();
// 如果是只有日期部分的格式 (yyyy-MM-dd)
if (text.length === 10 && !text.includes('T') && !text.includes(' ')) {
return text; // 直接返回日期格式
}
// 处理带时间的格式
return text.replace('T', ' ').slice(0, 16);
};
let deliveryPicker = null;
let expectedPicker = null;
const destroyPickers = () => {
if (deliveryPicker) {
deliveryPicker.destroy();
deliveryPicker = null;
}
if (expectedPicker) {
expectedPicker.destroy();
expectedPicker = null;
}
};
const initPickers = () => {
destroyPickers();
if (typeof AirDatepicker !== 'function') return;
if (state.modalType === 'salesOrder' && deliveryDateInput.value) {
deliveryPicker = new AirDatepicker(deliveryDateInput.value, {
timepicker: false,
autoClose: true,
zIndex: 2005,
dateFormat: 'yyyy-MM-dd',
onSelect: ({ formattedDate }) => {
state.form.delivery_date = formattedDate || '';
state.form.delivery_date_native = toNativeValue(formattedDate || '');
}
});
const initial = parseDateTimeLocal(state.form.delivery_date);
if (initial) deliveryPicker.selectDate(initial, { silent: true });
}
if (state.modalType === 'purchaseOrder' && expectedDateInput.value) {
expectedPicker = new AirDatepicker(expectedDateInput.value, {
timepicker: false,
autoClose: true,
zIndex: 2005,
dateFormat: 'yyyy-MM-dd',
onSelect: ({ formattedDate }) => {
state.form.expected_date = formattedDate || '';
state.form.expected_date_native = toNativeValue(formattedDate || '');
}
});
const initial = parseDateTimeLocal(state.form.expected_date);
if (initial) expectedPicker.selectDate(initial, { silent: true });
}
};
const openDateTimePicker = (pickerKind) => {
if (pickerKind === 'delivery' && deliveryPicker) {
deliveryPicker.show();
return;
}
if (pickerKind === 'expected' && expectedPicker) {
expectedPicker.show();
return;
}
const nativeInput = pickerKind === 'delivery' ? deliveryDateNativeInput.value : expectedDateNativeInput.value;
if (!nativeInput) return;
if (typeof nativeInput.showPicker === 'function') {
nativeInput.showPicker();
return;
}
nativeInput.focus();
nativeInput.click();
};
const getMovementTypeLabel = (movementType) => {
const movementLabelMap = {
in: '其他入库',
out: '其他出库',
adjust: '库存调整',
purchase_in: '采购入库',
return_from_production: '生产退料入库',
outsource_return: '外协回库',
finish_in: '完工入库',
issue_to_production: '生产领料出库',
outsource_send: '外协发料出库',
shipment_out: '销售出库',
scrap_out: '报废出库'
};
return movementLabelMap[movementType] || movementType;
};
const getMovementBadgeClass = (movementType) => {
if (['purchase_in', 'return_from_production', 'outsource_return', 'finish_in', 'in'].includes(movementType)) {
return 'badge-success';
}
if (['issue_to_production', 'outsource_send', 'shipment_out', 'scrap_out', 'out'].includes(movementType)) {
return 'badge-error';
}
return 'badge-warning';
};
const getPurchaseOrderStatusLabel = (status) => {
const statusMap = {
draft: '已下单',
pending: '已下单',
received: '已收货',
paid: '已付款'
};
return statusMap[status] || status;
};
const isPurchaseOrderLocked = (status) => {
return ['received', 'paid'].includes(status);
};
const loadDashboard = async () => {
state.loading = true;
try {
state.dashboard = await apiRequest('/api/dashboard');
} catch (e) {
handleApiError(e, '加载仪表盘');
} finally {
state.loading = false;
}
};
const loadFinishedProducts = async () => {
state.loading = true;
try {
state.finishedProducts = await apiRequest('/api/products?item_type=finished&limit=100');
} catch (e) {
handleApiError(e, '加载成品');
} finally {
state.loading = false;
}
};
const loadProducts = async () => {
// Compatibility wrapper if needed, or just load finished products
await loadFinishedProducts();
};
const loadMaterials = async () => {
state.loading = true;
try {
state.materials = await apiRequest('/api/products?item_type=material&limit=100');
} catch (e) {
handleApiError(e, '加载物料');
} finally {
state.loading = false;
}
};
const loadWarehouses = async () => {
state.loading = true;
try {
state.warehouses = await apiRequest('/api/warehouses');
} catch (e) {
handleApiError(e, '加载仓库');
} finally {
state.loading = false;
}
};
const ensureStockBaseData = async () => {
if (!state.materials.length) {
await loadMaterials();
}
if (!state.warehouses.length) {
await loadWarehouses();
}
if (!state.warehouses.length) {
try {
await apiRequest('/api/warehouses', {
method: 'POST',
body: JSON.stringify({
name: '默认仓库'
})
});
await loadWarehouses();
addNotification('已自动创建默认仓库', 'success');
} catch (e) {
handleApiError(e, '自动创建默认仓库');
}
}
};
const loadSuppliers = async () => {
state.loading = true;
try {
state.suppliers = await apiRequest('/api/suppliers');
} catch (e) {
handleApiError(e, '加载供应商');
} finally {
state.loading = false;
}
};
const loadProductionOrders = async () => {
state.loading = true;
try {
const [orders, warehouses] = await Promise.all([
apiRequest('/api/sales-orders?limit=100'),
apiRequest('/api/warehouses')
]);
state.productionOrders = orders?.items || [];
state.warehouses = warehouses || [];
if (!state.productionWarehouseId) {
state.productionWarehouseId = state.warehouses.find(w => w.is_default)?.id || state.warehouses[0]?.id || null;
}
} catch (e) {
handleApiError(e, '加载按单生产数据');
} finally {
state.loading = false;
}
};
const loadPurchaseOrders = async () => {
state.loading = true;
try {
const [orders, warehouses] = await Promise.all([
apiRequest('/api/purchase-orders?limit=100'),
apiRequest('/api/warehouses')
]);
state.purchaseOrders = orders?.items || [];
state.warehouses = warehouses || [];
if (!state.purchaseWarehouseId) {
state.purchaseWarehouseId = state.warehouses.find(w => w.is_default)?.id || state.warehouses[0]?.id || null;
}
} catch (e) {
handleApiError(e, '加载采购订单');
} finally {
state.loading = false;
}
};
const loadCustomers = async () => {
state.loading = true;
try {
state.customers = await apiRequest('/api/customers');
} catch (e) {
handleApiError(e, '加载客户');
} finally {
state.loading = false;
}
};
const loadInventory = async () => {
state.loading = true;
try {
const data = await apiRequest('/api/inventory');
state.inventory = data?.items || [];
} catch (e) {
handleApiError(e, '加载库存');
} finally {
state.loading = false;
}
};
const loadMovements = async () => {
state.loading = true;
try {
const data = await apiRequest('/api/stock-movements');
state.movements = data?.items || [];
} catch (e) {
handleApiError(e, '加载变动记录');
} finally {
state.loading = false;
}
};
const loadFinance = async () => {
state.loading = true;
try {
const selectedYear = Number(state.financePeriod.year) || new Date().getFullYear();
const selectedQuarter = state.financePeriod.quarter ? Number(state.financePeriod.quarter) : null;
const periodQuery = selectedQuarter
? `year=${selectedYear}&quarter=${selectedQuarter}`
: `year=${selectedYear}`;
const [
summary,
transactions,
receivables,
payables,
customerStatement,
supplierStatement,
customerProductStatement,
supplierProductStatement
] = await Promise.all([
apiRequest(`/api/finance/summary?${periodQuery}`),
apiRequest(`/api/finance/transactions?status=confirmed&limit=20&${periodQuery}`),
apiRequest(`/api/finance/receivables?limit=20&${periodQuery}`),
apiRequest(`/api/finance/payables?limit=20&${periodQuery}`),
apiRequest(`/api/finance/partner-statement/customer?${periodQuery}`),
apiRequest(`/api/finance/partner-statement/supplier?${periodQuery}`),
apiRequest(`/api/finance/partner-product-statement/customer?${periodQuery}`),
apiRequest(`/api/finance/partner-product-statement/supplier?${periodQuery}`)
]);
state.financeSummary = summary;
state.financeTransactions = transactions;
state.receivables = receivables;
state.payables = payables;
state.customerFinanceStatement = customerStatement.items || [];
state.supplierFinanceStatement = supplierStatement.items || [];
state.customerProductStatement = customerProductStatement.items || [];
state.supplierProductStatement = supplierProductStatement.items || [];
} catch (e) {
handleApiError(e, '加载财务数据');
} finally {
state.loading = false;
}
};
const refreshFinanceByPeriod = () => {
if (state.activeTab === 'finance') {
loadFinance();
}
};
const switchTab = (tab) => {
state.activeTab = tab;
if (!state.backendDbReady) {
return;
}
switch (tab) {
case 'dashboard': loadDashboard(); break;
case 'sales_orders': loadProductionOrders(); break;
case 'products': loadFinishedProducts(); break;
case 'materials': loadMaterials(); break;
case 'purchases': loadPurchaseOrders(); break;
case 'inventory': loadInventory(); break;
case 'customers': loadCustomers(); break;
case 'suppliers': loadSuppliers(); break;
case 'finance': loadFinance(); break;
case 'movements': loadMovements(); break;
}
};
const menuGroups = [
{
key: 'overview',
title: '概览',
items: [
{ key: 'dashboard', label: '仪表盘' }
]
},
{
key: 'sales',
title: '销售',
items: [
{ key: 'sales_orders', label: '销售订单管理' }
]
},
{
key: 'purchase',
title: '采购',
items: [
{ key: 'purchases', label: '采购订单管理' }
]
},
{
key: 'product',
title: '产品',
items: [
{ key: 'products', label: '成品管理' },
{ key: 'materials', label: '物料管理' }
]
},
{
key: 'partner',
title: '往来单位',
items: [
{ key: 'customers', label: '客户管理' },
{ key: 'suppliers', label: '供应商管理' }
]
},
{
key: 'warehouse',
title: '仓库',
items: [
{ key: 'inventory', label: '库存管理' },
{ key: 'movements', label: '库存变动记录' }
]
},
{
key: 'finance',
title: '财务',
items: [
{ key: 'finance', label: '财务概览' }
]
}
];
const openGroups = reactive(
Object.fromEntries(menuGroups.map(g => [g.key, true]))
);
const toggleGroup = (groupKey) => {
openGroups[groupKey] = !openGroups[groupKey];
};
const activeMenu = computed(() => {
for (const group of menuGroups) {
const item = group.items.find(i => i.key === state.activeTab);
if (item) {
return { group, item };
}
}
return null;
});
const handleMenuClick = (itemKey) => {
switchTab(itemKey);
};
const switchProductCategory = (category) => {
state.productCategory = category;
};
const checkBackendHealth = async () => {
try {
const resp = await fetch('/health', { method: 'GET' });
if (!resp.ok) {
state.backendDbReady = false;
state.backendDbMessage = '后端服务异常,暂无法加载业务数据';
return;
}
const health = await resp.json().catch(() => null);
if (health && health.database_connected === false) {
state.backendDbReady = false;
state.backendDbMessage = '数据库未连接,当前仅可浏览界面,业务数据暂不可用';
return;
}
state.backendDbReady = true;
state.backendDbMessage = '';
} catch {
state.backendDbReady = false;
state.backendDbMessage = '无法连接后端服务';
}
};
const openCreateProduct = (itemType) => {
state.modalType = 'product';
state.editingItem = null;
state.form = {
item_type: itemType,
unit: itemType === 'material' ? 'kg' : '件',
min_stock: 0,
max_stock: 1000,
cost_price: 0,
sale_price: 0
};
state.showModal = true;
};
const openModal = async (type, item = null) => {
state.modalType = type;
state.editingItem = item;
if (item) {
if (type === 'salesOrder') {
// 检查订单状态,如果是已收款状态,则禁止编辑
if (item.status === 'paid') {
addNotification('已收款的销售订单禁止修改', 'warning');
state.modalType = null;
state.editingItem = null;
return;
}
await loadCustomers();
await loadFinishedProducts();
const detail = await apiRequest(`/api/sales-orders/${item.id}`);
// 加载已消耗的物料
const movements = await apiRequest(`/api/stock-movements?reference_type=sales_order&reference_id=${item.id}&movement_type=consumption`);
state.consumedMaterials = (movements || []).map(movement => ({
material_name: movement.product_name || movement.product_sku || '未知物料',
quantity: Math.abs(movement.quantity),
unit_price: movement.unit_price || 0,
amount: movement.total_amount || 0
}));
state.form = {
customer_id: detail.customer_id,
delivery_date: toPickerValue(detail.delivery_date),
delivery_date_native: toNativeValue(toPickerValue(detail.delivery_date)),
remark: detail.remark || '',
items: (detail.items || []).map(line => ({
mode: line.product_id ? 'existing' : 'new',
product_id: line.product_id,
product_sku: '',
product_name: '',
quantity: line.quantity,
unit_price: line.unit_price,
remark: line.remark || ''
}))
};
} else if (type === 'purchaseOrder') {
await loadSuppliers();
await loadMaterials();
const detail = await apiRequest(`/api/purchase-orders/${item.id}`);
state.form = {
supplier_id: detail.supplier_id,
expected_date: toPickerValue(detail.expected_date),
expected_date_native: toNativeValue(toPickerValue(detail.expected_date)),
remark: detail.remark || '',
items: (detail.items || []).map(line => ({
product_id: line.product_id,
quantity: line.quantity,
remark: line.remark || ''
}))
};
} else if (type === 'purchaseReceive') {
await loadWarehouses();
const detail = await apiRequest(`/api/purchase-orders/${item.id}`);
state.purchaseReceiveItems = (detail.items || [])
.map(line => ({
item_id: line.id,
material_label: `${line.product_sku || line.product_id} - ${line.product_name || ''}`.trim(),
remaining_quantity: Math.max((line.quantity || 0) - (line.received_quantity || 0), 0),
receive_quantity: Math.max((line.quantity || 0) - (line.received_quantity || 0), 0)
}))
.filter(line => line.remaining_quantity > 0);
state.form = {
warehouse_id: state.purchaseWarehouseId || state.warehouses.find(w => w.is_default)?.id || state.warehouses[0]?.id || null,
remark: ''
};
} else if (type === 'product') {
state.form = { ...item };
if (item.item_type === 'finished') {
await loadMaterials();
const bom = await apiRequest(`/api/products/${item.id}/materials`);
state.productBomItems = (bom.items || []).map(bomItem => ({
material_id: bomItem.material_id,
quantity: bomItem.quantity
}));
}
} else {
state.form = { ...item };
}
} else {
state.form = {};
if (type === 'inventoryItem') {
await ensureStockBaseData();
state.form = {
product_id: state.materials[0]?.id || null,
warehouse_id: state.warehouses.find(w => w.is_default)?.id || state.warehouses[0]?.id || null,
quantity: 0,
locked_quantity: 0,
batch_number: '',
location: ''
};
}
if (type === 'salesOrder') {
state.form = {
customer_id: null,
delivery_date: '',
delivery_date_native: '',
remark: '',
items: []
};
}
if (type === 'purchaseOrder') {
state.form = {
supplier_id: null,
expected_date: '',
expected_date_native: '',
remark: '',
items: []
};
}
if (type === 'product') {
state.form = {
item_type: 'finished',
unit: '件',
min_stock: 0,
max_stock: 1000,
cost_price: 0,
sale_price: 0
};
}
if (type === 'salesOrder') {
await loadCustomers();
await loadFinishedProducts();
state.form = {
customer_id: state.customers[0]?.id || null,
delivery_date: '',
remark: '',
items: [{
mode: 'new',
product_id: null,
product_sku: '',
product_name: '',
quantity: 1,
unit_price: 0,
remark: ''
}]
};
}
if (type === 'purchaseOrder') {
await loadSuppliers();
await loadMaterials();
state.form = {
supplier_id: state.suppliers[0]?.id || null,
expected_date: '',
remark: '',
items: [{
product_id: state.materials[0]?.id || null,
quantity: 1,
remark: ''
}]
};
}
}
state.showModal = true;
nextTick(() => {
initPickers();
});
};
const openMaterialConsumptionModal = async () => {
// 确保是在销售订单编辑或新增页面
if (state.modalType !== 'salesOrder') {
addNotification('请先打开销售订单编辑页面', 'warning');
return;
}
// 加载物料和仓库数据
await loadMaterials();
await loadWarehouses();
// 初始化物料消耗列表
state.materialConsumptionItems = [];
// 打开物料消耗模态框
state.showMaterialConsumptionModal = true;
};
const addMaterialConsumptionItem = () => {
state.materialConsumptionItems.push({
material_id: state.materials[0]?.id || null,
quantity: 1,
remark: ''
});
};
const removeMaterialConsumptionItem = (index) => {
state.materialConsumptionItems.splice(index, 1);
};
const saveMaterialConsumption = async () => {
// 确保是在销售订单页面
if (state.modalType !== 'salesOrder') {
addNotification('请先打开销售订单编辑页面', 'warning');
return;
}
// 如果是新增订单,先保存订单再添加物料消耗
if (!state.editingItem) {
// 关闭物料消耗模态框
state.showMaterialConsumptionModal = false;
await saveSalesOrder();
return;
}
// 验证物料消耗项
for (const [i, item] of state.materialConsumptionItems.entries()) {
const idx = i + 1;
if (!item.material_id) {
addNotification(`第 ${idx} 行:请选择物料`, 'warning');
return;
}
if (!Number.isFinite(item.quantity) || item.quantity <= 0) {
addNotification(`第 ${idx} 行:数量必须大于 0`, 'warning');
return;
}
}
try {
// 调用 API 保存物料消耗
const result = await apiRequest(`/api/sales-orders/${state.editingItem.id}/consume-materials`, {
method: 'POST',
body: JSON.stringify({
items: state.materialConsumptionItems.map(item => ({
material_id: item.material_id,
quantity: item.quantity,
remark: item.remark
}))
})
});
addNotification('物料消耗记录保存成功', 'success');
// 关闭模态框
state.showMaterialConsumptionModal = false;
// 刷新订单详情
const detail = await apiRequest(`/api/sales-orders/${state.editingItem.id}`);
state.form = {
...state.form,
actual_material_cost: detail.actual_material_cost
};
// 刷新已消耗的物料
const movements = await apiRequest(`/api/stock-movements?reference_type=sales_order&reference_id=${state.editingItem.id}&movement_type=consumption`);
state.consumedMaterials = (movements || []).map(movement => ({
material_name: movement.product_name || movement.product_sku || '未知物料',
quantity: Math.abs(movement.quantity),
unit_price: movement.unit_price || 0,
amount: movement.total_amount || 0
}));
} catch (e) {
handleApiError(e, '保存物料消耗');
}
};
const openRestockModal = async () => {
// 加载物料和供应商数据
await loadMaterials();
await loadSuppliers();
// 初始化补货列表
state.restockItems = [];
// 打开补货模态框
state.showRestockModal = true;
};
const addRestockItem = () => {
state.restockItems.push({
material_id: state.materials[0]?.id || null,
quantity: 1,
unit_price: 0,
remark: ''
});
};
const removeRestockItem = (index) => {
state.restockItems.splice(index, 1);
};
const saveRestock = async () => {
// 验证补货项
if (!state.restockItems || state.restockItems.length === 0) {
addNotification('请至少添加一个补货物料', 'warning');
return;
}
for (const [i, item] of state.restockItems.entries()) {
const idx = i + 1;
if (!item.material_id) {
addNotification(`第 ${idx} 行:请选择物料`, 'warning');
return;
}
if (!Number.isFinite(item.quantity) || item.quantity <= 0) {
addNotification(`第 ${idx} 行:数量必须大于 0`, 'warning');
return;
}
if (!Number.isFinite(item.unit_price) || item.unit_price < 0) {
addNotification(`第 ${idx} 行:单价必须大于等于 0`, 'warning');
return;
}
}
try {
// 创建采购订单
const payload = {
supplier_id: state.suppliers[0]?.id || null,
expected_date: new Date().toISOString().split('T')[0],
remark: '物料补货',
items: state.restockItems.map(item => ({
product_id: item.material_id,
quantity: item.quantity,
remark: item.remark
}))
};
const result = await apiRequest('/api/purchase-orders', {
method: 'POST',
body: JSON.stringify(payload)
});
addNotification('采购订单创建成功', 'success');
// 关闭补货模态框
state.showRestockModal = false;
// 清空补货列表
state.restockItems = [];
// 刷新采购订单列表
loadPurchaseOrders();
} catch (e) {
handleApiError(e, '保存补货订单');
}
};
const closeModal = () => {
state.showModal = false;
state.modalType = '';
state.editingItem = null;
state.productBomItems = [];
state.purchaseReceiveItems = [];
state.form = {};
destroyPickers();
};
const saveProduct = async () => {
try {
if (state.editingItem) {
await apiRequest(`/api/products/${state.editingItem.id}`, {
method: 'PUT',
body: JSON.stringify(state.form)
});
if (state.form.item_type === 'finished' && state.productBomItems.length > 0) {
await apiRequest(`/api/products/${state.editingItem.id}/materials`, {
method: 'PUT',
body: JSON.stringify({ items: state.productBomItems })
});
}
addNotification('产品更新成功', 'success');
} else {
await apiRequest('/api/products', {
method: 'POST',
body: JSON.stringify(state.form)
});
addNotification('产品创建成功', 'success');
}
closeModal();
loadProducts();
loadMaterials();
} catch (e) {
handleApiError(e, '保存产品');
}
};
const deleteProduct = async (id) => {
if (!confirm('确定要删除这个产品吗?')) return;
try {
await apiRequest(`/api/products/${id}`, { method: 'DELETE' });
addNotification('产品已删除', 'success');
loadProducts();
loadMaterials();
} catch (e) {
handleApiError(e, '删除产品');
}
};
const addBomItem = () => {
state.productBomItems.push({
material_id: state.materials[0]?.id || null,
quantity: 1
});
};
const removeBomItem = (idx) => {
state.productBomItems.splice(idx, 1);
};
const saveSupplier = async () => {
try {
if (state.editingItem) {
await apiRequest(`/api/suppliers/${state.editingItem.id}`, {
method: 'PUT',
body: JSON.stringify(state.form)
});
addNotification('供应商更新成功', 'success');
} else {
await apiRequest('/api/suppliers', {
method: 'POST',
body: JSON.stringify(state.form)
});
addNotification('供应商创建成功', 'success');
}
closeModal();
loadSuppliers();
} catch (e) {
handleApiError(e, '保存供应商');
}
};
const deleteSupplier = async (id) => {
if (!confirm('确定要删除这个供应商吗?')) return;
try {
await apiRequest(`/api/suppliers/${id}`, { method: 'DELETE' });
addNotification('供应商已删除', 'success');
loadSuppliers();
} catch (e) {
handleApiError(e, '删除供应商');
}
};
const saveCustomer = async () => {
try {
if (state.editingItem) {
await apiRequest(`/api/customers/${state.editingItem.id}`, {
method: 'PUT',
body: JSON.stringify(state.form)
});
addNotification('客户更新成功', 'success');
} else {
await apiRequest('/api/customers', {
method: 'POST',
body: JSON.stringify(state.form)
});
addNotification('客户创建成功', 'success');
}
closeModal();
loadCustomers();
} catch (e) {
handleApiError(e, '保存客户');
}
};
const deleteCustomer = async (id) => {
if (!confirm('确定要删除这个客户吗?')) return;
try {
await apiRequest(`/api/customers/${id}`, { method: 'DELETE' });
addNotification('客户已删除', 'success');
loadCustomers();
} catch (e) {
handleApiError(e, '删除客户');
}
};
const saveInventoryItem = async () => {
try {
if (state.editingItem) {
await apiRequest(`/api/inventory/${state.editingItem.id}`, {
method: 'PUT',
body: JSON.stringify({
quantity: state.form.quantity,
locked_quantity: state.form.locked_quantity,
batch_number: state.form.batch_number,
location: state.form.location
})
});
addNotification('物料库存更新成功', 'success');
} else {
await apiRequest('/api/inventory', {
method: 'POST',
body: JSON.stringify(state.form)
});
addNotification('物料库存创建成功', 'success');
}
closeModal();
loadInventory();
} catch (e) {
handleApiError(e, '保存物料库存');
}
};
const deleteInventoryItem = async (id) => {
if (!confirm('确定要删除这个物料库存记录吗?')) return;
try {
await apiRequest(`/api/inventory/${id}`, { method: 'DELETE' });
addNotification('物料库存已删除', 'success');
loadInventory();
} catch (e) {
handleApiError(e, '删除物料库存');
}
};
const addSalesOrderItem = () => {
state.form.items = state.form.items || [];
state.form.items.push({
mode: 'new',
product_id: null,
product_sku: '',
product_name: '',
quantity: 1,
unit_price: 0,
remark: ''
});
};
const setSalesOrderLineMode = (line, mode) => {
line.mode = mode;
if (mode === 'new') {
line.product_id = null;
line.product_sku = '';
line.product_name = '';
} else {
line.product_sku = '';
line.product_name = '';
if (!line.product_id) {
line.product_id = state.finishedProducts[0]?.id || null;
}
}
};
const removeSalesOrderItem = (index) => {
state.form.items.splice(index, 1);
};
const saveSalesOrder = async () => {
try {
if (!state.form.customer_id) {
addNotification('请选择客户', 'warning');
return;
}
if (!state.form.items || !state.form.items.length) {
addNotification('请至少添加一个成品明细', 'warning');
return;
}
for (const [i, item] of state.form.items.entries()) {
const idx = i + 1;
if (item.mode === 'existing') {
if (!item.product_id) {
addNotification(`第 ${idx} 行:请选择模具`, 'warning');
return;
}
} else {
if (!item.product_sku || !String(item.product_sku).trim()) {
addNotification(`第 ${idx} 行:请输入模具SKU`, 'warning');
return;
}
if (!item.product_name || !String(item.product_name).trim()) {
addNotification(`第 ${idx} 行:请输入模具名称`, 'warning');
return;
}
}
if (!Number.isFinite(item.quantity) || item.quantity <= 0) {
addNotification(`第 ${idx} 行:数量必须大于 0`, 'warning');
return;
}
if (!Number.isFinite(item.unit_price) || item.unit_price < 0) {
addNotification(`第 ${idx} 行:单价必须大于等于 0`, 'warning');
return;
}
}
const payload = {
customer_id: state.form.customer_id,
delivery_date: toApiDateTime(state.form.delivery_date),
remark: state.form.remark,
items: state.form.items.map(item => ({
product_id: item.mode === 'existing' ? (item.product_id || null) : null,
product_sku: item.mode === 'existing' ? null : (item.product_sku || null),
product_name: item.mode === 'existing' ? null : (item.product_name || null),
product_category: null,
product_unit: '件',
quantity: item.quantity,
unit_price: item.unit_price,
remark: item.remark || ''
}))
};
let orderId;
if (state.editingItem) {
const result = await apiRequest(`/api/sales-orders/${state.editingItem.id}`, {
method: 'PUT',
body: JSON.stringify(payload)
});
orderId = state.editingItem.id;
addNotification(result.production_status === 'bom_missing' ? '订单已保存,但成品未配置BOM,未扣减物料' : '销售订单更新成功并已自动扣减物料', result.production_status === 'bom_missing' ? 'warning' : 'success');
} else {
const result = await apiRequest('/api/sales-orders', {
method: 'POST',
body: JSON.stringify(payload)
});
orderId = result.id;
addNotification(result.production_status === 'bom_missing' ? '订单已创建,但成品未配置BOM,未扣减物料' : '销售订单创建成功并已自动扣减物料', result.production_status === 'bom_missing' ? 'warning' : 'success');
}
// 如果有物料消耗记录,保存物料消耗
if (state.materialConsumptionItems && state.materialConsumptionItems.length > 0) {
try {
// 调用 API 保存物料消耗
const result = await apiRequest(`/api/sales-orders/${orderId}/consume-materials`, {
method: 'POST',
body: JSON.stringify({
items: state.materialConsumptionItems.map(item => ({
material_id: item.material_id,
quantity: item.quantity,
remark: item.remark
}))
})
});
addNotification('物料消耗记录保存成功', 'success');
// 清空物料消耗记录
state.materialConsumptionItems = [];
} catch (e) {
handleApiError(e, '保存物料消耗');
}
}
closeModal();
loadProductionOrders();
loadFinishedProducts();
loadInventory();
loadMovements();
} catch (e) {
handleApiError(e, '保存销售订单');
}
};
const updateSalesOrderStatus = async (order, targetStatus) => {
try {
await apiRequest(`/api/sales-orders/${order.id}/status`, {
method: 'PATCH',
body: JSON.stringify({ status: targetStatus })
});
addNotification('订单状态已更新', 'success');
loadProductionOrders();
} catch (e) {
handleApiError(e, '更新订单状态');
}
};
const updatePurchaseOrderStatus = async (order, targetStatus) => {
try {
await apiRequest(`/api/purchase-orders/${order.id}/status`, {
method: 'PATCH',
body: JSON.stringify({ status: targetStatus })
});
addNotification('采购订单状态已更新', 'success');
loadPurchaseOrders();
} catch (e) {
handleApiError(e, '更新采购订单状态');
}
};
const deleteSalesOrder = async (orderId) => {
if (!confirm('确定删除这个销售订单吗?系统会自动回补已扣减物料。')) return;
try {
await apiRequest(`/api/sales-orders/${orderId}`, { method: 'DELETE' });
addNotification('销售订单已删除并回补物料', 'success');
if (state.productionPlan?.sales_order_id === orderId) {
state.productionPlan = null;
}
loadProductionOrders();
loadInventory();
loadMovements();
} catch (e) {
handleApiError(e, '删除销售订单');
}
};
const addPurchaseOrderItem = () => {
state.form.items = state.form.items || [];
state.form.items.push({
product_id: state.materials[0]?.id || null,
quantity: 1,
remark: ''
});
};
const removePurchaseOrderItem = (index) => {
state.form.items.splice(index, 1);
};
const savePurchaseOrder = async () => {
try {
if (!state.form.supplier_id) {
addNotification('请选择供应商', 'warning');
return;
}
if (!state.form.items || !state.form.items.length) {
addNotification('请至少添加一个物料明细', 'warning');
return;
}
for (const [i, item] of state.form.items.entries()) {
const idx = i + 1;
if (!item.product_id) {
addNotification(`第 ${idx} 行:请选择物料`, 'warning');
return;
}
if (!Number.isFinite(item.quantity) || item.quantity <= 0) {
addNotification(`第 ${idx} 行:数量必须大于 0`, 'warning');
return;
}
}
const payload = {
supplier_id: state.form.supplier_id,
expected_date: toApiDateTime(state.form.expected_date),
remark: state.form.remark,
items: state.form.items.map(item => ({
product_id: item.product_id,
quantity: item.quantity,
remark: item.remark
}))
};
if (state.editingItem) {
await apiRequest(`/api/purchase-orders/${state.editingItem.id}`, {
method: 'PUT',
body: JSON.stringify(payload)
});
addNotification('采购订单更新成功', 'success');
} else {
await apiRequest('/api/purchase-orders', {
method: 'POST',
body: JSON.stringify(payload)
});
addNotification('采购订单创建成功', 'success');
}
closeModal();
loadPurchaseOrders();
} catch (e) {
handleApiError(e, '保存采购订单');
}
};
const deletePurchaseOrder = async (orderId) => {
if (!confirm('确定删除这个采购订单吗?')) return;
try {
await apiRequest(`/api/purchase-orders/${orderId}`, { method: 'DELETE' });
addNotification('采购订单已删除', 'success');
loadPurchaseOrders();
} catch (e) {
handleApiError(e, '删除采购订单');
}
};
const receivePurchaseOrder = async () => {
try {
if (!state.editingItem?.id) return;
const items = (state.purchaseReceiveItems || [])
.filter(line => Number(line.receive_quantity) > 0)
.map(line => ({
item_id: line.item_id,
receive_quantity: Number(line.receive_quantity)
}));
if (!items.length) {
addNotification('请填写本次入库数量', 'warning');
return;
}
await apiRequest(`/api/purchase-orders/${state.editingItem.id}/receive`, {
method: 'POST',
body: JSON.stringify({
warehouse_id: state.form.warehouse_id || state.purchaseWarehouseId,
items,
remark: state.form.remark || ''
})
});
addNotification('采购到货入库成功', 'success');
closeModal();
loadPurchaseOrders();
loadInventory();
loadMovements();
} catch (e) {
handleApiError(e, '采购到货入库');
}
};
onMounted(() => {
if (!appState.user) {
router.push('/login');
return;
}
checkBackendHealth().then(() => {
if (!state.backendDbReady) {
addNotification(state.backendDbMessage || '业务服务不可用', 'warning');
return;
}
loadDashboard();
});
});
return {
state,
switchTab,
menuGroups,
openGroups,
toggleGroup,
activeMenu,
handleMenuClick,
formatNumber,
formatCurrency,
formatDateTime,
formatDate,
openModal,
switchProductCategory,
openCreateProduct,
closeModal,
deliveryDateInput,
expectedDateInput,
openDateTimePicker,
saveProduct,
deleteProduct,
addBomItem,
removeBomItem,
saveSupplier,
deleteSupplier,
saveCustomer,
deleteCustomer,
saveInventoryItem,
deleteInventoryItem,
addSalesOrderItem,
removeSalesOrderItem,
setSalesOrderLineMode,
saveSalesOrder,
updateSalesOrderStatus,
updatePurchaseOrderStatus,
deleteSalesOrder,
addPurchaseOrderItem,
removePurchaseOrderItem,
savePurchaseOrder,
deletePurchaseOrder,
receivePurchaseOrder,
loadPurchaseOrders,
loadProductionOrders,
refreshFinanceByPeriod,
getMovementTypeLabel,
getMovementBadgeClass,
openMaterialConsumptionModal,
addMaterialConsumptionItem,
removeMaterialConsumptionItem,
saveMaterialConsumption,
openRestockModal,
addRestockItem,
removeRestockItem,
saveRestock,
getPurchaseOrderStatusLabel,
isPurchaseOrderLocked
};
},
template: `
提示
{{ state.backendDbMessage }}
📦
{{ state.dashboard?.product_count || 0 }}
成品数量
📊
{{ state.dashboard?.total_stock || 0 }}
物料库存总量
💰
{{ formatCurrency(state.dashboard?.total_value || 0) }}
库存价值
🏭
{{ state.dashboard?.supplier_count || 0 }}
供应商
👥
{{ state.dashboard?.customer_count || 0 }}
客户
🏪
{{ state.dashboard?.warehouse_count || 0 }}
仓库
| SKU |
名称 |
分类 |
单位 |
成本价 |
销售价 |
基础物料成本 |
操作 |
| {{ product.sku }} |
{{ product.name }} |
{{ product.category || '-' }} |
{{ product.unit }} |
{{ formatCurrency(product.cost_price) }} |
{{ formatCurrency(product.sale_price) }} |
{{ formatCurrency(product.material_cost || 0) }} |
|
| SKU |
名称 |
分类 |
单位 |
成本价 |
最低库存 |
操作 |
| {{ product.sku }} |
{{ product.name }} |
{{ product.category || '-' }} |
{{ product.unit }} |
{{ formatCurrency(product.cost_price) }} |
{{ product.min_stock || '-' }} |
|
| SKU |
物料 |
仓库 |
数量 |
可用 |
操作 |
| {{ item.product_sku }} |
{{ item.product_name }} |
{{ item.warehouse_name }} |
{{ item.quantity }} |
{{ item.available_quantity }} |
|
| 采购单 |
供应商 |
状态 |
订单创建 |
预计到货 |
实际到货 |
实际付款 |
总金额 |
已付款 |
操作 |
| {{ order.order_no }} |
{{ order.supplier_name }} |
{{ getPurchaseOrderStatusLabel(order.status) }} |
{{ order.created_at ? formatDateTime(order.created_at) : '-' }} |
{{ order.expected_date ? formatDate(order.expected_date) : '-' }} |
{{ order.received_date ? formatDateTime(order.received_date) : '-' }} |
{{ order.paid_date ? formatDateTime(order.paid_date) : '-' }} |
{{ formatCurrency(order.total_amount || 0) }} |
{{ formatCurrency(order.paid_amount || 0) }} |
|
| 编码 |
名称 |
联系人 |
电话 |
邮箱 |
操作 |
| {{ supplier.code }} |
{{ supplier.name }} |
{{ supplier.contact_person || '-' }} |
{{ supplier.phone || '-' }} |
{{ supplier.email || '-' }} |
|
| 编码 |
名称 |
联系人 |
电话 |
邮箱 |
操作 |
| {{ customer.code }} |
{{ customer.name }} |
{{ customer.contact_person || '-' }} |
{{ customer.phone || '-' }} |
{{ customer.email || '-' }} |
|
| 销售单 |
客户 |
订单金额 |
交付日期 |
订单创建 |
实际交付 |
实际收款 |
订单状态 |
操作 |
| {{ order.order_no }} |
{{ order.customer_name }} |
{{ formatCurrency(order.total_amount || 0) }} |
{{ order.delivery_date ? formatDate(order.delivery_date) : '-' }} |
{{ order.created_at ? formatDateTime(order.created_at) : '-' }} |
{{ order.actual_delivery_date ? formatDateTime(order.actual_delivery_date) : '-' }} |
{{ order.actual_payment_date ? formatDateTime(order.actual_payment_date) : '-' }} |
{{ order.status === 'manufacturing' ? '制造中' : order.status === 'delivered' ? '已交付' : order.status === 'paid' ? '已收款' : order.status }} |
|
🧾
{{ formatCurrency(state.financeSummary?.receivable_total || 0) }}
应收总额
💸
{{ formatCurrency(state.financeSummary?.payable_total || 0) }}
应付总额
💵
{{ formatCurrency(state.financeSummary?.period_receipt_total || 0) }}
周期收款
🏦
{{ formatCurrency(state.financeSummary?.period_payment_total || 0) }}
周期付款
客户账款(周期)
| 客户 |
订单数 |
流水数 |
订单金额 |
订单已收 |
实收流水 |
应收余额 |
| {{ item.partner_name }} |
{{ item.order_count }} |
{{ item.transaction_count }} |
{{ formatCurrency(item.order_total) }} |
{{ formatCurrency(item.settled_total) }} |
{{ formatCurrency(item.transaction_total) }} |
{{ formatCurrency(item.outstanding_total) }} |
供应商账款(周期)
| 供应商 |
订单数 |
流水数 |
订单金额 |
订单已付 |
实付流水 |
应付余额 |
| {{ item.partner_name }} |
{{ item.order_count }} |
{{ item.transaction_count }} |
{{ formatCurrency(item.order_total) }} |
{{ formatCurrency(item.settled_total) }} |
{{ formatCurrency(item.transaction_total) }} |
{{ formatCurrency(item.outstanding_total) }} |
客户-商品追溯(周期)
| 客户 |
SKU |
商品 |
订单数 |
数量 |
订单金额 |
已结款 |
未结款 |
| {{ item.partner_name }} |
{{ item.product_sku || '-' }} |
{{ item.product_name }} |
{{ item.order_count }} |
{{ formatNumber(item.order_quantity) }} |
{{ formatCurrency(item.order_amount) }} |
{{ formatCurrency(item.settled_amount) }} |
{{ formatCurrency(item.outstanding_amount) }} |
供应商-商品追溯(周期)
| 供应商 |
SKU |
商品 |
订单数 |
数量 |
订单金额 |
已结款 |
未结款 |
| {{ item.partner_name }} |
{{ item.product_sku || '-' }} |
{{ item.product_name }} |
{{ item.order_count }} |
{{ formatNumber(item.order_quantity) }} |
{{ formatCurrency(item.order_amount) }} |
{{ formatCurrency(item.settled_amount) }} |
{{ formatCurrency(item.outstanding_amount) }} |
最近财务流水
| 单号 |
类型 |
往来方 |
金额 |
状态 |
日期 |
| {{ txn.txn_no }} |
{{ txn.txn_type === 'receipt' ? '收款' : '付款' }} |
{{ txn.partner_type === 'customer' ? '客户' : '供应商' }}#{{ txn.partner_id }} |
{{ formatCurrency(txn.amount) }} |
{{ txn.status === 'confirmed' ? '已确认' : '已作废' }} |
{{ formatDateTime(txn.txn_date) }} |
| 物料 |
类型 |
数量 |
变动前 |
变动后 |
时间 |
| {{ movement.product_name }}{{ movement.product_sku ? ' (' + movement.product_sku + ')' : '' }} |
{{ getMovementTypeLabel(movement.movement_type) }}
|
{{ movement.quantity }} |
{{ movement.before_quantity }} |
{{ movement.after_quantity }} |
{{ formatDateTime(movement.created_at) }} |
📦
暂无物料
请点击"添加物料"按钮添加物料消耗明细
`
};
const DesignSystemView = {
setup() {
const state = reactive({
ui: document.documentElement.dataset.ui || 'v1',
theme: document.documentElement.dataset.theme || 'light'
});
const tokensPreview = computed(() => {
const styles = getComputedStyle(document.documentElement);
const keys = [
'--bg-primary',
'--bg-secondary',
'--text-primary',
'--text-secondary',
'--border-default',
'--primary-500',
'--primary-600',
'--success-500',
'--warning-500',
'--danger-500',
'--radius-md',
'--radius-lg',
'--shadow-sm',
'--shadow-md',
'--duration-fast',
'--duration-normal'
];
return keys
.map(k => ({ key: k, value: styles.getPropertyValue(k).trim() }))
.filter(item => item.value);
});
const toggleTheme = () => {
const next = state.theme === 'dark' ? 'light' : 'dark';
state.theme = next;
document.documentElement.dataset.theme = next;
try { localStorage.setItem('gemold_theme', next); } catch (e) {}
};
const toggleUi = () => {
const next = state.ui === 'v2' ? 'v1' : 'v2';
state.ui = next;
document.documentElement.dataset.ui = next;
try { localStorage.setItem('gemold_ui_version', next); } catch (e) {}
};
return { state, tokensPreview, toggleTheme, toggleUi };
},
template: `
| Token |
Value |
Preview |
{{ t.key }} |
{{ t.value }} |
-
|
`
};
const ReleaseView = {
setup() {
const state = reactive({
ui: document.documentElement.dataset.ui || 'v1',
theme: document.documentElement.dataset.theme || 'light',
percent: 0,
uid: ''
});
const load = () => {
try { state.uid = localStorage.getItem('gemold_uid') || ''; } catch (e) { state.uid = ''; }
try {
const p = Number(localStorage.getItem('gemold_ui_rollout_percent') || 0);
state.percent = Number.isFinite(p) ? p : 0;
} catch (e) {
state.percent = 0;
}
};
const setUi = (v) => {
state.ui = v;
try { localStorage.setItem('gemold_ui_version', v); } catch (e) {}
document.documentElement.dataset.ui = v;
addNotification(`已切换 UI 到 ${v}(刷新后完全生效)`, 'success');
};
const setTheme = (v) => {
state.theme = v;
try { localStorage.setItem('gemold_theme', v); } catch (e) {}
document.documentElement.dataset.theme = v;
addNotification(`已切换主题到 ${v}(刷新后完全生效)`, 'success');
};
const savePercent = () => {
var p = Number(state.percent);
if (!Number.isFinite(p) || p < 0) p = 0;
if (p > 100) p = 100;
state.percent = p;
try { localStorage.setItem('gemold_ui_rollout_percent', String(p)); } catch (e) {}
addNotification(`已设置灰度比例为 ${p}%(新用户分桶生效)`, 'success');
};
const rollbackToV1 = () => {
try { localStorage.setItem('gemold_ui_version', 'v1'); } catch (e) {}
try { localStorage.setItem('gemold_ui_rollout_percent', '0'); } catch (e) {}
document.documentElement.dataset.ui = 'v1';
state.ui = 'v1';
state.percent = 0;
addNotification('已回滚到 v1(建议刷新页面确认)', 'warning');
};
onMounted(load);
return { state, setUi, setTheme, savePercent, rollbackToV1 };
},
template: `
`
};
const routes = [
{ path: "/", component: HomeView },
{ path: "/login", component: LoginView },
{ path: "/users", component: UsersView },
{ path: "/moldinsight", component: MoldInsightView },
{ path: "/moldinsight/result/:taskId", component: ResultView },
{ path: "/inventory", component: InventoryView },
{ path: "/_design-system", component: DesignSystemView },
{ path: "/_release", component: ReleaseView }
];
const router = createRouter({
history: createWebHistory(),
routes
});
router.beforeEach((to, from, next) => {
const publicPages = ['/login', '/_design-system', '/_release'];
const authRequired = !publicPages.includes(to.path);
if (authRequired && !appState.user) {
return next('/login');
}
if (to.path === '/login' && appState.user) {
return next('/');
}
next();
});
const app = createApp(App);
app.use(router);
app.mount("#app");