Add optimization branch updates

This commit is contained in:
汤学会
2026-06-12 16:29:05 +08:00
parent 64e7ac346d
commit 2a00248ac3
31 changed files with 838 additions and 76 deletions
@@ -481,11 +481,11 @@ assert.match(
);
assert.match(
inventoryLedgerSource,
/source_doc_type: isProductionSurplusInbound\.value \? "WORK_ORDER" : null[\s\S]*source_doc_id: isProductionSurplusInbound\.value \? Number\(selectedGenericWorkOrder\.value\.work_order_id\) : null/
/source_doc_type: isProductionSurplusInbound\.value \? "PRODUCTION_LEDGER" : null[\s\S]*source_doc_id: isProductionSurplusInbound\.value \? Number\(selectedGenericWorkOrder\.value\.production_ledger_id \|\| selectedGenericWorkOrder\.value\.work_order_id\) : null/
);
assert.match(
inventoryLedgerSource,
/source_doc_type: isProductionScrapInbound\.value \|\| isReworkScrapInbound\.value \? "WORK_ORDER" : activeWarehouseType\.value === "SEMI" \? "PRODUCT_ROUTE_OPERATION" : null[\s\S]*source_doc_id: isProductionScrapInbound\.value \|\| isReworkScrapInbound\.value \? Number\(selectedGenericWorkOrder\.value\.work_order_id\) : activeWarehouseType\.value === "SEMI" \? Number\(selectedSourceDocId \|\| 0\) : null/
/source_doc_type: isProductionScrapInbound\.value \? "PRODUCTION_LEDGER" : isReworkScrapInbound\.value \? "WORK_ORDER" : activeWarehouseType\.value === "SEMI" \? "PRODUCT_ROUTE_OPERATION" : null[\s\S]*isProductionScrapInbound\.value[\s\S]*Number\(selectedGenericWorkOrder\.value\.production_ledger_id \|\| selectedGenericWorkOrder\.value\.work_order_id\)[\s\S]*isReworkScrapInbound\.value[\s\S]*Number\(selectedGenericWorkOrder\.value\.work_order_id\)/
);
assert.match(inventoryLedgerSource, /:key="`inventory-main-table-\$\{activeInventoryTab\}`"/);
assert.match(inventoryLedgerSource, /:key="`inventory-controls-\$\{activeInventoryTab\}`"/);
@@ -0,0 +1,18 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import path from "node:path";
const root = process.cwd();
const source = readFileSync(path.join(root, "src/services/api.js"), "utf8");
assert.match(source, /\/auth\/login-key/, "login should fetch the backend public key before submitting");
assert.match(source, /password_ciphertext/, "login payload should submit encrypted password ciphertext");
assert.match(source, /login_key_id/, "login payload should include the public key id used for encryption");
assert.doesNotMatch(
source,
/JSON\.stringify\(\{\s*username,\s*password\s*\}\)/,
"login must not serialize the plaintext password field"
);
assert.doesNotMatch(source, /password:\s*password/, "login payload must not include plaintext password");
console.log("login password encryption checks passed");
@@ -19,7 +19,7 @@ assert.match(
assert.match(
source,
/!isFinishedOutsourcingInbound && !isProductionScrapInbound && !isOutsourcingScrapInbound/,
/!isFinishedOutsourcingInbound && !isProductionScrapInbound && !isReworkScrapInbound && !isOutsourcingScrapInbound/,
"outsourcing scrap inbound should hide the manual lot number field"
);
@@ -10,7 +10,7 @@ const app = read("src/App.vue");
const productionLedger = read("src/views/ProductionLedgerView.vue");
const inventory = read("src/views/InventoryLedgerView.vue");
assert.match(router, /ProductionLedgerView/, "router should import ProductionLedgerView");
assert.match(router, /component:\s*view\("ProductionLedgerView"\)/, "router should lazy load ProductionLedgerView");
assert.match(router, /name:\s*"production-ledger"/, "router should expose production-ledger route");
assert.match(router, /path:\s*"\/work-order-ledger"[\s\S]*redirect:\s*\{\s*name:\s*"production-ledger"\s*\}/, "old work-order-ledger route should redirect to production-ledger");
assert.match(app, /生产台账/, "sidebar should show 生产台账");
@@ -15,12 +15,12 @@ const inventoryView = readSource("src/views/InventoryLedgerView.vue");
assert.match(
router,
/import\s+ProductionLedgerView\s+from\s+"..\/views\/ProductionLedgerView\.vue";/,
"router should import ProductionLedgerView"
/component:\s*view\("ProductionLedgerView"\)/,
"router should lazy load ProductionLedgerView"
);
assert.match(
router,
/path:\s*"\/production-ledger"[\s\S]*?name:\s*"production-ledger"[\s\S]*?component:\s*ProductionLedgerView[\s\S]*?permission:\s*"MENU_WORK_ORDER"/,
/path:\s*"\/production-ledger"[\s\S]*?name:\s*"production-ledger"[\s\S]*?component:\s*view\("ProductionLedgerView"\)[\s\S]*?permission:\s*"MENU_WORK_ORDER"/,
"router should define production-ledger route with MENU_WORK_ORDER permission"
);
assert.match(
@@ -31,7 +31,7 @@ assert.match(
assert.match(
source,
/ref="salesOrderPickerRef"\s+class="order-tag-picker"\s+@pointerdown\.stop\s+@mousedown\.stop\s+@click\.stop/,
/ref="salesOrderPickerRef"[\s\S]*class="[^"]*\border-tag-picker\b[^"]*"[\s\S]*@pointerdown\.stop[\s\S]*@mousedown\.stop[\s\S]*@click\.stop/,
"sales order picker should keep internal clicks from bubbling into unrelated close handlers"
);
@@ -10,7 +10,11 @@ assert.doesNotMatch(source, /YL-NB|buildDefaultLotNo/, "purchase receipt UI must
assert.doesNotMatch(source, /送货单号|supplier_delivery_no/, "purchase receipt UI should not keep the duplicate supplier delivery number field");
assert.match(source, /后端自动生成库存批次号|系统自动生成库存批次号/, "purchase receipt UI should explain that inventory lot numbers are generated by the backend");
assert.match(source, /uploadResource/, "purchase receipt UI should upload logistics auxiliary photos");
assert.match(source, /selectedPurchaseTargetType\s*===\s*["']RAW["'][\s\S]*运单号[\s\S]*辅助照片/, "raw purchase receipt UI should show waybill and auxiliary photo fields");
assert.match(
source,
/if\s*\(\s*selectedPurchaseTargetType\.value\s*===\s*["']RAW["']\s*\)\s*\{[\s\S]*key:\s*["']waybill_no["'][\s\S]*label:\s*["']运单号["'][\s\S]*key:\s*["']order_photo["'][\s\S]*label:\s*["']辅助照片["']/,
"raw purchase receipt UI should show waybill and auxiliary photo fields"
);
assert.match(source, /validateLogisticsForm\s*\(\s*\)/, "raw purchase receipt submit should validate logistics information before posting");
assert.match(source, /waybill_no:\s*selectedPurchaseTargetType\.value\s*===\s*["']RAW["']\s*\?\s*form\.waybill_no/, "purchase receipt payload should include raw waybill number");
assert.match(source, /order_photo_url:\s*selectedPurchaseTargetType\.value\s*===\s*["']RAW["']\s*\?\s*form\.order_photo_url/, "purchase receipt payload should include raw auxiliary photo URL");
@@ -11,7 +11,7 @@ const requiredInventorySnippets = [
"返工废料入库",
"reworkScrapInbound",
"isReworkScrapInbound",
"source_doc_type: isProductionScrapInbound.value || isReworkScrapInbound.value ? \"WORK_ORDER\"",
"source_doc_type: isProductionScrapInbound.value ? \"PRODUCTION_LEDGER\" : isReworkScrapInbound.value ? \"WORK_ORDER\"",
"inbound_qty: inboundQty"
];
@@ -0,0 +1,29 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
const routerSource = readFileSync(new URL("../src/router/index.js", import.meta.url), "utf8");
assert.doesNotMatch(
routerSource,
/import\s+\w+View\s+from\s+"..\/views\/[^"]+\.vue";/,
"router should not statically import view components into the initial bundle"
);
assert.match(
routerSource,
/import\.meta\.glob\("\.\.\/views\/\*\.vue"\)/,
"router should register lazy view modules with Vite import.meta.glob"
);
for (const viewName of [
"LoginView",
"DashboardView",
"InventoryLedgerView",
"PurchaseOrderView",
"ProductionLedgerView",
"SystemPermissionView"
]) {
assert.match(routerSource, new RegExp(`component:\\s*view\\("${viewName}"\\)`), `${viewName} route should use lazy view loader`);
}
console.log("router lazy loading static checks passed");
@@ -1,9 +1,7 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
const appVuePath = resolve("frontend/src/App.vue");
const source = readFileSync(appVuePath, "utf8");
const source = readFileSync(new URL("../src/App.vue", import.meta.url), "utf8");
const navStart = source.indexOf("const navStages = [");
const navEnd = source.indexOf("\n];", navStart);
+9 -3
View File
@@ -139,13 +139,19 @@ const styleSource = readFileSync(
fileURLToPath(new URL("../src/styles/main.css", import.meta.url)),
"utf8"
);
const stocktakeDiffModalStart = styleSource.indexOf(".stocktake-diff-modal {");
const stocktakeDiffModalEnd = styleSource.indexOf("\n}", stocktakeDiffModalStart);
assert.notEqual(stocktakeDiffModalStart, -1);
assert.notEqual(stocktakeDiffModalEnd, -1);
const stocktakeDiffModalBlock = styleSource.slice(stocktakeDiffModalStart, stocktakeDiffModalEnd + 2);
assert.match(styleSource, /\.stocktake-diff-modal \.stocktake-diff-table-v2[\s\S]*margin: 14px 18px 0;/);
assert.match(styleSource, /\.stocktake-diff-modal \.stocktake-footer[\s\S]*min-height: 72px;/);
assert.match(styleSource, /\.stocktake-diff-modal-mask[\s\S]*box-sizing: border-box;/);
assert.match(styleSource, /\.stocktake-diff-modal-mask[\s\S]*z-index: 3;/);
assert.match(styleSource, /\.stocktake-diff-modal[\s\S]*height: min\(820px, 100%\);/);
assert.match(styleSource, /\.stocktake-diff-modal[\s\S]*max-height: 100%;/);
assert.doesNotMatch(styleSource, /\.stocktake-diff-modal[\s\S]*calc\(100vh - 64px\)/);
assert.match(stocktakeDiffModalBlock, /height: min\(820px, 100%\);/);
assert.match(stocktakeDiffModalBlock, /max-height: 100%;/);
assert.doesNotMatch(stocktakeDiffModalBlock, /calc\(100vh - 64px\)/);
assert.match(styleSource, /\.stocktake-diff-modal \.stocktake-footer[\s\S]*justify-content: flex-end;/);
assert.match(styleSource, /\.stocktake-confirm-mask[\s\S]*z-index: 4;/);
assert.match(styleSource, /\.stocktake-diff-cell,[\s\S]*overflow-wrap: anywhere;/);
@@ -18,8 +18,8 @@ assert.match(
assert.match(
routerSource,
/import SystemPermissionView from "\.\.\/views\/SystemPermissionView\.vue";/,
"router should import SystemPermissionView"
/component:\s*view\("SystemPermissionView"\)/,
"router should lazy load SystemPermissionView"
);
assert.match(routerSource, /path:\s*"\/system-management"[\s\S]*redirect:/, "router should add /system-management redirect");
assert.match(routerSource, /path:\s*"\/system-permissions"/, "router should add /system-permissions route");
@@ -203,7 +203,7 @@ assert.doesNotMatch(
);
assert.match(
systemPermissionSource,
/function fitMindMapToScreen\(\)[\s\S]*mindMapFitRequestKey\.value \+= 1/,
/async function requestMindMapFitToScreen[\s\S]*mindMapFitRequestKey\.value \+= 1[\s\S]*function fitMindMapToScreen\(\)\s*\{\s*requestMindMapFitToScreen\(\{ showToast: true, closeMenu: true \}\);/m,
"one-screen fit action should request a viewport fit explicitly"
);
assert.match(systemPermissionSource, /const updatePayload\s*=/, "org edit must build an update-only payload");
@@ -0,0 +1,40 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
function read(path) {
return readFileSync(new URL(`../${path}`, import.meta.url), "utf8");
}
function ruleBlock(source, selector) {
const escaped = selector.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const match = source.match(new RegExp(`${escaped}\\s*\\{[\\s\\S]*?\\n\\}`, "m"));
assert.ok(match, `Expected to find CSS block for ${selector}`);
return match[0];
}
function assertNoDeclaration(block, property) {
assert.ok(!new RegExp(`\\b${property}\\s*:`, "m").test(block), `Unexpected ${property} declaration in:\n${block}`);
}
const mainCss = read("src/styles/main.css");
const purchaseOrderView = read("src/views/PurchaseOrderView.vue");
const salesPlanningView = read("src/views/SalesPlanningView.vue");
const orgMindMap = read("src/components/OrgMindMap.vue");
assertNoDeclaration(ruleBlock(mainCss, ".workflow-drawer"), "border-left");
assertNoDeclaration(ruleBlock(mainCss, "body .login-console-frame"), "border-left");
assertNoDeclaration(ruleBlock(purchaseOrderView, ".section-title"), "border-left");
assertNoDeclaration(ruleBlock(salesPlanningView, ".section-title"), "border-left");
assertNoDeclaration(ruleBlock(purchaseOrderView, ".purchase-decision-section"), "border-right");
assert.ok(
!/transition:[^;]*(?:stroke-width|\br\s+[0-9.]+s)/.test(mainCss),
"SVG DAG transitions should not animate stroke-width or radius-like r properties"
);
assert.ok(
!/transition:[^;]*(?:\bwidth\b|\bheight\b)/.test(orgMindMap),
"Org mind map canvas should not animate width or height"
);
console.log("ui antipattern guard ok");
-1
View File
@@ -484,7 +484,6 @@ defineExpose({
.org-mind-map-canvas {
display: block;
transition: width 0.18s ease, height 0.18s ease;
}
.org-mind-map-connector {
+33 -45
View File
@@ -1,30 +1,18 @@
import { createRouter, createWebHistory } from "vue-router";
import DashboardView from "../views/DashboardView.vue";
import SalesPlanningView from "../views/SalesPlanningView.vue";
import EmployeeManagementView from "../views/EmployeeManagementView.vue";
import ProductManagementView from "../views/ProductManagementView.vue";
import BomManagementView from "../views/BomManagementView.vue";
import ProcessRouteManagementView from "../views/ProcessRouteManagementView.vue";
import LoginView from "../views/LoginView.vue";
import SupplierManagementView from "../views/SupplierManagementView.vue";
import PurchaseOrderView from "../views/PurchaseOrderView.vue";
import PurchaseReceiptView from "../views/PurchaseReceiptView.vue";
import InventoryLedgerView from "../views/InventoryLedgerView.vue";
import ProductionLedgerView from "../views/ProductionLedgerView.vue";
import OperationReportView from "../views/OperationReportView.vue";
import DeliveryManagementView from "../views/DeliveryManagementView.vue";
import EquipmentManagementView from "../views/EquipmentManagementView.vue";
import QualityInspectionView from "../views/QualityInspectionView.vue";
import FinanceReportView from "../views/FinanceReportView.vue";
import ProductSpecLiteView from "../views/ProductSpecLiteView.vue";
import SupplierMaterialLiteView from "../views/SupplierMaterialLiteView.vue";
import SystemPermissionView from "../views/SystemPermissionView.vue";
import SystemExtensionView from "../views/SystemExtensionView.vue";
import MasterDataImportView from "../views/MasterDataImportView.vue";
import { isAuthenticated, readSession } from "../services/api";
import { loadSmartOperationReportConfig } from "../services/systemFeatures";
const viewModules = import.meta.glob("../views/*.vue");
function view(name) {
const loader = viewModules[`../views/${name}.vue`];
if (!loader) {
throw new Error(`Missing route view: ${name}`);
}
return loader;
}
const masterDataPermissionOrder = [
{
routeName: "supplier-management",
@@ -224,13 +212,13 @@ const routes = [
{
path: "/login",
name: "login",
component: LoginView,
component: view("LoginView"),
meta: { title: "工业风登录", layout: "auth" }
},
{
path: "/",
name: "dashboard",
component: DashboardView,
component: view("DashboardView"),
meta: { title: "原材料批次生命周期追踪", requiresAuth: true, permission: "MENU_DASHBOARD" }
},
{
@@ -243,13 +231,13 @@ const routes = [
{
path: "/customer-management",
name: "customer-management",
component: SalesPlanningView,
component: view("SalesPlanningView"),
meta: { title: "客户名录", requiresAuth: true, permission: "MENU_SALES_PLANNING" }
},
{
path: "/order-list",
name: "order-list",
component: SalesPlanningView,
component: view("SalesPlanningView"),
meta: { title: "销售订单列表", requiresAuth: true, permission: "MENU_SALES_PLANNING" }
},
{
@@ -312,7 +300,7 @@ const routes = [
{
path: "/supplier-material-lite",
name: "supplier-material-lite",
component: SupplierMaterialLiteView,
component: view("SupplierMaterialLiteView"),
meta: {
title: "原材料名录",
requiresAuth: true,
@@ -322,7 +310,7 @@ const routes = [
{
path: "/product-spec-lite",
name: "product-spec-lite",
component: ProductSpecLiteView,
component: view("ProductSpecLiteView"),
meta: {
title: "产品需规清单",
requiresAuth: true,
@@ -332,31 +320,31 @@ const routes = [
{
path: "/supplier-management",
name: "supplier-management",
component: SupplierManagementView,
component: view("SupplierManagementView"),
meta: { title: "供应商名录", requiresAuth: true, permission: "MENU_SUPPLIER_MANAGEMENT" }
},
{
path: "/purchase-orders",
name: "purchase-orders",
component: PurchaseOrderView,
component: view("PurchaseOrderView"),
meta: { title: "采购订单", requiresAuth: true, permission: "MENU_PURCHASE_ORDER" }
},
{
path: "/purchase-receipts",
name: "purchase-receipts",
component: PurchaseReceiptView,
component: view("PurchaseReceiptView"),
meta: { title: "到货入库", requiresAuth: true, permission: "MENU_PURCHASE_RECEIPT" }
},
{
path: "/inventory-ledger",
name: "inventory-ledger",
component: InventoryLedgerView,
component: view("InventoryLedgerView"),
meta: { title: "原材料库存台账与批次", requiresAuth: true, permission: "MENU_INVENTORY_LEDGER" }
},
{
path: "/production-ledger",
name: "production-ledger",
component: ProductionLedgerView,
component: view("ProductionLedgerView"),
meta: { title: "生产台账", requiresAuth: true, permission: "MENU_WORK_ORDER" }
},
{
@@ -368,7 +356,7 @@ const routes = [
{
path: "/operation-reports",
name: "operation-reports",
component: OperationReportView,
component: view("OperationReportView"),
meta: { title: "工序报工", requiresAuth: true, permission: "MENU_OPERATION_REPORT" }
},
{
@@ -380,25 +368,25 @@ const routes = [
{
path: "/delivery-management",
name: "delivery-management",
component: DeliveryManagementView,
component: view("DeliveryManagementView"),
meta: { title: "发货台账", requiresAuth: true, permission: "MENU_DELIVERY_MANAGEMENT" }
},
{
path: "/equipment-management",
name: "equipment-management",
component: EquipmentManagementView,
component: view("EquipmentManagementView"),
meta: { title: "设备管理", requiresAuth: true, permission: "MENU_EQUIPMENT_MANAGEMENT" }
},
{
path: "/quality-inspection",
name: "quality-inspection",
component: QualityInspectionView,
component: view("QualityInspectionView"),
meta: { title: "质量检验", requiresAuth: true, permission: "MENU_QUALITY_INSPECTION" }
},
{
path: "/finance-reports",
name: "finance-reports",
component: FinanceReportView,
component: view("FinanceReportView"),
meta: { title: "冲压产品材料成本核算", requiresAuth: true, permission: "MENU_FINANCE_REPORT" }
},
{
@@ -416,25 +404,25 @@ const routes = [
{
path: "/bom-management",
name: "bom-management",
component: BomManagementView,
component: view("BomManagementView"),
meta: { title: "BOM管理", requiresAuth: true, permission: "MENU_BOM_MANAGEMENT" }
},
{
path: "/process-route-management",
name: "process-route-management",
component: ProcessRouteManagementView,
component: view("ProcessRouteManagementView"),
meta: { title: "工艺路线管理", requiresAuth: true, permission: "MENU_ROUTE_MANAGEMENT" }
},
{
path: "/employee-management",
name: "employee-management",
component: EmployeeManagementView,
component: view("EmployeeManagementView"),
meta: { title: "员工管理", requiresAuth: true, permission: "MENU_EMPLOYEE_MANAGEMENT" }
},
{
path: "/master-data-import",
name: "master-data-import",
component: MasterDataImportView,
component: view("MasterDataImportView"),
meta: {
title: "基础资料导入导出",
requiresAuth: true,
@@ -444,7 +432,7 @@ const routes = [
{
path: "/product-management",
name: "product-management",
component: ProductManagementView,
component: view("ProductManagementView"),
meta: { title: "产品管理", requiresAuth: true, permission: "MENU_PRODUCT_MANAGEMENT" }
},
{
@@ -460,13 +448,13 @@ const routes = [
{
path: "/system-permissions",
name: "system-permissions",
component: SystemPermissionView,
component: view("SystemPermissionView"),
meta: { title: "系统权限管理", requiresAuth: true, permission: "MENU_SYSTEM_PERMISSION" }
},
{
path: "/system-extension",
name: "system-extension",
component: SystemExtensionView,
component: view("SystemExtensionView"),
meta: { title: "系统拓展", requiresAuth: true, permission: "MENU_SYSTEM_EXTENSION" }
}
];
+55 -1
View File
@@ -245,10 +245,64 @@ export function isAuthenticated() {
return Boolean(readSession());
}
function pemToArrayBuffer(pem) {
const base64Text = pem
.replace(/-----BEGIN PUBLIC KEY-----/g, "")
.replace(/-----END PUBLIC KEY-----/g, "")
.replace(/\s/g, "");
const binary = window.atob(base64Text);
const bytes = new Uint8Array(binary.length);
for (let index = 0; index < binary.length; index += 1) {
bytes[index] = binary.charCodeAt(index);
}
return bytes.buffer;
}
function arrayBufferToBase64(buffer) {
const bytes = new Uint8Array(buffer);
let binary = "";
for (let index = 0; index < bytes.byteLength; index += 1) {
binary += String.fromCharCode(bytes[index]);
}
return window.btoa(binary);
}
async function encryptLoginPassword(password, publicKeyPem) {
if (!window.crypto?.subtle) {
throw new Error("当前访问方式不支持安全加密登录,请使用 HTTPS 或 localhost 地址访问系统");
}
const publicKey = await window.crypto.subtle.importKey(
"spki",
pemToArrayBuffer(publicKeyPem),
{
name: "RSA-OAEP",
hash: "SHA-256"
},
false,
["encrypt"]
);
const ciphertext = await window.crypto.subtle.encrypt(
{ name: "RSA-OAEP" },
publicKey,
new TextEncoder().encode(password)
);
return arrayBufferToBase64(ciphertext);
}
export async function login(username, password) {
const loginKey = await requestJson("/auth/login-key", {
method: "GET",
skipAuthRedirect: true
});
const passwordCiphertext = await encryptLoginPassword(password, loginKey.public_key_pem);
return requestJson("/auth/login", {
method: "POST",
body: JSON.stringify({ username, password }),
body: JSON.stringify({
username,
password_ciphertext: passwordCiphertext,
login_key_id: loginKey.key_id
}),
skipAuthRedirect: true
});
}
+5 -5
View File
@@ -2556,7 +2556,7 @@ a {
stroke-linejoin: bevel;
filter: drop-shadow(0 0 7px rgba(79, 180, 255, 0.34));
opacity: 0.92;
transition: stroke 0.14s ease, stroke-width 0.14s ease, opacity 0.14s ease, filter 0.14s ease;
transition: stroke 0.14s ease, opacity 0.14s ease, filter 0.14s ease;
}
.dag-edge-highlighted {
@@ -2605,7 +2605,7 @@ a {
stroke: rgba(6, 21, 34, 0.96);
stroke-width: 1.6;
filter: drop-shadow(0 0 8px rgba(79, 180, 255, 0.5));
transition: r 0.14s ease, opacity 0.14s ease, filter 0.14s ease, stroke-width 0.14s ease;
transition: opacity 0.14s ease, filter 0.14s ease;
}
.dag-edge-terminal-warning {
@@ -2621,7 +2621,7 @@ a {
stroke: rgba(144, 215, 255, 0.96);
stroke-width: 2.4;
filter: drop-shadow(0 0 8px rgba(79, 180, 255, 0.5));
transition: r 0.14s ease, opacity 0.14s ease, filter 0.14s ease, stroke-width 0.14s ease;
transition: opacity 0.14s ease, filter 0.14s ease;
}
.dag-edge-start-warning {
@@ -3825,7 +3825,7 @@ a {
background:
radial-gradient(circle at top right, rgba(79, 180, 255, 0.14), transparent 24%),
linear-gradient(180deg, rgba(9, 18, 29, 0.98), rgba(5, 11, 18, 0.98));
border-left: 1px solid rgba(79, 180, 255, 0.2);
border: 1px solid rgba(79, 180, 255, 0.2);
border-radius: 28px 0 0 28px;
box-shadow: -24px 0 70px rgba(0, 0, 0, 0.32);
}
@@ -15060,7 +15060,7 @@ body .login-console-frame {
position: relative;
z-index: 2;
padding: 20px 20px 22px 24px;
border-left: 2px solid rgba(56, 189, 248, 0.48);
border: 1px solid rgba(56, 189, 248, 0.28);
border-radius: 34px;
background:
linear-gradient(90deg, rgba(56, 189, 248, 0.12), transparent 22%),
@@ -411,6 +411,7 @@
:class="{ 'is-uploaded': genericForm.order_photo_url, 'is-busy': logisticsPhotoUploading }"
>
<input
class="modern-upload-input"
type="file"
accept="image/*"
:disabled="logisticsPhotoUploading"
+1
View File
@@ -160,6 +160,7 @@ async function handleLogin() {
try {
const user = await login(form.username, form.password);
form.password = "";
saveSession(user);
message.value = `欢迎回来,${user.display_name}`;
const redirect = typeof route.query.redirect === "string" ? route.query.redirect : "/";
+2 -2
View File
@@ -1444,7 +1444,7 @@ function renderPurchaseContractHtml(contractOrders) {
h1 { margin: 0 0 10px; text-align: center; letter-spacing: 10px; font-size: 28px; }
.contract-no { text-align: right; color: #475569; margin-bottom: 22px; }
.meta-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 24px; margin-bottom: 18px; line-height: 1.8; }
.section-title { margin: 22px 0 10px; padding-left: 10px; border-left: 4px solid #2563eb; font-weight: 800; font-size: 16px; }
.section-title { margin: 22px 0 10px; padding: 6px 10px; border: 1px solid #bfdbfe; border-radius: 4px; background: #eff6ff; font-weight: 800; font-size: 16px; }
table { width: 100%; border-collapse: collapse; margin: 8px 0 14px; font-size: 12px; }
th, td { border: 1px solid #94a3b8; padding: 7px 6px; vertical-align: middle; }
th { background: #eaf2ff; text-align: center; }
@@ -2281,7 +2281,7 @@ onUnmounted(() => {
min-width: 0;
padding: 0;
border: 0;
border-right: 2px solid var(--document-paper-grid, rgba(20, 14, 6, 0.88));
box-shadow: inset -1px 0 0 var(--document-paper-grid, rgba(20, 14, 6, 0.88));
border-radius: 0;
background: transparent;
}
+1 -1
View File
@@ -932,7 +932,7 @@ function renderContractHtml(orders) {
h1 { margin: 0 0 10px; text-align: center; letter-spacing: 10px; font-size: 28px; }
.contract-no { text-align: right; color: #475569; margin-bottom: 22px; }
.meta-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 24px; margin-bottom: 18px; line-height: 1.8; }
.section-title { margin: 22px 0 10px; padding-left: 10px; border-left: 4px solid #2563eb; font-weight: 800; font-size: 16px; }
.section-title { margin: 22px 0 10px; padding: 6px 10px; border: 1px solid #bfdbfe; border-radius: 4px; background: #eff6ff; font-weight: 800; font-size: 16px; }
table { width: 100%; border-collapse: collapse; margin: 8px 0 14px; font-size: 12px; }
th, td { border: 1px solid #94a3b8; padding: 7px 6px; vertical-align: middle; }
th { background: #eaf2ff; text-align: center; }