From 53e9b23a40186efd149fd5c5350b5eced1f69920 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期二, 25 八月 2026 17:54:26 +0800
Subject: [PATCH] feat(auth): 更新认证页面为农业数字化平台主题
---
src/views/wls/materialstock/data.ts | 29 ++
src/views/wls/productsales/modules/form.vue | 51 +++
src/packages/effects/layouts/src/authentication/authentication.vue | 627 ++++++++++++++++++++++++++++++++++++++++---
public/agriculture-login-bg.png | 0
src/api/mes/wm/productsales/detail/index.ts | 2
src/views/_core/authentication/login.vue | 6
src/views/wls/productsales/modules/line-list.vue | 12
src/packages/effects/layouts/src/authentication/form.vue | 2
src/packages/effects/common-ui/src/ui/authentication/login.vue | 11
src/packages/@core/ui-kit/shadcn-ui/src/components/input-password/input-password.vue | 8
src/api/mes/wm/materialstock/index.ts | 4
src/api/mes/wm/productsales/index.ts | 41 ++
src/layouts/auth.vue | 6
src/views/wls/productsales/data.ts | 17 +
14 files changed, 750 insertions(+), 66 deletions(-)
diff --git a/public/agriculture-login-bg.png b/public/agriculture-login-bg.png
new file mode 100644
index 0000000..4b2340c
--- /dev/null
+++ b/public/agriculture-login-bg.png
Binary files differ
diff --git a/src/api/mes/wm/materialstock/index.ts b/src/api/mes/wm/materialstock/index.ts
index edbd6fe..3527368 100644
--- a/src/api/mes/wm/materialstock/index.ts
+++ b/src/api/mes/wm/materialstock/index.ts
@@ -28,6 +28,10 @@
reservedQuantity?: number; // 鍗犵敤閲�
inTransitQuantity?: number; // 鍦ㄩ�旈噺
availableQuantity?: number; // 鍙敤閲�
+ stockState?: number; // 搴撳瓨鐘舵�侊細10鏆傚瓨搴擄紝20鍚堟牸搴擄紝30宸插嚭搴�
+ expiryDate?: string; // 鏈夋晥鏈�
+ ageDays?: number; // 搴撻緞澶╂暟
+ warningLevel?: 'LOW_STOCK' | 'EXPIRING' | 'AGEING' | null; // 椋庨櫓绫诲瀷
receiptTime?: string; // 鍏ュ簱鏃ユ湡
frozen?: boolean; // 鏄惁鍐荤粨
createTime?: Date; // 鍒涘缓鏃堕棿
diff --git a/src/api/mes/wm/productsales/detail/index.ts b/src/api/mes/wm/productsales/detail/index.ts
index 5aa0756..c6b36a9 100644
--- a/src/api/mes/wm/productsales/detail/index.ts
+++ b/src/api/mes/wm/productsales/detail/index.ts
@@ -19,6 +19,8 @@
locationName?: string; // 搴撳尯鍚嶇О
areaId?: number; // 搴撲綅缂栧彿
areaName?: string; // 搴撲綅鍚嶇О
+ bagCodeId?: number; // 琚嬬爜缂栧彿
+ palletCodeId?: number; // 鎵樼洏鐮佺紪鍙�
remark?: string; // 澶囨敞
}
}
diff --git a/src/api/mes/wm/productsales/index.ts b/src/api/mes/wm/productsales/index.ts
index 08c1e86..ab2c08e 100644
--- a/src/api/mes/wm/productsales/index.ts
+++ b/src/api/mes/wm/productsales/index.ts
@@ -23,7 +23,38 @@
shippingNumber?: string; // 杩愯緭鍗曞彿
status?: number; // 鍗曟嵁鐘舵��
remark?: string; // 澶囨敞
+ stockState?: number; // 搴撳瓨鐘舵��
createTime?: Date; // 鍒涘缓鏃堕棿
+ }
+
+ export interface ProductSalesScanRequest {
+ salesId: number;
+ scanContent: string;
+ quantity?: number;
+ }
+
+ export interface ProductSalesScanResponse {
+ scanType?: 'BAG' | 'PALLET' | string;
+ codeId?: number;
+ scanContent?: string;
+ itemId?: number;
+ itemCode?: string;
+ itemName?: string;
+ batchId?: number;
+ batchCode?: string;
+ materialStockId?: number;
+ availableQuantity?: number;
+ scanQuantity?: number;
+ outboundQuantity?: number;
+ alreadyScanned?: boolean;
+ detailId?: number;
+ warehouseId?: number;
+ locationId?: number;
+ areaId?: number;
+ palletExpanded?: boolean;
+ bagCount?: number;
+ inboundBagCount?: number;
+ message?: string;
}
}
@@ -62,6 +93,16 @@
return requestClient.put(`/mes/wm/product-sales/submit?id=${id}`);
}
+/** 鎵爜鎷h揣 */
+export function scanProductSales(
+ data: MesWmProductSalesApi.ProductSalesScanRequest,
+) {
+ return requestClient.post<MesWmProductSalesApi.ProductSalesScanResponse>(
+ '/mes/wm/product-sales/scan',
+ data,
+ );
+}
+
/** 鎵ц鎷h揣 */
export function stockProductSales(id: number) {
return requestClient.put(`/mes/wm/product-sales/stock?id=${id}`);
diff --git a/src/layouts/auth.vue b/src/layouts/auth.vue
index 1cdccb9..e8bdb59 100644
--- a/src/layouts/auth.vue
+++ b/src/layouts/auth.vue
@@ -8,7 +8,7 @@
declare const __APP_LOGO__: string;
-const appName = computed(() => preferences.copyright.companyName);
+const appName = computed(() => '绉嶆涓氭暟瀛楀寲骞冲彴');
const logo = computed(() => __APP_LOGO__ || preferences.logo.source);
const logoDark = computed(() => preferences.logo.sourceDark);
</script>
@@ -18,8 +18,8 @@
:app-name="appName"
:logo="logo"
:logo-dark="logoDark"
- :page-description="$t('authentication.pageDesc')"
- :page-title="$t('authentication.pageTitle')"
+ page-description="鏁板瓧璧嬭兘鍐滀笟 路 绉戞妧寮曢鏈潵"
+ page-title="绉嶆涓氭暟瀛楀寲骞冲彴"
>
<!-- 鑷畾涔夊伐鍏锋爮 -->
<!-- <template #toolbar></template> -->
diff --git a/src/packages/@core/ui-kit/shadcn-ui/src/components/input-password/input-password.vue b/src/packages/@core/ui-kit/shadcn-ui/src/components/input-password/input-password.vue
index 8e34e07..547fa8d 100644
--- a/src/packages/@core/ui-kit/shadcn-ui/src/components/input-password/input-password.vue
+++ b/src/packages/@core/ui-kit/shadcn-ui/src/components/input-password/input-password.vue
@@ -42,16 +42,18 @@
<slot name="strengthText"> </slot>
</p>
</template>
- <div
+ <button
:class="{
'top-3': !!passwordStrength,
'top-1/2 -translate-y-1/2 items-center': !passwordStrength,
}"
- class="text-foreground/60 hover:text-foreground absolute inset-y-0 right-0 flex cursor-pointer pr-3 text-lg leading-5"
+ type="button"
+ aria-label="鍒囨崲瀵嗙爜鍙鎬�"
+ class="text-foreground/60 hover:text-foreground absolute inset-y-0 right-0 flex cursor-pointer rounded-sm border-0 bg-transparent p-0 pr-3 text-lg leading-5 focus-visible:ring-2 focus-visible:ring-emerald-500/50 focus-visible:outline-none"
@click="show = !show"
>
<Eye v-if="show" class="size-4" />
<EyeOff v-else class="size-4" />
- </div>
+ </button>
</div>
</template>
diff --git a/src/packages/effects/common-ui/src/ui/authentication/login.vue b/src/packages/effects/common-ui/src/ui/authentication/login.vue
index 7aec52a..0f414df 100644
--- a/src/packages/effects/common-ui/src/ui/authentication/login.vue
+++ b/src/packages/effects/common-ui/src/ui/authentication/login.vue
@@ -104,7 +104,7 @@
</script>
<template>
- <div @keydown.enter.prevent="handleSubmit">
+ <div class="authentication-login" @keydown.enter.prevent="handleSubmit">
<slot name="title">
<Title>
<slot name="title">
@@ -124,7 +124,7 @@
<div
v-if="showRememberMe || showForgetPassword"
- class="mb-7 flex items-center justify-between"
+ class="mb-6 flex items-center justify-between"
>
<div class="flex-center">
<VbenCheckbox
@@ -138,8 +138,11 @@
<span
v-if="showForgetPassword"
- class="vben-link text-sm font-normal"
+ class="vben-link cursor-pointer rounded-sm text-sm font-normal outline-none focus-visible:ring-2 focus-visible:ring-emerald-500/50"
+ role="link"
+ tabindex="0"
@click="handleGo(forgetPasswordPath)"
+ @keydown.enter.prevent="handleGo(forgetPasswordPath)"
>
{{ $t('authentication.forgetPassword') }}
</span>
@@ -150,7 +153,7 @@
}"
:loading="loading"
aria-label="login"
- class="h-11 w-full rounded-xl text-base font-semibold shadow-sm transition-shadow duration-200 hover:shadow-md focus-visible:ring-2 focus-visible:ring-primary/40"
+ class="h-12 w-full rounded-xl bg-emerald-600 text-base font-semibold tracking-[0.18em] text-white shadow-[0_10px_24px_rgba(5,150,105,0.22)] transition-[background-color,box-shadow] duration-200 hover:bg-emerald-700 hover:shadow-[0_12px_28px_rgba(5,150,105,0.3)] focus-visible:ring-2 focus-visible:ring-emerald-500/50 disabled:cursor-not-allowed disabled:opacity-70"
@click="handleSubmit"
>
{{ submitButtonText || $t('common.login') }}
diff --git a/src/packages/effects/layouts/src/authentication/authentication.vue b/src/packages/effects/layouts/src/authentication/authentication.vue
index 3410935..bacba87 100644
--- a/src/packages/effects/layouts/src/authentication/authentication.vue
+++ b/src/packages/effects/layouts/src/authentication/authentication.vue
@@ -7,7 +7,6 @@
import { Copyright } from '../basic/copyright';
import AuthenticationFormView from './form.vue';
-import SloganIcon from './icons/slogan.vue';
import Toolbar from './toolbar.vue';
interface Props {
@@ -43,19 +42,18 @@
* @zh_CN 鏍规嵁涓婚閫夋嫨鍚堥�傜殑 logo 鍥炬爣
*/
const logoSrc = computed(() => {
- // 濡傛灉鏄殫鑹蹭富棰樹笖鎻愪緵浜� logoDark锛屽垯浣跨敤鏆楄壊涓婚鐨� logo
if (isDark.value && props.logoDark) {
return props.logoDark;
}
- // 鍚﹀垯浣跨敤榛樿鐨� logo
return props.logo;
});
+
</script>
<template>
<div
:class="[isDark ? 'dark' : '']"
- class="flex min-h-full flex-1 overflow-x-hidden select-none"
+ class="agri-auth flex min-h-full flex-1 overflow-x-hidden select-none"
>
<template v-if="toolbar">
<slot name="toolbar">
@@ -65,7 +63,7 @@
<!-- 宸︿晶璁よ瘉闈㈡澘 -->
<AuthenticationFormView
v-if="authPanelLeft"
- class="min-h-full w-2/5 flex-1"
+ class="agri-left-panel min-h-full w-2/5 flex-1"
data-side="left"
>
<template v-if="copyright" #copyright>
@@ -79,10 +77,10 @@
</AuthenticationFormView>
<slot name="logo">
- <!-- 澶撮儴 Logo 鍜屽簲鐢ㄥ悕绉� -->
+ <!-- 澶撮儴 Logo 鍜屽簲鐢ㄥ悕绉帮紙灏忓睆灞曠ず锛屽ぇ灞忕敱鍝佺墝鍖轰唬鏇匡級 -->
<div
v-if="logoSrc || appName"
- class="absolute top-0 left-0 z-10 flex flex-1"
+ class="agri-mini-logo absolute top-0 left-0 z-10 flex flex-1"
@click="clickLogo"
>
<div
@@ -104,34 +102,161 @@
</slot>
<!-- 绯荤粺浠嬬粛 -->
- <div v-if="!authPanelCenter" class="relative hidden w-0 flex-1 lg:block">
- <div
- class="absolute inset-0 size-full bg-background-deep dark:bg-[#070709]"
- >
- <div class="login-background absolute top-0 left-0 size-full"></div>
+ <div v-if="!authPanelCenter" class="relative hidden w-0 flex-[1.18] lg:block">
+ <div class="agri-promo-layer absolute inset-0 size-full overflow-hidden">
<div
:key="authPanelLeft ? 'left' : authPanelRight ? 'right' : 'center'"
- class="mr-20 flex-col-center h-full px-8 text-center"
- :class="{
- 'enter-x': authPanelLeft,
- '-enter-x': authPanelRight,
- }"
+ class="agri-promo relative flex h-full flex-col justify-center px-8 text-left"
>
- <template v-if="sloganImage">
- <img
- :alt="appName"
- :src="sloganImage"
- class="h-64 w-2/5 animate-float"
- />
- </template>
- <SloganIcon v-else :alt="appName" class="h-64 w-2/5 animate-float" />
- <div class="text-1xl mt-6 font-sans text-foreground lg:text-2xl">
- {{ pageTitle }}
+ <!-- 鍝佺墝鍖� -->
+ <div class="agri-brand">
+ <div class="flex items-center gap-4">
+ <svg viewBox="0 0 64 64" class="size-14 shrink-0" aria-hidden="true">
+ <circle
+ cx="32" cy="32" r="25" fill="none" stroke="#6ee7b7" stroke-width="2.5"
+ stroke-linecap="round" stroke-dasharray="118 40" transform="rotate(-30 32 32)"
+ />
+ <circle
+ cx="32" cy="32" r="25" fill="none" stroke="#a7f3d0" stroke-width="2.5"
+ stroke-linecap="round" stroke-dasharray="26 132" transform="rotate(115 32 32)"
+ />
+ <circle cx="53" cy="17" r="3" fill="#6ee7b7" />
+ <circle cx="9" cy="41" r="2.4" fill="#a7f3d0" />
+ <path d="M32 45c0-8 0-12 0-16" stroke="#a7f3d0" stroke-width="2.4" stroke-linecap="round" fill="none" />
+ <path d="M32 34C24 32 19.5 26 19.5 19.5 27.5 21.5 32 26 32 34Z" fill="#34d399" />
+ <path d="M32 30c6.5-1.5 10.5-6.5 10.5-12C36 20 32 24 32 30Z" fill="#6ee7b7" />
+ <path d="M20 47q12-6 24 0" stroke="#a7f3d0" stroke-width="2.4" stroke-linecap="round" fill="none" />
+ </svg>
+ <div>
+ <div class="agri-brand-title text-3xl font-semibold tracking-[0.14em] xl:text-4xl">
+ {{ pageTitle }}
+ </div>
+ <div class="agri-brand-sub mt-2 text-xs tracking-[0.3em] xl:text-sm">
+ AGRICULTURE DIGITAL PLATFORM
+ </div>
+ </div>
+ </div>
+ <div class="agri-brand-tag mt-6 flex items-center gap-4">
+ <span class="agri-brand-tag-line h-px w-12"></span>
+ <span class="text-base tracking-[0.22em]">{{ pageDescription }}</span>
+ <span class="agri-brand-tag-line h-px w-12"></span>
+ </div>
</div>
- <div class="mt-2 dark:text-muted-foreground">
- {{ pageDescription }}
+
+ <div class="mt-12 flex items-start gap-10">
+ <!-- 鏁版嵁鍗$墖鍒� -->
+ <div class="agri-stats flex w-60 shrink-0 flex-col gap-4" aria-label="骞冲彴杩愯姒傝">
+ <div class="agri-stat">
+ <span class="agri-stat-icon">
+ <svg viewBox="0 0 24 24" class="size-5" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
+ <path d="M12 20v-8" />
+ <path d="M12 12C12 8 9 5 4.5 4.5 5 9 8 12 12 12Z" />
+ <path d="M12 10c0-3.5 2.6-6 7-6.5-.4 4-3 6.5-7 6.5Z" />
+ <path d="M5 20h14" />
+ </svg>
+ </span>
+ <div>
+ <span class="agri-stat-label">绉嶆闈㈢Н</span>
+ <div class="agri-stat-num">12,580<i>浜�</i></div>
+ <span class="agri-stat-trend agri-stat-up">杈冩槰鏃� 鈫� 2.35%</span>
+ </div>
+ </div>
+ <div class="agri-stat">
+ <span class="agri-stat-icon">
+ <svg viewBox="0 0 24 24" class="size-5" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
+ <rect x="6" y="6" width="12" height="12" rx="2" />
+ <rect x="10" y="10" width="4" height="4" />
+ <path d="M9 2v4M15 2v4M9 18v4M15 18v4M2 9h4M2 15h4M18 9h4M18 15h4" />
+ </svg>
+ </span>
+ <div>
+ <span class="agri-stat-label">鍦ㄧ嚎璁惧</span>
+ <div class="agri-stat-num">1,286<i>鍙�</i></div>
+ <span class="agri-stat-trend agri-stat-up">杈冩槰鏃� 鈫� 5.21%</span>
+ </div>
+ </div>
+ <div class="agri-stat">
+ <span class="agri-stat-icon">
+ <svg viewBox="0 0 24 24" class="size-5" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
+ <path d="M12 3s-6 6.6-6 11a6 6 0 0 0 12 0c0-4.4-6-11-6-11Z" />
+ <path d="M9.5 14a2.5 2.5 0 0 0 2.5 2.5" />
+ </svg>
+ </span>
+ <div>
+ <span class="agri-stat-label">浠婃棩鐏屾簤</span>
+ <div class="agri-stat-num">328<i>浜�</i></div>
+ <span class="agri-stat-trend agri-stat-up">杈冩槰鏃� 鈫� 1.15%</span>
+ </div>
+ </div>
+ <div class="agri-stat">
+ <span class="agri-stat-icon agri-stat-icon-warn">
+ <svg viewBox="0 0 24 24" class="size-5" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
+ <path d="M18 9a6 6 0 1 0-12 0c0 5-2 6-2 6h16s-2-1-2-6" />
+ <path d="M10.3 19a2 2 0 0 0 3.4 0" />
+ </svg>
+ </span>
+ <div>
+ <span class="agri-stat-label">AI 棰勮</span>
+ <div class="agri-stat-num">6<i>鏉�</i></div>
+ <span class="agri-stat-trend agri-stat-down">杈冩槰鏃� 鈫� 12.50%</span>
+ </div>
+ </div>
+ </div>
+
+ <!-- 鐩戞祴闈㈡澘鍒� -->
+ <div class="agri-monitors hidden w-72 shrink-0 flex-col gap-6 xl:flex">
+ <div class="agri-monitor">
+ <div class="agri-monitor-title">鍦熷¥澧掓儏鐩戞祴</div>
+ <div class="mt-3 flex items-baseline gap-2">
+ <svg viewBox="0 0 24 24" class="size-5 self-center" aria-hidden="true">
+ <path d="M12 3c0 0-6 6.6-6 11a6 6 0 0 0 12 0c0-4.4-6-11-6-11Z" fill="#7dd3fc" />
+ </svg>
+ <strong>68.5%</strong>
+ <span>鍦熷¥婀垮害</span>
+ </div>
+ <svg viewBox="0 0 260 92" class="mt-3 w-full" aria-hidden="true">
+ <defs>
+ <linearGradient id="agriSoilGrad" x1="0" y1="0" x2="0" y2="1">
+ <stop offset="0%" stop-color="rgba(110, 231, 183, 0.55)" />
+ <stop offset="100%" stop-color="rgba(110, 231, 183, 0.04)" />
+ </linearGradient>
+ </defs>
+ <g stroke="rgba(255, 255, 255, 0.14)" stroke-dasharray="3 4" stroke-width="1">
+ <path d="M28 10h226" /><path d="M28 45h226" /><path d="M28 80h226" />
+ </g>
+ <g fill="rgba(255, 255, 255, 0.55)" font-size="8">
+ <text x="2" y="13">100%</text><text x="6" y="48">50%</text><text x="6" y="83">0%</text>
+ </g>
+ <path
+ d="M28 62 C48 58 60 68 76 60 C94 51 108 64 124 56 C142 47 158 54 174 42 C192 30 210 40 228 30 C240 24 248 27 254 22 L254 80 L28 80 Z"
+ fill="url(#agriSoilGrad)"
+ />
+ <path
+ d="M28 62 C48 58 60 68 76 60 C94 51 108 64 124 56 C142 47 158 54 174 42 C192 30 210 40 228 30 C240 24 248 27 254 22"
+ fill="none" stroke="#6ee7b7" stroke-width="2" stroke-linecap="round"
+ />
+ </svg>
+ <div class="agri-monitor-axis">
+ <span>00:00</span><span>06:00</span><span>12:00</span><span>18:00</span><span>24:00</span>
+ </div>
+ </div>
+
+ <div class="agri-monitor agri-monitor-ai">
+ <div class="agri-monitor-title flex items-center gap-2">
+ <svg viewBox="0 0 24 24" class="size-4.5" fill="none" stroke="#a7f3d0" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
+ <path d="M12 3l1.9 5.1L19 10l-5.1 1.9L12 17l-1.9-5.1L5 10l5.1-1.9Z" />
+ <path d="M19 15l.9 2.1L22 18l-2.1.9L19 21l-.9-2.1L16 18l2.1-.9Z" />
+ </svg>
+ AI 鏅鸿兘鍒嗘瀽
+ </div>
+ <p>
+ 褰撳墠浣滅墿闀垮娍鑹ソ锛屽湡澹ゅ鎯呴�傚疁锛屾湭鏉�3澶╂棤鏄庢樉闄嶉洦锛屽缓璁�傛椂鐏屾簤锛屼繚璇佷綔鐗╃ǔ浜ч珮浜с��
+ </p>
+ </div>
+ </div>
</div>
</div>
+
</div>
</div>
@@ -154,37 +279,431 @@
</div>
<!-- 鍙充晶璁よ瘉闈㈡澘 -->
- <AuthenticationFormView
- v-if="authPanelRight"
- class="min-h-full w-2/5 flex-1"
- data-side="right"
- >
- <template v-if="copyright" #copyright>
- <slot name="copyright">
- <Copyright
- v-if="preferences.copyright.enable"
- v-bind="preferences.copyright"
- />
- </slot>
- </template>
- </AuthenticationFormView>
+ <div v-if="authPanelRight" class="agri-form-wrap relative">
+ <AuthenticationFormView class="agri-form-panel min-h-full w-full" data-side="right" />
+
+ <!-- 鍗$墖鍙充笂鏍戝彾瑁呴グ -->
+ <svg
+ class="pointer-events-none absolute top-0 right-0 h-36 w-40"
+ viewBox="0 0 180 150"
+ aria-hidden="true"
+ >
+ <path d="M180 4c-26 22-38 52-36 92" fill="none" stroke="#4ade80" stroke-width="2" stroke-linecap="round" />
+ <g fill="#4ade80" opacity="0.85">
+ <path d="M160 22q-16-10-30-2 12 12 30 2Z" />
+ <path d="M150 44q-18-6-30 4 14 10 30-4Z" />
+ <path d="M146 70q-18-2-28 10 16 7 28-10Z" />
+ <path d="M146 96q-16 2-24 14 16 4 24-14Z" />
+ </g>
+ <g fill="#86efac" opacity="0.7">
+ <path d="M164 34q4-16 18-22 2 16-18 22Z" />
+ <path d="M156 60q6-14 20-18 0 16-20 18Z" />
+ <path d="M152 86q8-12 22-14-2 16-22 14Z" />
+ </g>
+ <g fill="#bbf7d0" opacity="0.45">
+ <path d="M96 118q-14-8-26-1 10 10 26 1Z" />
+ <path d="M70 136q-12-6-22 0 9 8 22 0Z" />
+ </g>
+ </svg>
+
+ <!-- 鍗$墖搴曢儴鍐滃満鎻掔敾 -->
+ <svg
+ class="agri-farm pointer-events-none absolute inset-x-0 bottom-0 h-24 w-full"
+ viewBox="0 0 560 130"
+ preserveAspectRatio="xMidYMax slice"
+ aria-hidden="true"
+ >
+ <path d="M0 92 Q120 62 260 88 T560 80 V130 H0 Z" fill="#dcfce7" />
+ <path d="M0 112 Q160 88 320 110 T560 104 V130 H0 Z" fill="#bbf7d0" />
+ <g fill="#a7f3d0">
+ <path d="M36 76 L58 58 L80 76 Z" />
+ <rect x="42" y="76" width="32" height="22" fill="#ecfdf5" />
+ <rect x="54" y="84" width="9" height="14" fill="#86efac" />
+ </g>
+ <g>
+ <rect x="116" y="78" width="4" height="16" fill="#86efac" />
+ <circle cx="118" cy="70" r="12" fill="#86efac" />
+ <rect x="146" y="84" width="3.5" height="12" fill="#6ee7b7" />
+ <circle cx="148" cy="78" r="8" fill="#6ee7b7" />
+ </g>
+ <g stroke="#6ee7b7" stroke-width="3" stroke-linecap="round">
+ <path d="M470 100V64" />
+ <path d="M470 64l-14-10M470 64l16-6M470 64l-2 18" />
+ </g>
+ <g fill="#a7f3d0">
+ <path d="M498 82 L514 68 L530 82 Z" />
+ <rect x="503" y="82" width="22" height="16" fill="#ecfdf5" />
+ </g>
+ </svg>
+ </div>
</div>
</template>
<style scoped>
-.login-background {
- background:
- radial-gradient(circle at 72% 38%, hsl(var(--primary) / 24%), transparent 42%),
- linear-gradient(145deg, hsl(var(--background-deep)) 0%, hsl(var(--background)) 100%);
- filter: blur(70px);
+.agri-auth {
+ position: relative;
+ min-height: 100dvh;
+ overflow-y: auto;
+ background-color: #124b39;
+ background-image: url('/agriculture-login-bg.png');
+ background-position: center;
+ background-size: cover;
+ isolation: isolate;
}
-.dark {
- .login-background {
- background:
- radial-gradient(circle at 72% 38%, hsl(var(--primary) / 18%), transparent 42%),
- linear-gradient(145deg, hsl(var(--background-deep)) 0%, hsl(var(--background)) 100%);
- filter: blur(70px);
+.login-background {
+ background-image: none;
+ filter: saturate(1.06) contrast(1.01);
+}
+
+.agri-left-panel,
+.agri-left-panel :deep(*) {
+ background-color: transparent !important;
+}
+
+/* 鍝佺墝鏂囧瓧锛氱櫧鑹� + 鏂囧瓧闃村奖淇濊瘉鍦ㄨ儗鏅浘涓婄殑鍙鎬� */
+.agri-brand-title {
+ color: #fff;
+ text-shadow: 0 2px 10px rgba(2, 44, 34, 0.6);
+}
+
+.agri-brand-sub {
+ color: rgba(240, 253, 250, 0.92);
+ text-shadow: 0 1px 6px rgba(2, 44, 34, 0.55);
+}
+
+.agri-brand-tag {
+ color: rgba(240, 253, 250, 0.92);
+ text-shadow: 0 1px 6px rgba(2, 44, 34, 0.55);
+}
+
+.agri-brand-tag-line { background: rgba(167, 243, 208, 0.8); }
+
+/* 澶у睆闅愯棌宸︿笂瑙掑皬 logo锛岀敱鍝佺墝鍖哄睍绀� */
+@media (min-width: 1024px) {
+ .agri-mini-logo {
+ display: none;
+ }
+}
+
+/* ---------- 鍝佺墝涓庡唴瀹� ---------- */
+.agri-promo {
+ max-width: 780px;
+ margin-left: clamp(2rem, 6vw, 7rem);
+ padding-top: 3rem;
+ padding-bottom: 4.5rem;
+}
+
+.agri-brand svg {
+ filter: drop-shadow(0 6px 16px rgba(16, 185, 129, 0.35));
+}
+
+/* ---------- 鏁版嵁鍗$墖 ---------- */
+.agri-stat {
+ display: flex;
+ align-items: center;
+ gap: 0.85rem;
+ border: 1px solid rgba(167, 243, 208, 0.42);
+ border-radius: 1.1rem;
+ background: linear-gradient(135deg, rgba(8, 87, 65, 0.66), rgba(4, 57, 49, 0.3));
+ padding: 0.9rem 1rem;
+ box-shadow: 0 12px 28px rgba(0, 36, 28, 0.16);
+ transition: border-color 200ms ease, background 200ms ease;
+ color: rgba(236, 253, 245, 0.92);
+ backdrop-filter: blur(12px);
+}
+
+.agri-stat-icon {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 2.6rem;
+ height: 2.6rem;
+ flex-shrink: 0;
+ border-radius: 9999px;
+ background: linear-gradient(135deg, #10b981, #047857);
+ color: #ecfdf5;
+ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.22);
+}
+
+.agri-stat-icon-warn {
+ background: linear-gradient(135deg, #f59e0b, #b45309);
+}
+
+.agri-stat-label {
+ font-size: 0.75rem;
+ color: rgba(236, 253, 245, 0.75);
+}
+
+.agri-stat-num {
+ margin-top: 0.1rem;
+ font-size: 1.35rem;
+ font-weight: 700;
+ color: #fff;
+ line-height: 1.2;
+}
+
+.agri-stat-num i {
+ margin-left: 0.3rem;
+ font-size: 0.75rem;
+ font-style: normal;
+ font-weight: 400;
+ color: rgba(236, 253, 245, 0.8);
+}
+
+.agri-stat-trend {
+ display: block;
+ margin-top: 0.15rem;
+ font-size: 0.7rem;
+}
+
+.agri-stat-up { color: #6ee7b7; }
+.agri-stat-down { color: #fca5a5; }
+
+/* ---------- 鐩戞祴闈㈡澘 ---------- */
+.agri-monitor {
+ border: 1px solid rgba(125, 211, 252, 0.4);
+ border-radius: 1.1rem;
+ background: linear-gradient(160deg, rgba(8, 51, 68, 0.6), rgba(6, 78, 59, 0.32));
+ padding: 1rem 1.1rem;
+ color: rgba(236, 253, 245, 0.92);
+ backdrop-filter: blur(12px);
+ box-shadow: 0 0 24px rgba(56, 189, 248, 0.18), 0 14px 30px rgba(0, 36, 28, 0.18);
+}
+
+.agri-monitor-title {
+ font-size: 0.85rem;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ color: #bae6fd;
+}
+
+.agri-monitor strong {
+ font-size: 1.6rem;
+ font-weight: 700;
+ color: #fff;
+}
+
+.agri-monitor strong + span,
+.agri-monitor-title + div span {
+ font-size: 0.75rem;
+ color: rgba(236, 253, 245, 0.75);
+}
+
+.agri-monitor-axis {
+ display: flex;
+ justify-content: space-between;
+ margin-top: 0.35rem;
+ padding-left: 1.6rem;
+ font-size: 0.7rem;
+ color: rgba(255, 255, 255, 0.6);
+}
+
+.agri-monitor-ai {
+ margin-left: 2.5rem;
+ border-color: rgba(167, 243, 208, 0.4);
+ box-shadow: 0 0 24px rgba(52, 211, 153, 0.16), 0 14px 30px rgba(0, 36, 28, 0.18);
+}
+
+.agri-monitor-ai p {
+ margin: 0.6rem 0 0;
+ font-size: 0.78rem;
+ line-height: 1.7;
+ color: rgba(236, 253, 245, 0.85);
+}
+
+/* ---------- 鍙充晶鐧诲綍鍗$墖 ---------- */
+.agri-form-wrap {
+ align-self: center;
+ max-height: calc(100dvh - 5rem);
+ margin: 2.5rem clamp(1rem, 4vw, 4.5rem) 2.5rem 1.5rem;
+ width: 40%;
+ flex: 0.82 1 0%;
+ /* 澶栧眰缁熶竴鍦嗚瑁佸壀锛岄伩鍏嶅簳閮ㄦ彃鐢荤瓑鍏勫紵瑁呴グ鎶婂崱鐗囩洿瑙掑寲 */
+ overflow: hidden;
+ border-radius: 1.75rem;
+ box-shadow: 0 24px 70px rgba(2, 44, 34, 0.24);
+}
+
+.agri-form-panel {
+ overflow: auto;
+ border: 1px solid rgba(255, 255, 255, 0.9);
+ border-radius: 1.75rem;
+ background: rgba(250, 253, 251, 0.96) !important;
+ backdrop-filter: blur(18px);
+}
+
+/* 琛ㄥ崟鍐呭涓婄Щ锛屼负搴曢儴鎻掔敾鐣欏嚭绌洪棿 */
+.agri-form-wrap :deep(.side-content) {
+ margin-bottom: 4.5rem;
+}
+
+/* 鏍囬涓嬬殑缁胯壊瑁呴グ鏉� */
+.agri-form-wrap :deep(h2 + p)::after {
+ content: '';
+ display: block;
+ width: 2.75rem;
+ height: 0.28rem;
+ margin-top: 0.8rem;
+ border-radius: 9999px;
+ background: #16a34a;
+}
+
+/* 杈撳叆妗嗗墠缂�鍥炬爣 */
+.agri-form-wrap :deep(input[autocomplete='username']),
+.agri-form-wrap :deep(input[autocomplete='current-password']) {
+ padding-left: 2.6rem;
+ background-repeat: no-repeat;
+ background-position: 0.85rem center;
+ background-size: 1.05rem;
+}
+
+.agri-form-wrap :deep(input[autocomplete='username']) {
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2'/%3E%3Ccircle cx='12' cy='7' r='4'/%3E%3C/svg%3E");
+}
+
+.agri-form-wrap :deep(input[autocomplete='current-password']) {
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='11' width='18' height='11' rx='2'/%3E%3Cpath d='M7 11V7a5 5 0 0 1 10 0v4'/%3E%3C/svg%3E");
+}
+
+/* 杈撳叆妗嗘暣浣撹川鎰燂細鍔犻珮銆佸渾瑙掋�佹煍鍜岃竟妗嗐�佽仛鐒︾豢鑹插厜鍦� */
+.agri-form-wrap :deep(input) {
+ height: 3rem;
+ border-radius: 0.75rem;
+ border-color: rgba(15, 118, 110, 0.16);
+ background-color: #fff;
+ font-size: 0.95rem;
+ transition: border-color 200ms ease, box-shadow 200ms ease;
+}
+
+.agri-form-wrap :deep(input:hover) {
+ border-color: rgba(16, 185, 129, 0.45);
+}
+
+.agri-form-wrap :deep(input:focus),
+.agri-form-wrap :deep(input:focus-visible) {
+ border-color: #10b981;
+ box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.16);
+ outline: none;
+}
+
+.dark .agri-form-wrap :deep(input) {
+ border-color: rgba(255, 255, 255, 0.16);
+ background-color: rgba(255, 255, 255, 0.06);
+}
+
+.dark .agri-form-wrap :deep(input:focus),
+.dark .agri-form-wrap :deep(input:focus-visible) {
+ border-color: #34d399;
+ box-shadow: 0 0 0 3px rgba(52, 211, 153, 0.2);
+}
+
+@media (max-width: 1023px) {
+ .agri-farm {
+ display: none;
+ }
+}
+
+/* ---------- 鏆楄壊妯″紡 ---------- */
+.dark .login-background {
+ background-image:
+ linear-gradient(90deg, rgba(2, 26, 20, 0.9), rgba(2, 26, 20, 0.62)),
+ url('/agriculture-login-bg.png');
+}
+
+.dark .agri-form-panel {
+ background: rgba(13, 31, 25, 0.96) !important;
+}
+
+.dark .agri-form-wrap > svg {
+ opacity: 0.35;
+}
+
+/* ---------- 鍝嶅簲寮� ---------- */
+@media (max-width: 1023px) {
+ .agri-promo { margin-left: 2rem; }
+ .agri-form-wrap { margin-right: 1.25rem; }
+}
+
+@media (max-height: 720px) and (min-width: 1024px) {
+ .agri-form-wrap {
+ max-height: calc(100dvh - 2rem);
+ margin-top: 1rem;
+ margin-bottom: 1rem;
+ }
+
+ .agri-promo {
+ padding-top: 1.5rem;
+ padding-bottom: 3rem;
+ }
+
+ .agri-promo .mt-12 {
+ margin-top: 2rem;
+ }
+}
+
+@media (max-width: 767px) {
+ .agri-form-wrap {
+ align-self: stretch;
+ margin: 1rem;
+ width: auto;
+ flex: none;
+ }
+
+ .agri-form-panel {
+ border-radius: 1.5rem;
+ }
+}
+
+/* ---------- 鍏ュ満鍔ㄧ敾锛堢函 CSS锛屽姩鐢荤粨鏉熷繀鐒跺仠鐣欎簬鍙鎬侊級 ---------- */
+@media (prefers-reduced-motion: no-preference) {
+ .agri-promo {
+ animation: agri-in-left 0.75s cubic-bezier(0.22, 0.61, 0.36, 1) both;
+ }
+
+ .agri-form-wrap {
+ animation: agri-in-right 0.75s cubic-bezier(0.22, 0.61, 0.36, 1) both;
+ }
+
+ .agri-stat,
+ .agri-monitor {
+ animation: agri-in-up 0.55s cubic-bezier(0.22, 0.61, 0.36, 1) both;
+ }
+
+ .agri-stat:nth-child(1) { animation-delay: 0.2s; }
+ .agri-stat:nth-child(2) { animation-delay: 0.28s; }
+ .agri-stat:nth-child(3) { animation-delay: 0.36s; }
+ .agri-stat:nth-child(4) { animation-delay: 0.44s; }
+ .agri-monitor:nth-child(1) { animation-delay: 0.3s; }
+ .agri-monitor:nth-child(2) { animation-delay: 0.4s; }
+}
+
+@keyframes agri-in-left {
+ from {
+ opacity: 0;
+ transform: translateX(-28px);
+ }
+}
+
+@keyframes agri-in-right {
+ from {
+ opacity: 0;
+ transform: translateX(28px);
+ }
+}
+
+@keyframes agri-in-up {
+ from {
+ opacity: 0;
+ transform: translateY(18px);
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .agri-auth :deep(*) {
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ transition-duration: 0.01ms !important;
}
}
</style>
diff --git a/src/packages/effects/layouts/src/authentication/form.vue b/src/packages/effects/layouts/src/authentication/form.vue
index ddd756c..0a40a36 100644
--- a/src/packages/effects/layouts/src/authentication/form.vue
+++ b/src/packages/effects/layouts/src/authentication/form.vue
@@ -10,7 +10,7 @@
<template>
<div
- class="relative flex-col-center bg-background px-6 py-10 lg:flex-initial lg:px-8 dark:bg-background-deep"
+ class="relative flex-col-center bg-background px-6 py-8 lg:flex-initial lg:px-10 dark:bg-background-deep"
>
<slot></slot>
<!-- Router View with Transition and KeepAlive -->
diff --git a/src/views/_core/authentication/login.vue b/src/views/_core/authentication/login.vue
index 819eb4b..883e6ea 100644
--- a/src/views/_core/authentication/login.vue
+++ b/src/views/_core/authentication/login.vue
@@ -48,6 +48,8 @@
{
component: 'VbenInput',
componentProps: {
+ 'aria-label': $t('authentication.username'),
+ autocomplete: 'username',
placeholder: $t('authentication.usernameTip'),
},
fieldName: 'username',
@@ -60,6 +62,8 @@
{
component: 'VbenInputPassword',
componentProps: {
+ 'aria-label': $t('authentication.password'),
+ autocomplete: 'current-password',
placeholder: $t('authentication.passwordTip'),
},
fieldName: 'password',
@@ -79,6 +83,8 @@
ref="loginRef"
:form-schema="formSchema"
:loading="authStore.loginLoading"
+ title="娆㈣繋鍥炴潵"
+ sub-title="鐧诲綍绉嶆涓氭暟瀛楀寲骞冲彴锛屽紑鍚櫤鎱х妞�"
:show-code-login="false"
:show-qrcode-login="false"
:show-third-party-login="false"
diff --git a/src/views/wls/materialstock/data.ts b/src/views/wls/materialstock/data.ts
index 8cc4eb3..1edf653 100644
--- a/src/views/wls/materialstock/data.ts
+++ b/src/views/wls/materialstock/data.ts
@@ -62,6 +62,20 @@
},
},
{
+ fieldName: 'stockState',
+ label: '搴撳瓨鐘舵��',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: [
+ { label: '鏆傚瓨搴擄紙寰呰川妫�锛�', value: 10 },
+ { label: '鍚堟牸搴擄紙姝e紡搴撳瓨锛�', value: 20 },
+ { label: '宸插嚭搴擄紙閿�鍞墸鍑忥級', value: 30 },
+ ],
+ placeholder: '璇烽�夋嫨搴撳瓨鐘舵��',
+ },
+ },
+ {
fieldName: 'frozen',
label: '鏄惁鍐荤粨',
component: 'Select',
@@ -103,6 +117,21 @@
minWidth: 120,
},
{
+ field: 'stockState',
+ title: '搴撳瓨鐘舵��',
+ width: 150,
+ cellRender: {
+ name: 'CellTag',
+ props: {
+ options: [
+ { label: '鏆傚瓨搴�', value: 10, color: 'orange' },
+ { label: '鍚堟牸搴�', value: 20, color: 'green' },
+ { label: '宸插嚭搴�', value: 30, color: 'default' },
+ ],
+ },
+ },
+ },
+ {
field: 'quantity',
title: '鍦ㄥ簱鏁伴噺',
width: 100,
diff --git a/src/views/wls/productsales/data.ts b/src/views/wls/productsales/data.ts
index 9522d75..4834c35 100644
--- a/src/views/wls/productsales/data.ts
+++ b/src/views/wls/productsales/data.ts
@@ -570,6 +570,23 @@
): VxeTableGridOptions<MesWmProductSalesDetailApi.ProductSalesDetail>['columns'] {
return [
{
+ field: 'itemName',
+ title: '鐗╂枡',
+ minWidth: 140,
+ },
+ {
+ field: 'batchCode',
+ title: '鎵规鍙�',
+ minWidth: 130,
+ },
+ {
+ field: 'bagCodeId',
+ title: '鎵爜鏉ユ簮',
+ minWidth: 110,
+ formatter: ({ row }) =>
+ row.palletCodeId ? `鎵樼洏鐮� #${row.palletCodeId}` : row.bagCodeId ? `琚嬬爜 #${row.bagCodeId}` : '鎵嬪伐鎷h揣',
+ },
+ {
field: 'warehouseName',
title: '浠撳簱鍚嶇О',
minWidth: 100,
diff --git a/src/views/wls/productsales/modules/form.vue b/src/views/wls/productsales/modules/form.vue
index f6ba967..dcb1abc 100644
--- a/src/views/wls/productsales/modules/form.vue
+++ b/src/views/wls/productsales/modules/form.vue
@@ -16,6 +16,7 @@
createProductSales,
finishProductSales,
getProductSales,
+ scanProductSales,
shippingProductSales,
stockProductSales,
submitProductSales,
@@ -29,6 +30,10 @@
const emit = defineEmits(['success']);
const formType = ref<FormType>('create');
const formData = ref<MesWmProductSalesApi.ProductSales>();
+const scanContent = ref('');
+const scanQuantity = ref<number>();
+const scanLoading = ref(false);
+const scanRefreshKey = ref(0);
const originalSnapshot = ref(''); // 琛ㄥ崟鍘熷鏁版嵁蹇収锛岀敤浜庢彁浜ゆ椂璺宠繃鏈彉鏇寸殑淇濆瓨璇锋眰
const isEditable = computed(() =>
// 鏄惁涓虹紪杈戞ā寮忥紙鍙繚瀛橈級
@@ -96,6 +101,31 @@
emit('success');
} finally {
modalApi.unlock();
+ }
+}
+
+/** 鎵爜鎷h揣锛氭壂鐮佹灙閫氬父浠ュ洖杞︾粨鏉熻緭鍏� */
+async function handleScan() {
+ if (!formData.value?.id || !scanContent.value.trim() || scanLoading.value) {
+ return;
+ }
+ scanLoading.value = true;
+ try {
+ const result = await scanProductSales({
+ quantity: scanQuantity.value,
+ salesId: formData.value.id,
+ scanContent: scanContent.value.trim(),
+ });
+ message[result.alreadyScanned ? 'warning' : 'success'](
+ result.alreadyScanned
+ ? `璇ョ爜宸叉壂鎻忥紝鏄庣粏缂栧彿锛�${result.detailId ?? '-'}`
+ : `${result.message || '鎵爜鎴愬姛'}${result.scanType ? `锛�${result.scanType === 'PALLET' ? '鎵樼洏鐮�' : '琚嬬爜'}锛塦 : ''}`,
+ );
+ scanContent.value = '';
+ scanQuantity.value = undefined;
+ scanRefreshKey.value += 1;
+ } finally {
+ scanLoading.value = false;
}
}
@@ -231,9 +261,30 @@
<template v-if="formData?.id">
<Divider>鐗╂枡淇℃伅</Divider>
<div class="mx-4">
+ <div v-if="isStock" class="mb-3 flex items-center gap-2 px-4">
+ <a-input
+ v-model:value="scanContent"
+ :disabled="scanLoading"
+ autofocus
+ class="flex-1"
+ placeholder="璇锋壂鎻忚鐮佹垨鎵樼洏鐮侊紝鍥炶溅鎻愪氦"
+ @press-enter="handleScan"
+ />
+ <a-input-number
+ v-model:value="scanQuantity"
+ :disabled="scanLoading"
+ :min="0"
+ placeholder="鏁伴噺锛堝彲閫夛級"
+ style="width: 140px"
+ />
+ <Button :loading="scanLoading" type="default" @click="handleScan">
+ 鎵爜
+ </Button>
+ </div>
<LineList
:form-type="formType"
:notice-id="formData.noticeId"
+ :refresh-key="scanRefreshKey"
:sales-id="formData.id"
/>
</div>
diff --git a/src/views/wls/productsales/modules/line-list.vue b/src/views/wls/productsales/modules/line-list.vue
index 0cdc204..a1e8f07 100644
--- a/src/views/wls/productsales/modules/line-list.vue
+++ b/src/views/wls/productsales/modules/line-list.vue
@@ -5,7 +5,7 @@
import type { MesWmProductSalesDetailApi } from '#/api/mes/wm/productsales/detail';
import type { MesWmProductSalesLineApi } from '#/api/mes/wm/productsales/line';
-import { computed, reactive } from 'vue';
+import { computed, reactive, watch } from 'vue';
import { useVbenModal } from '@vben/common-ui';
@@ -28,6 +28,7 @@
formType: FormType;
noticeId?: number;
salesId: number;
+ refreshKey?: number;
}>();
const isEditable = computed(() =>
@@ -57,6 +58,15 @@
gridApi.query();
}
+watch(
+ () => props.refreshKey,
+ (refreshKey, previousRefreshKey) => {
+ if (refreshKey !== undefined && refreshKey !== previousRefreshKey) {
+ handleRefresh();
+ }
+ },
+);
+
/** 娣诲姞鐗╂枡 */
function handleCreate() {
lineFormModalApi
--
Gitblit v1.9.3