feat(ui): 江苏商贸品牌视觉与首页/登录页改版
- 新增「江苏商贸」盾形矢量 Logo(藏青+金)与校园夜景手绘插画
- 登录页改为学术典雅风格:插画铺满品牌区、文案移至左下角、
象牙白表单卡片、金色聚焦态与描边
- 首页改用系统真实数据:我的待办 / 课题申报 / 财务报销 / 课题指南,
新增待办列表与最近课题申报,快捷入口对齐实际菜单
- 移除首页 MES 生产看板模块(本系统无对应数据,原页面恒为 0)
- 清理 Vben 模板默认文案(pageTitle/pageDesc/loginSubtitle/codeSubtitle)
- 注册 JSSM 公司配置并接通 VITE_COMPANY
Co-Authored-By: Claude <noreply@anthropic.com>
| | |
| | | # åºç¨æ é¢ |
| | | VITE_APP_TITLE=å·¥ä¸AIæºé å¹³å° |
| | | VITE_APP_TITLE=æ±èåè´¸ |
| | | |
| | | # åºç¨å½å空é´ï¼ç¨äºç¼åãstoreçåè½çåç¼ï¼ç¡®ä¿é离 |
| | | VITE_APP_NAMESPACE=yudao-vben-antd |
| | |
| | | # 端å£å· |
| | | VITE_PORT=5666 |
| | | |
| | | # åºç¨æ é¢ |
| | | VITE_APP_TITLE=æ±èåè´¸ |
| | | |
| | | # å½åå
¬å¸æ è¯ï¼å³å®åºç¨åç§° / Logo / 徿 ï¼è§ company.config.tsï¼ |
| | | VITE_COMPANY=JSSM |
| | | |
| | | VITE_BASE=/ |
| | | |
| | | # 请æ±è·¯å¾ |
| | |
| | | favicon: '/logos/YZFXLogo.ico', |
| | | apiUrl: 'http://36.213.150.237:9006/admin-api', |
| | | }, |
| | | JSSM: { |
| | | name: 'æ±èåè´¸', |
| | | logo: '/logos/JSSM.svg', |
| | | favicon: '/logos/JSSM.svg', |
| | | apiUrl: '', |
| | | }, |
| | | }; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64" role="img" aria-label="æ±èåè´¸"> |
| | | <defs> |
| | | <!-- èé主è²ï¼å·¦ä¸è§ç¥äº®ï¼å³ä¸è§æ²ä¸å»ï¼å½¢æä½ç§¯æ --> |
| | | <linearGradient id="jssmNavy" x1="0" y1="0" x2="0.85" y2="1"> |
| | | <stop offset="0%" stop-color="#1E4571"/> |
| | | <stop offset="52%" stop-color="#123156"/> |
| | | <stop offset="100%" stop-color="#0A1D35"/> |
| | | </linearGradient> |
| | | <!-- éè²ï¼ä¸äº®ä¸æ·±ï¼æ¨¡æéå±å
æ³½ --> |
| | | <linearGradient id="jssmGold" x1="0" y1="0" x2="0" y2="1"> |
| | | <stop offset="0%" stop-color="#EBD07E"/> |
| | | <stop offset="45%" stop-color="#D4AF37"/> |
| | | <stop offset="100%" stop-color="#B8901F"/> |
| | | </linearGradient> |
| | | </defs> |
| | | |
| | | <!-- ç¾å½¢å¾½æ ä¸»ä½ --> |
| | | <path d="M8 14 A6 6 0 0 1 14 8 H50 A6 6 0 0 1 56 14 V32 C56 46 45.5 55.5 32 60 C18.5 55.5 8 46 8 32 Z" |
| | | fill="url(#jssmNavy)"/> |
| | | <!-- å¤åéè¾¹ï¼æ·±è²èæ¯ä¸é å®å¾åè½®å» --> |
| | | <path d="M8 14 A6 6 0 0 1 14 8 H50 A6 6 0 0 1 56 14 V32 C56 46 45.5 55.5 32 60 C18.5 55.5 8 46 8 32 Z" |
| | | fill="none" stroke="url(#jssmGold)" stroke-width="1.5" opacity="0.9"/> |
| | | <!-- å
åé线ï¼å¢å å¾½ç« ç屿¬¡ --> |
| | | <path d="M12.5 15 A3.5 3.5 0 0 1 16 11.5 H48 A3.5 3.5 0 0 1 51.5 15 V32 C51.5 44 42.5 52.5 32 56.5 C21.5 52.5 12.5 44 12.5 32 Z" |
| | | fill="none" stroke="url(#jssmGold)" stroke-width="1" opacity="0.55"/> |
| | | |
| | | <!-- é¡¶é¨å¦æ¯ä¹æ --> |
| | | <path d="M32 15.6 L33.9 19 L32 22.4 L30.1 19 Z" fill="url(#jssmGold)"/> |
| | | |
| | | <!-- å±å¼ç书æ¬ï¼å·¦é¡µ --> |
| | | <path d="M31.2 29.5 C27.4 26.1 22.2 24.6 17.8 25.5 V41.4 C22.2 40.5 27.4 42 31.2 45.4 Z" |
| | | fill="url(#jssmGold)"/> |
| | | <!-- å±å¼ç书æ¬ï¼å³é¡µï¼ç¥æï¼æå¼å·¦å³å±æ¬¡ï¼ --> |
| | | <path d="M32.8 29.5 C36.6 26.1 41.8 24.6 46.2 25.5 V41.4 C41.8 40.5 36.6 42 32.8 45.4 Z" |
| | | fill="url(#jssmGold)" opacity="0.78"/> |
| | | <!-- 书è --> |
| | | <path d="M32 29.5 V45.4" stroke="#0A1D35" stroke-width="1.6" stroke-linecap="round" opacity="0.55"/> |
| | | </svg> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 900" preserveAspectRatio="xMidYMid slice" role="img" aria-label="妿¯æ ¡å夿¯"> |
| | | <defs> |
| | | <!-- å¤ç©ºï¼ä¸æ·±ä¸æµ
çèé --> |
| | | <linearGradient id="sky" x1="0" y1="0" x2="0.25" y2="1"> |
| | | <stop offset="0%" stop-color="#08182C"/> |
| | | <stop offset="42%" stop-color="#0F2440"/> |
| | | <stop offset="72%" stop-color="#14375F"/> |
| | | <stop offset="100%" stop-color="#0B1C33"/> |
| | | </linearGradient> |
| | | |
| | | <!-- æå
å
æ --> |
| | | <radialGradient id="moonGlow" cx="0.5" cy="0.5" r="0.5"> |
| | | <stop offset="0%" stop-color="#EBD07E" stop-opacity="0.30"/> |
| | | <stop offset="45%" stop-color="#D4AF37" stop-opacity="0.10"/> |
| | | <stop offset="100%" stop-color="#D4AF37" stop-opacity="0"/> |
| | | </radialGradient> |
| | | |
| | | <!-- 建çåå
é¢ï¼å·¦äº®å³æ --> |
| | | <linearGradient id="facade" x1="0" y1="0" x2="1" y2="0.35"> |
| | | <stop offset="0%" stop-color="#1E4571"/> |
| | | <stop offset="55%" stop-color="#16375C"/> |
| | | <stop offset="100%" stop-color="#0E2743"/> |
| | | </linearGradient> |
| | | |
| | | <!-- 穹顶 --> |
| | | <linearGradient id="dome" x1="0.15" y1="0" x2="0.85" y2="1"> |
| | | <stop offset="0%" stop-color="#2A5B8C"/> |
| | | <stop offset="60%" stop-color="#17395E"/> |
| | | <stop offset="100%" stop-color="#0D2340"/> |
| | | </linearGradient> |
| | | |
| | | <!-- éè²çªå
--> |
| | | <linearGradient id="winLight" x1="0" y1="0" x2="0" y2="1"> |
| | | <stop offset="0%" stop-color="#EBD07E"/> |
| | | <stop offset="100%" stop-color="#C9A227"/> |
| | | </linearGradient> |
| | | |
| | | <!-- æ°´é¢ --> |
| | | <linearGradient id="pool" x1="0" y1="0" x2="0" y2="1"> |
| | | <stop offset="0%" stop-color="#0C2036"/> |
| | | <stop offset="100%" stop-color="#061320"/> |
| | | </linearGradient> |
| | | |
| | | <!-- æ°´é¢éè²åå½± --> |
| | | <linearGradient id="reflect" x1="0" y1="0" x2="0" y2="1"> |
| | | <stop offset="0%" stop-color="#D4AF37" stop-opacity="0.22"/> |
| | | <stop offset="100%" stop-color="#D4AF37" stop-opacity="0"/> |
| | | </linearGradient> |
| | | |
| | | <!-- åºé¨åæï¼ç»æåçåºå¯è¯»åºå --> |
| | | <linearGradient id="vignette" x1="0" y1="0" x2="0" y2="1"> |
| | | <stop offset="0%" stop-color="#08182C" stop-opacity="0"/> |
| | | <stop offset="62%" stop-color="#08182C" stop-opacity="0.35"/> |
| | | <stop offset="100%" stop-color="#08182C" stop-opacity="0.85"/> |
| | | </linearGradient> |
| | | </defs> |
| | | |
| | | <!-- ===== å¤ç©º ===== --> |
| | | <rect width="900" height="900" fill="url(#sky)"/> |
| | | |
| | | <!-- æäº®ä¸å
æ --> |
| | | <circle cx="686" cy="176" r="210" fill="url(#moonGlow)"/> |
| | | <circle cx="686" cy="176" r="46" fill="#EBD07E" opacity="0.92"/> |
| | | <circle cx="686" cy="176" r="46" fill="none" stroke="#F5E7C0" stroke-width="1" opacity="0.5"/> |
| | | <!-- æé¢æé¨ï¼è®©å®ä¸æ¯çº¯è²å --> |
| | | <circle cx="672" cy="164" r="38" fill="#D4AF37" opacity="0.18"/> |
| | | <circle cx="700" cy="188" r="14" fill="#08182C" opacity="0.12"/> |
| | | <circle cx="672" cy="196" r="9" fill="#08182C" opacity="0.10"/> |
| | | |
| | | <!-- æç¹ --> |
| | | <g fill="#EBD07E"> |
| | | <circle cx="120" cy="96" r="1.8" opacity="0.55"/> |
| | | <circle cx="238" cy="152" r="1.3" opacity="0.42"/> |
| | | <circle cx="352" cy="82" r="2.1" opacity="0.6"/> |
| | | <circle cx="470" cy="134" r="1.4" opacity="0.4"/> |
| | | <circle cx="560" cy="72" r="1.7" opacity="0.5"/> |
| | | <circle cx="800" cy="118" r="1.5" opacity="0.45"/> |
| | | <circle cx="176" cy="228" r="1.2" opacity="0.32"/> |
| | | <circle cx="420" cy="206" r="1.6" opacity="0.44"/> |
| | | <circle cx="742" cy="268" r="1.3" opacity="0.34"/> |
| | | <circle cx="86" cy="330" r="1.5" opacity="0.3"/> |
| | | <circle cx="598" cy="184" r="1.1" opacity="0.3"/> |
| | | <circle cx="300" cy="286" r="1.2" opacity="0.28"/> |
| | | </g> |
| | | |
| | | <!-- ===== è¿å±± ===== --> |
| | | <path d="M0 556 L118 484 L214 534 L322 452 L430 528 L548 470 L662 540 L784 486 L900 552 L900 900 L0 900 Z" |
| | | fill="#0B1E36" opacity="0.75"/> |
| | | |
| | | <!-- ===== è¿æ¯æ¥¼ç¾¤ ===== --> |
| | | <g fill="#0D2440" opacity="0.85"> |
| | | <rect x="42" y="520" width="46" height="72"/> |
| | | <rect x="98" y="546" width="34" height="46"/> |
| | | <rect x="748" y="530" width="42" height="62"/> |
| | | <rect x="800" y="556" width="30" height="36"/> |
| | | </g> |
| | | |
| | | <!-- ===== 主楼ï¼å°é¶ ===== --> |
| | | <g> |
| | | <rect x="238" y="742" width="424" height="13" fill="#132E4E"/> |
| | | <rect x="256" y="729" width="388" height="13" fill="#17395E"/> |
| | | <rect x="274" y="716" width="352" height="13" fill="#1B4168"/> |
| | | </g> |
| | | |
| | | <!-- ===== ä¸»æ¥¼ï¼æ±å» ===== --> |
| | | <rect x="286" y="560" width="328" height="156" fill="url(#facade)"/> |
| | | |
| | | <!-- ===== å±é¡¶å±±è±ï¼ä¸è§æ¥£ï¼ ===== --> |
| | | <path d="M286 560 L450 470 L614 560 Z" fill="#17395E"/> |
| | | <path d="M286 560 L450 470 L614 560 Z" fill="none" stroke="#D4AF37" stroke-width="2" opacity="0.55"/> |
| | | <rect x="272" y="556" width="356" height="12" fill="#1B4168"/> |
| | | |
| | | <!-- å±±è±å
çåå¾½ --> |
| | | <circle cx="450" cy="528" r="24" fill="#0E2743" stroke="#D4AF37" stroke-width="1.6" opacity="0.9"/> |
| | | <circle cx="450" cy="528" r="13" fill="none" stroke="#D4AF37" stroke-width="1.2" opacity="0.65"/> |
| | | <circle cx="450" cy="528" r="4.5" fill="#D4AF37" opacity="0.85"/> |
| | | |
| | | <!-- ===== 穹顶 ===== --> |
| | | <path d="M450 470 A62 62 0 0 1 512 470 Z" fill="url(#dome)" transform="translate(-62,0)"/> |
| | | <path d="M388 470 A62 62 0 0 1 512 470 Z" fill="url(#dome)"/> |
| | | <path d="M388 470 A62 62 0 0 1 512 470" fill="none" stroke="#D4AF37" stroke-width="1.6" opacity="0.6"/> |
| | | <rect x="444" y="382" width="12" height="28" fill="#17395E"/> |
| | | <circle cx="450" cy="376" r="8" fill="#D4AF37" opacity="0.9"/> |
| | | <!-- å¡å°å
ç¹ --> |
| | | <circle cx="450" cy="376" r="18" fill="#EBD07E" opacity="0.16"/> |
| | | |
| | | <!-- ===== å
æ ¹ç«æ± ===== --> |
| | | <g fill="#1B4168"> |
| | | <rect x="302" y="578" width="26" height="138"/> |
| | | <rect x="356" y="578" width="26" height="138"/> |
| | | <rect x="410" y="578" width="26" height="138"/> |
| | | <rect x="464" y="578" width="26" height="138"/> |
| | | <rect x="518" y="578" width="26" height="138"/> |
| | | <rect x="572" y="578" width="26" height="138"/> |
| | | </g> |
| | | <!-- æ±å¤´ä¸æ±ç¡ --> |
| | | <g fill="#24507C"> |
| | | <rect x="298" y="572" width="34" height="10"/> |
| | | <rect x="352" y="572" width="34" height="10"/> |
| | | <rect x="406" y="572" width="34" height="10"/> |
| | | <rect x="460" y="572" width="34" height="10"/> |
| | | <rect x="514" y="572" width="34" height="10"/> |
| | | <rect x="568" y="572" width="34" height="10"/> |
| | | <rect x="300" y="708" width="30" height="10"/> |
| | | <rect x="354" y="708" width="30" height="10"/> |
| | | <rect x="408" y="708" width="30" height="10"/> |
| | | <rect x="462" y="708" width="30" height="10"/> |
| | | <rect x="516" y="708" width="30" height="10"/> |
| | | <rect x="570" y="708" width="30" height="10"/> |
| | | </g> |
| | | |
| | | <!-- ===== æ±é´çªå
===== --> |
| | | <g fill="url(#winLight)"> |
| | | <rect x="332" y="612" width="20" height="34" opacity="0.82"/> |
| | | <rect x="386" y="612" width="20" height="34" opacity="0.62"/> |
| | | <rect x="440" y="612" width="20" height="34" opacity="0.9"/> |
| | | <rect x="494" y="612" width="20" height="34" opacity="0.58"/> |
| | | <rect x="548" y="612" width="20" height="34" opacity="0.78"/> |
| | | </g> |
| | | <g fill="url(#winLight)"> |
| | | <rect x="332" y="662" width="20" height="30" opacity="0.55"/> |
| | | <rect x="386" y="662" width="20" height="30" opacity="0.72"/> |
| | | <rect x="440" y="662" width="20" height="30" opacity="0.6"/> |
| | | <rect x="494" y="662" width="20" height="30" opacity="0.8"/> |
| | | <rect x="548" y="662" width="20" height="30" opacity="0.5"/> |
| | | </g> |
| | | |
| | | <!-- ä¸å¤®å¤§é¨ --> |
| | | <path d="M432 716 L432 676 A18 18 0 0 1 468 676 L468 716 Z" fill="#0E2743"/> |
| | | <path d="M432 716 L432 676 A18 18 0 0 1 468 676 L468 716" fill="none" stroke="#D4AF37" stroke-width="1.4" opacity="0.6"/> |
| | | <rect x="448" y="676" width="1.6" height="40" fill="#D4AF37" opacity="0.45"/> |
| | | |
| | | <!-- é¨åç两çå£ç¯ --> |
| | | <circle cx="414" cy="700" r="5" fill="#EBD07E" opacity="0.9"/> |
| | | <circle cx="414" cy="700" r="14" fill="#EBD07E" opacity="0.16"/> |
| | | <circle cx="486" cy="700" r="5" fill="#EBD07E" opacity="0.9"/> |
| | | <circle cx="486" cy="700" r="14" fill="#EBD07E" opacity="0.16"/> |
| | | |
| | | <!-- ===== ä¸¤ä¾§æ æ¨ ===== --> |
| | | <g fill="#0A1B2F"> |
| | | <!-- å·¦æ --> |
| | | <rect x="146" y="700" width="10" height="60"/> |
| | | <ellipse cx="151" cy="666" rx="52" ry="42"/> |
| | | <ellipse cx="120" cy="690" rx="34" ry="28"/> |
| | | <ellipse cx="182" cy="688" rx="30" ry="26"/> |
| | | <!-- 峿 --> |
| | | <rect x="742" y="704" width="10" height="56"/> |
| | | <ellipse cx="747" cy="672" rx="48" ry="40"/> |
| | | <ellipse cx="718" cy="694" rx="32" ry="26"/> |
| | | <ellipse cx="776" cy="692" rx="28" ry="24"/> |
| | | </g> |
| | | <!-- æ å åå
è¾¹ --> |
| | | <g fill="none" stroke="#D4AF37" stroke-width="1.1" opacity="0.18"> |
| | | <ellipse cx="151" cy="666" rx="52" ry="42"/> |
| | | <ellipse cx="747" cy="672" rx="48" ry="40"/> |
| | | </g> |
| | | |
| | | <!-- ===== åæ¯æ°´é¢ ===== --> |
| | | <rect x="0" y="755" width="900" height="145" fill="url(#pool)"/> |
| | | <rect x="0" y="755" width="900" height="1.6" fill="#D4AF37" opacity="0.22"/> |
| | | |
| | | <!-- 建ç卿°´é¢çåå½± --> |
| | | <g opacity="0.5"> |
| | | <rect x="300" y="758" width="300" height="66" fill="url(#facade)" opacity="0.35"/> |
| | | <rect x="330" y="758" width="18" height="52" fill="#D4AF37" opacity="0.16"/> |
| | | <rect x="384" y="758" width="18" height="52" fill="#D4AF37" opacity="0.12"/> |
| | | <rect x="438" y="758" width="18" height="52" fill="#D4AF37" opacity="0.2"/> |
| | | <rect x="492" y="758" width="18" height="52" fill="#D4AF37" opacity="0.12"/> |
| | | <rect x="546" y="758" width="18" height="52" fill="#D4AF37" opacity="0.16"/> |
| | | </g> |
| | | <rect x="0" y="755" width="900" height="120" fill="url(#reflect)"/> |
| | | |
| | | <!-- æ°´é¢æ³¢çº¹ --> |
| | | <g stroke="#EBD07E" stroke-width="1.2" opacity="0.16" stroke-linecap="round"> |
| | | <line x1="286" y1="792" x2="356" y2="792"/> |
| | | <line x1="404" y1="806" x2="490" y2="806"/> |
| | | <line x1="520" y1="786" x2="596" y2="786"/> |
| | | <line x1="330" y1="826" x2="420" y2="826"/> |
| | | <line x1="470" y1="842" x2="556" y2="842"/> |
| | | <line x1="180" y1="812" x2="238" y2="812"/> |
| | | <line x1="660" y1="820" x2="726" y2="820"/> |
| | | </g> |
| | | |
| | | <!-- ===== åºé¨åæ ===== --> |
| | | <rect width="900" height="900" fill="url(#vignette)"/> |
| | | </svg> |
| | |
| | | |
| | | import { Copyright } from '../basic/copyright'; |
| | | import AuthenticationFormView from './form.vue'; |
| | | import SloganIcon from './icons/slogan.vue'; |
| | | import Toolbar from './toolbar.vue'; |
| | | |
| | | interface Props { |
| | |
| | | </div> |
| | | </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> |
| | | <!-- æç»æ ¡åæç»ï¼å¤é¨ä¼ å
¥ sloganImage æ¶ä¼å
ç¨å¤é¨å¾ --> |
| | | <img |
| | | v-if="sloganImage" |
| | | alt="" |
| | | class="campus-illustration" |
| | | :src="sloganImage" |
| | | /> |
| | | <img |
| | | v-else |
| | | alt="" |
| | | class="campus-illustration" |
| | | src="/static/imgs/jssm-campus.svg" |
| | | /> |
| | | <!-- åæå å±ï¼åºé¨å éï¼ä¿è¯æåå¯è¯» --> |
| | | <div class="campus-overlay"></div> |
| | | <div class="academic-bg-pattern"></div> |
| | | |
| | | <div |
| | | :key="authPanelLeft ? 'left' : authPanelRight ? 'right' : 'center'" |
| | | class="mr-20 flex-col-center h-full" |
| | | class="campus-content" |
| | | :class="{ |
| | | 'enter-x': authPanelLeft, |
| | | '-enter-x': authPanelRight, |
| | | }" |
| | | > |
| | | <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> |
| | | <div class="mt-2 dark:text-muted-foreground"> |
| | | {{ pageDescription }} |
| | | </div> |
| | | <div class="campus-rule"></div> |
| | | <div class="academic-brand-title">{{ pageTitle }}</div> |
| | | <div class="academic-brand-desc">{{ pageDescription }}</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | <!-- ä¸å¿è®¤è¯é¢æ¿ --> |
| | | <div v-if="authPanelCenter" class="relative flex-center w-full"> |
| | | <div class="login-background absolute top-0 left-0 size-full"></div> |
| | | <!-- 妿¯é£è£
é¥°ç½æ ¼èæ¯ --> |
| | | <div class="academic-bg-pattern"></div> |
| | | <AuthenticationFormView |
| | | class="w-full rounded-3xl pb-20 shadow-float shadow-primary/5 md:w-2/3 md:bg-background lg:w-1/2 xl:w-[36%]" |
| | | data-side="bottom" |
| | |
| | | </template> |
| | | |
| | | <style scoped> |
| | | .login-background { |
| | | background: linear-gradient( |
| | | 154deg, |
| | | #07070915 30%, |
| | | hsl(var(--primary) / 30%) 48%, |
| | | #07070915 64% |
| | | /* æ ¡åæç»éºæ»¡å·¦ä¾§åçåºï¼object-position ç¥åä¸ï¼ä¿ä½å»ºçä¸»ä½ */ |
| | | .campus-illustration { |
| | | position: absolute; |
| | | inset: 0; |
| | | width: 100%; |
| | | height: 100%; |
| | | object-fit: cover; |
| | | object-position: center 34%; |
| | | } |
| | | |
| | | /* å å±ï¼åºé¨åæç»æåï¼å·¦ä¾§ç¥æä»¥çªåºç½è²æ é¢ */ |
| | | .campus-overlay { |
| | | position: absolute; |
| | | inset: 0; |
| | | background: |
| | | linear-gradient( |
| | | 180deg, |
| | | rgba(8, 24, 44, 0.12) 0%, |
| | | rgba(8, 24, 44, 0.22) 42%, |
| | | rgba(8, 24, 44, 0.86) 100% |
| | | ), |
| | | linear-gradient( |
| | | 90deg, |
| | | rgba(8, 24, 44, 0.4) 0%, |
| | | rgba(8, 24, 44, 0) 58% |
| | | ); |
| | | filter: blur(100px); |
| | | } |
| | | |
| | | /* è£
é¥°ç½æ ¼ï¼åå°å ä¹ä¸å¯è§çéè²ç»çº¿ */ |
| | | .academic-bg-pattern { |
| | | position: absolute; |
| | | inset: 0; |
| | | background-image: |
| | | linear-gradient(rgba(212, 175, 55, 0.03) 1px, transparent 1px), |
| | | linear-gradient(90deg, rgba(212, 175, 55, 0.03) 1px, transparent 1px); |
| | | background-size: 72px 72px; |
| | | pointer-events: none; |
| | | } |
| | | |
| | | /* åçææ¡ï¼å®ä½å°å·¦ä¸è§ */ |
| | | .campus-content { |
| | | position: absolute; |
| | | bottom: 68px; |
| | | left: 56px; |
| | | z-index: 10; |
| | | max-width: 64%; |
| | | } |
| | | |
| | | /* æ é¢ä¸æ¹çéè²ç横线 */ |
| | | .campus-rule { |
| | | width: 46px; |
| | | height: 2px; |
| | | margin-bottom: 20px; |
| | | border-radius: 2px; |
| | | background: linear-gradient(90deg, #e3c567, #b8901f); |
| | | } |
| | | |
| | | /* åçåºæ é¢ï¼ç±³ç½ï¼é´å½±æ¶æä»¥ä¿æåºé */ |
| | | .academic-brand-title { |
| | | color: #f5f0e4 !important; |
| | | font-size: 26px; |
| | | font-weight: 600; |
| | | letter-spacing: 5px; |
| | | text-shadow: 0 1px 10px rgba(0, 0, 0, 0.4); |
| | | } |
| | | |
| | | .academic-brand-desc { |
| | | margin-top: 12px; |
| | | color: rgba(212, 175, 55, 0.85) !important; |
| | | font-size: 13.5px; |
| | | letter-spacing: 3px; |
| | | } |
| | | |
| | | .dark { |
| | | .login-background { |
| | | background: linear-gradient( |
| | | 154deg, |
| | | #07070915 30%, |
| | | hsl(var(--primary) / 20%) 48%, |
| | | #07070915 64% |
| | | ); |
| | | filter: blur(100px); |
| | | .academic-bg-pattern { |
| | | background-image: |
| | | linear-gradient(rgba(212, 175, 55, 0.045) 1px, transparent 1px), |
| | | linear-gradient(90deg, rgba(212, 175, 55, 0.045) 1px, transparent 1px); |
| | | background-size: 72px 72px; |
| | | } |
| | | } |
| | | </style> |
| | |
| | | { |
| | | "welcomeBack": "Welcome Back", |
| | | "pageTitle": "Plug-and-play Admin system", |
| | | "pageDesc": "Efficient, versatile frontend template", |
| | | "pageTitle": "Unified Management Platform", |
| | | "pageDesc": "Collaboration · Data-driven · Standardized Process", |
| | | "loginSuccess": "Login Successful", |
| | | "loginSuccessDesc": "Welcome Back", |
| | | "loginSubtitle": "Enter your account details to manage your projects", |
| | | "loginSubtitle": "Enter your account details to sign in", |
| | | "selectAccount": "Quick Select Account", |
| | | "username": "Username", |
| | | "password": "Password", |
| | |
| | | "githubLogin": "Github Login", |
| | | "googleLogin": "Google Login", |
| | | "dingdingLogin": "Dingding Login", |
| | | "codeSubtitle": "Enter your phone number to start managing your project", |
| | | "codeSubtitle": "Enter your mobile number to sign in", |
| | | "code": "Security code", |
| | | "codeTip": "Security code required {0} characters", |
| | | "mobile": "Mobile", |
| | |
| | | { |
| | | "welcomeBack": "欢è¿åæ¥", |
| | | "pageTitle": "å¼ç®±å³ç¨ç大åä¸åå°ç®¡çç³»ç»", |
| | | "pageDesc": "å·¥ç¨åã髿§è½ãè·¨ç»ä»¶åºçå端模ç", |
| | | "pageTitle": "ç»ä¸ç®¡çå¹³å°", |
| | | "pageDesc": "åååå
¬ · æ°æ®å³ç · æµç¨è§è", |
| | | "loginSuccess": "ç»å½æå", |
| | | "loginSuccessDesc": "欢è¿åæ¥", |
| | | "loginSubtitle": "请è¾å
¥æ¨çè´¦æ·ä¿¡æ¯ä»¥å¼å§ç®¡çæ¨ç项ç®", |
| | | "loginSubtitle": "请è¾å
¥è´¦å·ä¿¡æ¯ç»å½ç»ä¸ç®¡çå¹³å°", |
| | | "selectAccount": "å¿«ééæ©è´¦å·", |
| | | "username": "è´¦å·", |
| | | "password": "å¯ç ", |
| | |
| | | "githubLogin": "Githubç»å½", |
| | | "googleLogin": "Googleç»å½", |
| | | "dingdingLogin": "ééç»å½", |
| | | "codeSubtitle": "请è¾å
¥æ¨çææºå·ç 以å¼å§ç®¡çæ¨ç项ç®", |
| | | "codeSubtitle": "请è¾å
¥ææºå·ç ç»å½ç»ä¸ç®¡çå¹³å°", |
| | | "code": "éªè¯ç ", |
| | | "codeTip": "请è¾å
¥{0}ä½éªè¯ç ", |
| | | "mobile": "ææºå·ç ", |
| | |
| | | |
| | | defineOptions({ name: 'Login' }); |
| | | |
| | | // vite.config.ts 注å
¥çå
¬å¸ Logo è·¯å¾ |
| | | declare const __APP_LOGO__: string; |
| | | |
| | | const authStore = useAuthStore(); |
| | | const accessStore = useAccessStore(); |
| | | const tenantEnable = isTenantEnable(); |
| | |
| | | const verifyRef = ref(); |
| | | |
| | | const captchaType = 'blockPuzzle'; // éªè¯ç ç±»åï¼'blockPuzzle' | 'clickWord' |
| | | |
| | | /** åºç¨æ é¢ï¼ç»å½é¡µåçæåï¼ */ |
| | | const appTitle = import.meta.env.VITE_APP_TITLE || 'æ±èåè´¸'; |
| | | |
| | | /** åçå¾½æ ï¼å¤ç¨ vite 注å
¥çå
¬å¸ Logoï¼ç¼ºçåè½å°å
ç½®å¾æ */ |
| | | const brandLogo = __APP_LOGO__ || '/logos/JSSM.svg'; |
| | | |
| | | /** è·åç§æ·å表 */ |
| | | const tenantList = ref<AuthApi.TenantResult[]>([]); // ç§æ·å表 |
| | |
| | | |
| | | const formSchema = computed((): VbenFormSchema[] => { |
| | | return [ |
| | | // { |
| | | // component: 'VbenSelect', |
| | | // componentProps: { |
| | | // options: tenantList.value.map((item) => ({ |
| | | // label: item.name, |
| | | // value: item.id.toString(), |
| | | // })), |
| | | // placeholder: $t('authentication.tenantTip'), |
| | | // }, |
| | | // fieldName: 'tenantId', |
| | | // label: $t('authentication.tenant'), |
| | | // dependencies: { |
| | | // triggerFields: ['tenantId'], |
| | | // if: tenantEnable, |
| | | // trigger(values) { |
| | | // accessStore.setTenantId(values.tenantId ? Number(values.tenantId) : null); |
| | | // }, |
| | | // }, |
| | | // }, |
| | | { |
| | | component: 'VbenInput', |
| | | componentProps: { |
| | |
| | | </script> |
| | | |
| | | <template> |
| | | <div> |
| | | <div class="academic-login-wrapper"> |
| | | <!-- é¡¶é¨åçæ è¯ï¼å¾½æ + å¹³å°å + éè²åé线 --> |
| | | <div class="academic-login-header"> |
| | | <img :alt="appTitle" :src="brandLogo" class="academic-brand-logo" /> |
| | | <h2 class="academic-brand-name">{{ appTitle }}</h2> |
| | | <div class="academic-decoration-line"></div> |
| | | </div> |
| | | |
| | | <AuthenticationLogin |
| | | ref="loginRef" |
| | | :form-schema="formSchema" |
| | |
| | | :show-register="false" |
| | | :show-forget-password="false" |
| | | :show-doc-link="false" |
| | | class="academic-login-form" |
| | | @submit="handleLogin" |
| | | > |
| | | <!-- èªå®ä¹æ é¢ï¼è¦ç欢è¿è¯ --> |
| | | <template #title> |
| | | <div class="academic-title-area"> |
| | | <h3 class="academic-title">è´¦å·ç»å½</h3> |
| | | <p class="academic-subtitle">{{ $t('authentication.loginSubtitle') }}</p> |
| | | </div> |
| | | </template> |
| | | |
| | | <!-- ç¬¬ä¸æ¹ç»å½ --> |
| | | <template #third-party-login> |
| | | <div class="mt-4"> |
| | | <div class="mb-4 flex items-center justify-between"> |
| | | <span class="w-[35%] border-b border-input dark:border-gray-600"></span> |
| | | <span class="text-center text-xs text-muted-foreground"> |
| | | å
¶ä»ç»å½æ¹å¼ |
| | | </span> |
| | | <span class="w-[35%] border-b border-input dark:border-gray-600"></span> |
| | | <div class="academic-third-party"> |
| | | <div class="academic-divider"> |
| | | <span class="academic-divider-line"></span> |
| | | <span class="academic-divider-text">å
¶ä»ç»å½æ¹å¼</span> |
| | | <span class="academic-divider-line"></span> |
| | | </div> |
| | | <VbenButton |
| | | class="w-full" |
| | | class="academic-wxcp-btn" |
| | | variant="outline" |
| | | :loading="wxcpLoading" |
| | | @click="handleWxcpLogin" |
| | | > |
| | | <SvgWeChatIcon class="mr-2 size-5" /> |
| | | <SvgWeChatIcon class="mr-2 size-4" /> |
| | | ä¼ä¸å¾®ä¿¡ç»å½ |
| | | </VbenButton> |
| | | </div> |
| | | </template> |
| | | </AuthenticationLogin> |
| | | |
| | | <!-- éªè¯ç ï¼æ»åæ¼å¾ï¼ï¼captchaEnable æ¶å¯ç¨ --> |
| | | <Verification |
| | | ref="verifyRef" |
| | | v-if="captchaEnable" |
| | |
| | | /> |
| | | </div> |
| | | </template> |
| | | |
| | | <style scoped> |
| | | /* ========== 妿¯ç§ç 飿 ¼ç»å½é¡µæ ·å¼ ========== */ |
| | | .academic-login-wrapper { |
| | | @apply relative flex flex-col w-full; |
| | | min-height: 460px; |
| | | } |
| | | |
| | | /* ---- é¡¶é¨åçæ è¯ ---- */ |
| | | .academic-login-header { |
| | | @apply flex flex-col items-center mb-8; |
| | | } |
| | | |
| | | .academic-brand-logo { |
| | | width: 52px; |
| | | height: 52px; |
| | | margin-bottom: 16px; |
| | | filter: drop-shadow(0 3px 10px rgba(15, 36, 64, 0.14)); |
| | | } |
| | | |
| | | .academic-brand-name { |
| | | font-size: 19px; |
| | | font-weight: 600; |
| | | letter-spacing: 5px; |
| | | /* æ«åå³ä¾§ä¼å¤åºä¸ä»½åè·ï¼ç¨çéç¼©è¿æµæ¶ï¼è§è§æçæ£å±
ä¸ */ |
| | | text-indent: 5px; |
| | | color: #0f2440; |
| | | margin: 0 0 14px; |
| | | } |
| | | |
| | | .academic-decoration-line { |
| | | position: relative; |
| | | width: 58px; |
| | | height: 2px; |
| | | background: linear-gradient( |
| | | 90deg, |
| | | transparent, |
| | | #d4af37 32%, |
| | | #d4af37 68%, |
| | | transparent |
| | | ); |
| | | border-radius: 2px; |
| | | } |
| | | |
| | | /* é线ä¸å¤®çè±å½¢ç¹ç¼ */ |
| | | .academic-decoration-line::after { |
| | | content: ''; |
| | | position: absolute; |
| | | top: 50%; |
| | | left: 50%; |
| | | width: 6px; |
| | | height: 6px; |
| | | background: #d4af37; |
| | | border-radius: 1px; |
| | | transform: translate(-50%, -50%) rotate(45deg); |
| | | } |
| | | |
| | | /* ---- èªå®ä¹æ é¢ ---- */ |
| | | .academic-title-area { |
| | | @apply text-left mb-6; |
| | | } |
| | | |
| | | .academic-title { |
| | | font-size: 26px; |
| | | font-weight: 600; |
| | | color: #0f2440; |
| | | letter-spacing: 2px; |
| | | margin: 0 0 8px 0; |
| | | line-height: 1.35; |
| | | } |
| | | |
| | | .academic-subtitle { |
| | | font-size: 13.5px; |
| | | color: #8a94a6; |
| | | margin: 0; |
| | | letter-spacing: 0.3px; |
| | | } |
| | | |
| | | /* ---- è¦çé»è®¤æ é¢ ---- */ |
| | | :deep(.academic-login-form .mb-7) { |
| | | display: none; |
| | | } |
| | | |
| | | /* ---- è¾å
¥æ¡ï¼æç½åº + éè²èç¦ ---- */ |
| | | :deep(.academic-login-form .vben-input) { |
| | | border-radius: 8px; |
| | | border-color: #e6e2d8; |
| | | background: #fdfcfa; |
| | | transition: all 0.25s ease; |
| | | height: 48px; |
| | | } |
| | | |
| | | :deep(.academic-login-form .vben-input:hover) { |
| | | border-color: #c9b77a; |
| | | } |
| | | |
| | | :deep(.academic-login-form .vben-input:focus-within) { |
| | | border-color: #d4af37; |
| | | background: #fff; |
| | | box-shadow: 0 0 0 3px rgba(212, 175, 55, 0.13); |
| | | } |
| | | |
| | | /* ---- ç»å½æé®ï¼è鿏å + éè²æè¾¹ä¸æå ---- */ |
| | | :deep(.academic-login-form .w-full > button), |
| | | :deep(.academic-login-form button[aria-label="login"]) { |
| | | height: 48px; |
| | | border-radius: 8px; |
| | | background: linear-gradient(135deg, #1e4571, #0f2440) !important; |
| | | border: 1px solid rgba(212, 175, 55, 0.45) !important; |
| | | font-size: 15px; |
| | | font-weight: 600; |
| | | letter-spacing: 6px; |
| | | text-indent: 6px; |
| | | box-shadow: 0 6px 18px rgba(15, 36, 64, 0.22); |
| | | transition: all 0.3s ease; |
| | | color: #f5e7c0 !important; |
| | | } |
| | | |
| | | /* æ¬ååªå æ·±æè¾¹ä¸æå½±ï¼ä¸åä½ç§»ï¼ä¿æå
å¶ */ |
| | | :deep(.academic-login-form .w-full > button:hover), |
| | | :deep(.academic-login-form button[aria-label="login"]:hover) { |
| | | box-shadow: |
| | | 0 8px 22px rgba(15, 36, 64, 0.26), |
| | | 0 0 0 1px rgba(212, 175, 55, 0.6); |
| | | } |
| | | |
| | | :deep(.academic-login-form .w-full > button:active), |
| | | :deep(.academic-login-form button[aria-label="login"]:active) { |
| | | box-shadow: 0 3px 12px rgba(15, 36, 64, 0.24); |
| | | } |
| | | |
| | | /* ---- è®°ä½è´¦å· / å¿è®°å¯ç ---- */ |
| | | :deep(.academic-login-form .vben-link) { |
| | | color: #b8901f; |
| | | font-weight: 500; |
| | | transition: color 0.2s; |
| | | } |
| | | |
| | | :deep(.academic-login-form .vben-link:hover) { |
| | | color: #8a6a12; |
| | | } |
| | | |
| | | /* ---- ç¬¬ä¸æ¹ç»å½ ---- */ |
| | | .academic-third-party { |
| | | @apply mt-5; |
| | | } |
| | | |
| | | .academic-divider { |
| | | @apply flex items-center gap-3 mb-4; |
| | | } |
| | | |
| | | .academic-divider-line { |
| | | flex: 1; |
| | | height: 1px; |
| | | background: linear-gradient(90deg, transparent, #e2e8f0, transparent); |
| | | } |
| | | |
| | | .academic-divider-text { |
| | | @apply text-xs; |
| | | color: #94a3b8; |
| | | white-space: nowrap; |
| | | letter-spacing: 0.5px; |
| | | } |
| | | |
| | | .academic-wxcp-btn { |
| | | @apply w-full rounded-lg; |
| | | height: 44px; |
| | | border-color: #e6e2d8; |
| | | color: #44546a; |
| | | font-weight: 500; |
| | | transition: all 0.3s; |
| | | } |
| | | |
| | | .academic-wxcp-btn:hover { |
| | | border-color: #d4af37; |
| | | color: #0f2440; |
| | | background: rgba(212, 175, 55, 0.06); |
| | | } |
| | | |
| | | /* ---- æè²éé
---- */ |
| | | :root.dark .academic-title { |
| | | color: #f0ead8; |
| | | } |
| | | |
| | | :root.dark .academic-brand-name { |
| | | color: #f0ead8; |
| | | } |
| | | |
| | | :root.dark .academic-subtitle { |
| | | color: #8a94a6; |
| | | } |
| | | |
| | | :root.dark .academic-decoration-line { |
| | | background: linear-gradient(90deg, #8bb4f0, #4a7ad4); |
| | | } |
| | | |
| | | :root.dark .academic-footer-text { |
| | | color: #64748b; |
| | | } |
| | | </style> |
| | |
| | | <script lang="ts" setup> |
| | | import { computed, onMounted, ref } from 'vue'; |
| | | import { computed } from 'vue'; |
| | | import { useRouter } from 'vue-router'; |
| | | |
| | | import { Page } from '@vben/common-ui'; |
| | | |
| | | import { Card, Col, Row } from 'ant-design-vue'; |
| | | import { Col, Row } from 'ant-design-vue'; |
| | | |
| | | import { getHomeSummary } from '#/api/mes/home'; |
| | | |
| | | import { defaultSummary } from './data'; |
| | | import AiInsights from './modules/ai-insights.vue'; |
| | | import AlertPanel from './modules/alert-panel.vue'; |
| | | import BizCards from './modules/biz-cards.vue'; |
| | | import KpiCards from './modules/kpi-cards.vue'; |
| | | import ProductionTrend from './modules/production-trend.vue'; |
| | | import QuickActions from './modules/quick-actions.vue'; |
| | | import RecentApplications from './modules/recent-applications.vue'; |
| | | import TodoPanel from './modules/todo-panel.vue'; |
| | | |
| | | defineOptions({ name: 'DashboardAnalytics' }); |
| | | |
| | | // vite.config.ts 注å
¥çå
¬å¸ Logo è·¯å¾ |
| | | declare const __APP_LOGO__: string; |
| | | const brandLogo = __APP_LOGO__ || '/logos/JSSM.svg'; |
| | | |
| | | const router = useRouter(); |
| | | const summary = ref(defaultSummary); |
| | | |
| | | const greeting = computed(() => { |
| | | const hour = new Date().getHours(); |
| | |
| | | function handleNavigate(name: string) { |
| | | router.push({ name }); |
| | | } |
| | | |
| | | async function loadSummary() { |
| | | summary.value = await getHomeSummary(); |
| | | } |
| | | |
| | | onMounted(() => { |
| | | loadSummary(); |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Page auto-content-height> |
| | | <!-- æ¬¢è¿æ --> |
| | | <div class="glass-card mb-6 flex items-center justify-between rounded-2xl p-6"> |
| | | <!-- æ¬¢è¿æ ï¼èéåº + éè²æè¾¹ --> |
| | | <div class="elite-hero mb-6"> |
| | | <img alt="" class="elite-hero-mark" :src="brandLogo" /> |
| | | <div class="relative z-10 flex flex-wrap items-center justify-between gap-4"> |
| | | <div> |
| | | <h1 class="bg-gradient-to-r from-blue-600 via-purple-600 to-pink-500 bg-clip-text text-2xl font-bold text-transparent dark:from-blue-400 dark:via-purple-400 dark:to-pink-400"> |
| | | {{ greeting }}ï¼æ¬¢è¿åæ¥ |
| | | </h1> |
| | | <p class="mt-1 text-sm text-gray-500 dark:text-gray-400">{{ today }}</p> |
| | | <h1 class="elite-hero-title">{{ greeting }}ï¼æ¬¢è¿åæ¥</h1> |
| | | <p class="elite-hero-date">{{ today }}</p> |
| | | </div> |
| | | <div class="hidden items-center gap-2 sm:flex"> |
| | | <div class="flex items-center gap-1.5 rounded-full bg-emerald-50 px-3 py-1.5 text-xs font-medium text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-400"> |
| | | <span class="relative flex size-2"> |
| | | <span class="absolute inline-flex size-full animate-ping rounded-full bg-emerald-400 opacity-75" /> |
| | | <span class="relative inline-flex size-2 rounded-full bg-emerald-500" /> |
| | | </span> |
| | | <div class="elite-status"> |
| | | <span class="elite-status-dot"></span> |
| | | ç³»ç»è¿è¡ä¸ |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- MES ç产 KPI --> |
| | | <KpiCards :summary="summary" @navigate="handleNavigate" /> |
| | | <!-- ç³»ç»ææ --> |
| | | <KpiCards @navigate="handleNavigate" /> |
| | | |
| | | <!-- ERP/WMS/BPM ç»è¥ KPI --> |
| | | <BizCards @navigate="handleNavigate" /> |
| | | |
| | | <!-- AI æºè½åæ --> |
| | | <AiInsights |
| | | :active-order-count="summary.workOrderActiveCount" |
| | | @navigate="handleNavigate" |
| | | /> |
| | | |
| | | <!-- ç产è¶å¿ + é¢è¦é¢æ¿ --> |
| | | <!-- å¾
å + å¿«æ·å
¥å£ --> |
| | | <Row :gutter="16" class="mb-4"> |
| | | <Col :lg="14" :md="24" :sm="24" :xl="14" :xs="24" class="mb-4"> |
| | | <div class="glass-card rounded-2xl p-5"> |
| | | <h3 class="mb-4 text-base font-semibold text-gray-800 dark:text-gray-100"> |
| | | ç产è¶å¿ï¼è¿7å¤©ï¼ |
| | | </h3> |
| | | <ProductionTrend /> |
| | | </div> |
| | | <TodoPanel @navigate="handleNavigate" /> |
| | | </Col> |
| | | <Col :lg="10" :md="24" :sm="24" :xl="10" :xs="24" class="mb-4"> |
| | | <AlertPanel |
| | | :repair-active-count="summary.repairActiveCount" |
| | | @navigate="handleNavigate" |
| | | /> |
| | | </Col> |
| | | </Row> |
| | | |
| | | <!-- å¿«æ·æä½ --> |
| | | <Row :gutter="16" class="mb-4"> |
| | | <Col :span="24"> |
| | | <QuickActions @navigate="handleNavigate" /> |
| | | </Col> |
| | | </Row> |
| | | |
| | | <!-- æè¿è¯¾é¢ç³æ¥ --> |
| | | <RecentApplications @navigate="handleNavigate" /> |
| | | </Page> |
| | | </template> |
| | | |
| | | <style scoped> |
| | | .glass-card { |
| | | background: linear-gradient(135deg, rgba(255,255,255,0.8), rgba(255,255,255,0.5)); |
| | | backdrop-filter: blur(20px); |
| | | -webkit-backdrop-filter: blur(20px); |
| | | border: 1px solid rgba(255,255,255,0.6); |
| | | box-shadow: |
| | | 0 8px 32px rgba(0,0,0,0.04), |
| | | 0 2px 8px rgba(0,0,0,0.02), |
| | | inset 0 1px 0 rgba(255,255,255,0.8); |
| | | /* ===== æ¬¢è¿æ ===== */ |
| | | .elite-hero { |
| | | position: relative; |
| | | overflow: hidden; |
| | | border-radius: 12px; |
| | | padding: 30px 34px; |
| | | background: |
| | | radial-gradient(circle at 88% 12%, rgba(212, 175, 55, 0.08), transparent 50%), |
| | | linear-gradient(135deg, #123156 0%, #0f2440 58%, #0b1c33 100%); |
| | | border: 1px solid rgba(212, 175, 55, 0.24); |
| | | box-shadow: 0 4px 18px rgba(15, 36, 64, 0.12); |
| | | } |
| | | |
| | | .dark .glass-card { |
| | | background: linear-gradient(135deg, rgba(30,30,50,0.7), rgba(20,20,40,0.5)); |
| | | border: 1px solid rgba(255,255,255,0.08); |
| | | box-shadow: |
| | | 0 8px 32px rgba(0,0,0,0.3), |
| | | 0 2px 8px rgba(0,0,0,0.2), |
| | | inset 0 1px 0 rgba(255,255,255,0.05); |
| | | /* å³ä¾§æ·¡æ·¡çå¾½æ æ°´å° */ |
| | | .elite-hero-mark { |
| | | position: absolute; |
| | | top: 50%; |
| | | right: 168px; |
| | | width: 118px; |
| | | height: 118px; |
| | | pointer-events: none; |
| | | opacity: 0.05; |
| | | transform: translateY(-50%); |
| | | } |
| | | |
| | | .elite-hero-title { |
| | | margin: 0; |
| | | font-size: 22px; |
| | | font-weight: 600; |
| | | letter-spacing: 2px; |
| | | color: #f5f0e4; |
| | | } |
| | | |
| | | .elite-hero-date { |
| | | margin: 8px 0 0; |
| | | font-size: 13px; |
| | | letter-spacing: 1px; |
| | | color: rgba(212, 175, 55, 0.85); |
| | | } |
| | | |
| | | .elite-status { |
| | | display: inline-flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | padding: 7px 16px; |
| | | font-size: 12.5px; |
| | | letter-spacing: 1px; |
| | | color: #f0e2b8; |
| | | background: rgba(212, 175, 55, 0.12); |
| | | border: 1px solid rgba(212, 175, 55, 0.38); |
| | | border-radius: 999px; |
| | | } |
| | | |
| | | /* éæç¶æç¹ï¼ä¸åèå²å¨ç» */ |
| | | .elite-status-dot { |
| | | display: inline-flex; |
| | | width: 7px; |
| | | height: 7px; |
| | | border-radius: 999px; |
| | | background: #d4af37; |
| | | } |
| | | </style> |
| | |
| | | <script lang="ts" setup> |
| | | import type { MesHomeApi } from '#/api/mes/home'; |
| | | |
| | | import { computed, ref } from 'vue'; |
| | | import { onMounted, ref } from 'vue'; |
| | | |
| | | import { CountTo } from '@vben/common-ui'; |
| | | import { IconifyIcon } from '@vben/icons'; |
| | | |
| | | import { Col, Row } from 'ant-design-vue'; |
| | | |
| | | defineOptions({ name: 'DashboardKpiCards' }); |
| | | import { getTaskTodoPage } from '#/api/bpm/task'; |
| | | import { getReimbursementPage } from '#/api/finance/reimbursement'; |
| | | import { getApplicationPage } from '#/api/research/application'; |
| | | import { getGuidelinePage } from '#/api/research/guideline'; |
| | | |
| | | const props = defineProps<{ |
| | | summary: MesHomeApi.Summary; |
| | | }>(); |
| | | defineOptions({ name: 'DashboardKpiCards' }); |
| | | |
| | | const emit = defineEmits<{ |
| | | navigate: [name: string]; |
| | | }>(); |
| | | |
| | | const qualityRate = computed(() => { |
| | | const total = |
| | | props.summary.todayQualifiedQuantity + |
| | | props.summary.todayUnqualifiedQuantity; |
| | | return total === 0 ? 0 : (props.summary.todayQualifiedQuantity / total) * 100; |
| | | const todoCount = ref(0); |
| | | const applicationCount = ref(0); |
| | | const reimbursementCount = ref(0); |
| | | const guidelineCount = ref(0); |
| | | |
| | | async function loadTodo() { |
| | | try { |
| | | const result = await getTaskTodoPage({ pageNo: 1, pageSize: 1 }); |
| | | todoCount.value = result.total ?? 0; |
| | | } catch { |
| | | /* æ æéææ¥å£å¼å¸¸ï¼ä¿æ 0 */ |
| | | } |
| | | } |
| | | |
| | | async function loadApplication() { |
| | | try { |
| | | const result = await getApplicationPage({ pageNo: 1, pageSize: 1 }); |
| | | applicationCount.value = result.total ?? 0; |
| | | } catch { |
| | | /* æ æéææ¥å£å¼å¸¸ï¼ä¿æ 0 */ |
| | | } |
| | | } |
| | | |
| | | async function loadReimbursement() { |
| | | try { |
| | | const result = await getReimbursementPage({ pageNo: 1, pageSize: 1 }); |
| | | reimbursementCount.value = result.total ?? 0; |
| | | } catch { |
| | | /* æ æéææ¥å£å¼å¸¸ï¼ä¿æ 0 */ |
| | | } |
| | | } |
| | | |
| | | async function loadGuideline() { |
| | | try { |
| | | const result = await getGuidelinePage({ pageNo: 1, pageSize: 1 }); |
| | | guidelineCount.value = result.total ?? 0; |
| | | } catch { |
| | | /* æ æéææ¥å£å¼å¸¸ï¼ä¿æ 0 */ |
| | | } |
| | | } |
| | | |
| | | onMounted(() => { |
| | | loadTodo(); |
| | | loadApplication(); |
| | | loadReimbursement(); |
| | | loadGuideline(); |
| | | }); |
| | | |
| | | const equipmentRate = computed(() => { |
| | | if (props.summary.machineryTotal === 0) return 0; |
| | | return (props.summary.machineryProducing / props.summary.machineryTotal) * 100; |
| | | }); |
| | | |
| | | /** 3D å¾æææ */ |
| | | interface TiltState { x: number; y: number } |
| | | |
| | | const tilts = ref<Record<string, TiltState>>({}); |
| | | |
| | | function onMouseMove(e: MouseEvent, id: string) { |
| | | const el = e.currentTarget as HTMLElement; |
| | | const rect = el.getBoundingClientRect(); |
| | | const x = ((e.clientX - rect.left) / rect.width - 0.5) * 2; |
| | | const y = ((e.clientY - rect.top) / rect.height - 0.5) * 2; |
| | | tilts.value[id] = { x: x * -6, y: y * 6 }; |
| | | } |
| | | |
| | | function onMouseLeave(id: string) { |
| | | tilts.value[id] = { x: 0, y: 0 }; |
| | | } |
| | | |
| | | function tiltStyle(id: string) { |
| | | const t = tilts.value[id] ?? { x: 0, y: 0 }; |
| | | return { |
| | | transform: `perspective(600px) rotateX(${t.y}deg) rotateY(${t.x}deg)`, |
| | | transition: t.x === 0 && t.y === 0 ? 'transform 0.5s ease' : 'transform 0.1s ease', |
| | | }; |
| | | } |
| | | </script> |
| | | |
| | | <template> |
| | | <Row :gutter="16"> |
| | | <!-- ç产订å --> |
| | | <!-- æçå¾
å --> |
| | | <Col :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4"> |
| | | <div |
| | | class="glass-tilt group cursor-pointer overflow-hidden rounded-2xl border-0" |
| | | :style="tiltStyle('order')" |
| | | @mousemove="(e: MouseEvent) => onMouseMove(e, 'order')" |
| | | @mouseleave="onMouseLeave('order')" |
| | | @click="emit('navigate', 'MesProWorkOrder')" |
| | | class="elite-card group cursor-pointer" |
| | | @click="emit('navigate', 'BpmTaskTodo')" |
| | | > |
| | | <div class="relative z-10 flex items-center gap-4 p-5"> |
| | | <div class="flex size-12 flex-shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-blue-500 to-cyan-400 shadow-lg shadow-blue-500/25 transition-transform duration-300 group-hover:scale-110"> |
| | | <IconifyIcon class="size-6 text-white" icon="lucide:clipboard-list" /> |
| | | <div class="flex items-center gap-4 p-5"> |
| | | <div class="elite-icon-tile"> |
| | | <IconifyIcon class="size-6" icon="lucide:list-todo" /> |
| | | </div> |
| | | <div class="min-w-0 flex-1"> |
| | | <div class="mb-1 text-xs font-medium text-gray-500 dark:text-gray-400">ç产订å</div> |
| | | <div class="flex items-baseline gap-1"> |
| | | <CountTo class="text-2xl font-bold leading-tight text-blue-600 dark:text-blue-400" :end-val="summary.workOrderActiveCount" :duration="1200" /> |
| | | <span class="text-xs text-gray-400">è¿è¡ä¸</span> |
| | | </div> |
| | | <div class="mt-1 flex items-center gap-1 text-xs text-gray-400"> |
| | | <span class="inline-block size-1.5 rounded-full bg-amber-400" /> å¾
æäº§ {{ summary.workOrderPrepareCount }} |
| | | <span class="mx-1">|</span> |
| | | <span class="inline-block size-1.5 rounded-full bg-emerald-400" /> 已宿 {{ summary.workOrderFinishedCount }} |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="absolute inset-0 -z-0 bg-gradient-to-br from-blue-500/5 to-cyan-500/5 opacity-0 transition-opacity duration-300 group-hover:opacity-100" /> |
| | | <div class="absolute -right-6 -top-6 size-24 rounded-full bg-blue-400/10 blur-xl transition-all duration-300 group-hover:scale-150" /> |
| | | </div> |
| | | </Col> |
| | | |
| | | <!-- 仿¥äº§é --> |
| | | <Col :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4"> |
| | | <div |
| | | class="glass-tilt group cursor-pointer overflow-hidden rounded-2xl border-0" |
| | | :style="tiltStyle('output')" |
| | | @mousemove="(e: MouseEvent) => onMouseMove(e, 'output')" |
| | | @mouseleave="onMouseLeave('output')" |
| | | @click="emit('navigate', 'MesProFeedback')" |
| | | > |
| | | <div class="relative z-10 flex items-center gap-4 p-5"> |
| | | <div class="flex size-12 flex-shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-emerald-500 to-green-400 shadow-lg shadow-emerald-500/25 transition-transform duration-300 group-hover:scale-110"> |
| | | <IconifyIcon class="size-6 text-white" icon="lucide:trending-up" /> |
| | | </div> |
| | | <div class="min-w-0 flex-1"> |
| | | <div class="mb-1 text-xs font-medium text-gray-500 dark:text-gray-400">仿¥äº§é</div> |
| | | <div class="flex items-baseline gap-1"> |
| | | <CountTo class="text-2xl font-bold leading-tight text-emerald-600 dark:text-emerald-400" :end-val="summary.todayOutput" :duration="1200" /> |
| | | <span class="text-xs text-gray-400">ä»¶</span> |
| | | </div> |
| | | <div class="mt-1 text-xs text-gray-400"> |
| | | æ¨æ¥ {{ summary.yesterdayOutput }} ä»¶ |
| | | <span v-if="summary.yesterdayOutput > 0 && summary.todayOutput >= summary.yesterdayOutput" class="text-emerald-500">â</span> |
| | | <span v-else-if="summary.yesterdayOutput > 0" class="text-red-500">â</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="absolute inset-0 -z-0 bg-gradient-to-br from-emerald-500/5 to-green-500/5 opacity-0 transition-opacity duration-300 group-hover:opacity-100" /> |
| | | <div class="absolute -right-6 -top-6 size-24 rounded-full bg-emerald-400/10 blur-xl transition-all duration-300 group-hover:scale-150" /> |
| | | </div> |
| | | </Col> |
| | | |
| | | <!-- è´¨éåæ ¼ç --> |
| | | <Col :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4"> |
| | | <div |
| | | class="glass-tilt group cursor-pointer overflow-hidden rounded-2xl border-0" |
| | | :style="tiltStyle('quality')" |
| | | @mousemove="(e: MouseEvent) => onMouseMove(e, 'quality')" |
| | | @mouseleave="onMouseLeave('quality')" |
| | | @click="emit('navigate', 'MesQcRecord')" |
| | | > |
| | | <div class="relative z-10 flex items-center gap-4 p-5"> |
| | | <div class="flex size-12 flex-shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-orange-500 to-amber-400 shadow-lg shadow-orange-500/25 transition-transform duration-300 group-hover:scale-110"> |
| | | <IconifyIcon class="size-6 text-white" icon="lucide:shield-check" /> |
| | | </div> |
| | | <div class="min-w-0 flex-1"> |
| | | <div class="mb-1 text-xs font-medium text-gray-500 dark:text-gray-400">è´¨éåæ ¼ç</div> |
| | | <div class="elite-label">æçå¾
å</div> |
| | | <div class="flex items-baseline gap-1"> |
| | | <CountTo |
| | | class="text-2xl font-bold leading-tight" |
| | | :class="qualityRate >= 95 ? 'text-emerald-600 dark:text-emerald-400' : qualityRate >= 80 ? 'text-orange-600 dark:text-orange-400' : 'text-red-600 dark:text-red-400'" |
| | | :decimals="1" :end-val="qualityRate" :duration="1200" |
| | | class="elite-value" |
| | | :class="todoCount > 0 ? 'text-[#b8901f] dark:text-[#e3c567]' : ''" |
| | | :duration="1200" |
| | | :end-val="todoCount" |
| | | /> |
| | | <span class="text-xs text-gray-400">%</span> |
| | | <span class="elite-unit">项</span> |
| | | </div> |
| | | <div class="mt-1 text-xs text-gray-400"> |
| | | åæ ¼ {{ summary.todayQualifiedQuantity }} | ä¸è¯ {{ summary.todayUnqualifiedQuantity }} |
| | | <div class="elite-meta">审æ¹ä¸å¿ · å¾
å¤çä»»å¡</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="absolute inset-0 -z-0 bg-gradient-to-br from-orange-500/5 to-amber-500/5 opacity-0 transition-opacity duration-300 group-hover:opacity-100" /> |
| | | <div class="absolute -right-6 -top-6 size-24 rounded-full bg-orange-400/10 blur-xl transition-all duration-300 group-hover:scale-150" /> |
| | | </div> |
| | | </Col> |
| | | |
| | | <!-- 设å¤è¿è¡ç --> |
| | | <!-- 课é¢ç³æ¥ --> |
| | | <Col :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4"> |
| | | <div |
| | | class="glass-tilt group cursor-pointer overflow-hidden rounded-2xl border-0" |
| | | :style="tiltStyle('equip')" |
| | | @mousemove="(e: MouseEvent) => onMouseMove(e, 'equip')" |
| | | @mouseleave="onMouseLeave('equip')" |
| | | @click="emit('navigate', 'MesDvMachinery')" |
| | | class="elite-card group cursor-pointer" |
| | | @click="emit('navigate', 'ResearchApplication')" |
| | | > |
| | | <div class="relative z-10 flex items-center gap-4 p-5"> |
| | | <div class="flex size-12 flex-shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-violet-500 to-purple-400 shadow-lg shadow-violet-500/25 transition-transform duration-300 group-hover:scale-110"> |
| | | <IconifyIcon class="size-6 text-white" icon="lucide:cpu" /> |
| | | <div class="flex items-center gap-4 p-5"> |
| | | <div class="elite-icon-tile"> |
| | | <IconifyIcon class="size-6" icon="lucide:file-text" /> |
| | | </div> |
| | | <div class="min-w-0 flex-1"> |
| | | <div class="mb-1 text-xs font-medium text-gray-500 dark:text-gray-400">设å¤è¿è¡ç</div> |
| | | <div class="elite-label">课é¢ç³æ¥</div> |
| | | <div class="flex items-baseline gap-1"> |
| | | <CountTo class="text-2xl font-bold leading-tight text-violet-600 dark:text-violet-400" :decimals="1" :end-val="equipmentRate" :duration="1200" /> |
| | | <span class="text-xs text-gray-400">%</span> |
| | | <CountTo |
| | | class="elite-value" |
| | | :duration="1200" |
| | | :end-val="applicationCount" |
| | | /> |
| | | <span class="elite-unit">项</span> |
| | | </div> |
| | | <div class="mt-1 flex items-center gap-1 text-xs text-gray-400"> |
| | | <span class="text-red-400">åæº {{ summary.machineryStop }}</span> |
| | | <span class="mx-1">|</span> |
| | | <span class="text-amber-400">ç»´æ¤ {{ summary.machineryMaintenance }}</span> |
| | | <div class="elite-meta">课é¢ç®¡ç · ç³æ¥ä¹¦</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="absolute inset-0 -z-0 bg-gradient-to-br from-violet-500/5 to-purple-500/5 opacity-0 transition-opacity duration-300 group-hover:opacity-100" /> |
| | | <div class="absolute -right-6 -top-6 size-24 rounded-full bg-violet-400/10 blur-xl transition-all duration-300 group-hover:scale-150" /> |
| | | </Col> |
| | | |
| | | <!-- è´¢å¡æ¥é --> |
| | | <Col :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4"> |
| | | <div |
| | | class="elite-card group cursor-pointer" |
| | | @click="emit('navigate', 'FinanceReimbursement')" |
| | | > |
| | | <div class="flex items-center gap-4 p-5"> |
| | | <div class="elite-icon-tile"> |
| | | <IconifyIcon class="size-6" icon="lucide:receipt" /> |
| | | </div> |
| | | <div class="min-w-0 flex-1"> |
| | | <div class="elite-label">è´¢å¡æ¥é</div> |
| | | <div class="flex items-baseline gap-1"> |
| | | <CountTo |
| | | class="elite-value" |
| | | :duration="1200" |
| | | :end-val="reimbursementCount" |
| | | /> |
| | | <span class="elite-unit">å</span> |
| | | </div> |
| | | <div class="elite-meta">è´¢å¡æ¥é · æ¥éå</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </Col> |
| | | |
| | | <!-- è¯¾é¢æå --> |
| | | <Col :lg="6" :md="12" :sm="12" :xl="6" :xs="24" class="mb-4"> |
| | | <div |
| | | class="elite-card group cursor-pointer" |
| | | @click="emit('navigate', 'ResearchGuideline')" |
| | | > |
| | | <div class="flex items-center gap-4 p-5"> |
| | | <div class="elite-icon-tile"> |
| | | <IconifyIcon class="size-6" icon="lucide:book-open" /> |
| | | </div> |
| | | <div class="min-w-0 flex-1"> |
| | | <div class="elite-label">è¯¾é¢æå</div> |
| | | <div class="flex items-baseline gap-1"> |
| | | <CountTo |
| | | class="elite-value" |
| | | :duration="1200" |
| | | :end-val="guidelineCount" |
| | | /> |
| | | <span class="elite-unit">份</span> |
| | | </div> |
| | | <div class="elite-meta">课é¢ç®¡ç · ç³æ¥æå</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </Col> |
| | | </Row> |
| | | </template> |
| | | |
| | | <style scoped> |
| | | .glass-tilt { |
| | | background: linear-gradient(135deg, rgba(255,255,255,0.85), rgba(255,255,255,0.55)); |
| | | backdrop-filter: blur(20px); |
| | | -webkit-backdrop-filter: blur(20px); |
| | | border: 1px solid rgba(255,255,255,0.6); |
| | | box-shadow: |
| | | 0 4px 24px rgba(0,0,0,0.04), |
| | | 0 1px 4px rgba(0,0,0,0.02), |
| | | inset 0 1px 0 rgba(255,255,255,0.8); |
| | | transform-style: preserve-3d; |
| | | /* ===== 妿¯å
¸é
å¡ç ===== */ |
| | | .elite-card { |
| | | background: #fdfcfa; |
| | | border: 1px solid rgba(212, 175, 55, 0.22); |
| | | border-radius: 14px; |
| | | box-shadow: 0 2px 14px rgba(15, 36, 64, 0.05); |
| | | transition: |
| | | border-color 0.3s ease, |
| | | box-shadow 0.3s ease; |
| | | } |
| | | |
| | | .dark .glass-tilt { |
| | | background: linear-gradient(135deg, rgba(30,30,55,0.75), rgba(20,20,40,0.5)); |
| | | border: 1px solid rgba(255,255,255,0.08); |
| | | box-shadow: |
| | | 0 4px 24px rgba(0,0,0,0.3), |
| | | 0 1px 4px rgba(0,0,0,0.2), |
| | | inset 0 1px 0 rgba(255,255,255,0.04); |
| | | .elite-card:hover { |
| | | border-color: rgba(212, 175, 55, 0.5); |
| | | box-shadow: 0 6px 20px rgba(15, 36, 64, 0.09); |
| | | } |
| | | |
| | | .dark .elite-card { |
| | | background: #101a2b; |
| | | border-color: rgba(212, 175, 55, 0.2); |
| | | box-shadow: 0 2px 14px rgba(0, 0, 0, 0.3); |
| | | } |
| | | |
| | | /* 徿 åºï¼è鿏å + éè²å¾æ */ |
| | | .elite-icon-tile { |
| | | display: flex; |
| | | width: 48px; |
| | | height: 48px; |
| | | flex-shrink: 0; |
| | | align-items: center; |
| | | justify-content: center; |
| | | border-radius: 12px; |
| | | color: #e3c567; |
| | | background: linear-gradient(135deg, #1e4571, #0f2440); |
| | | border: 1px solid rgba(212, 175, 55, 0.3); |
| | | box-shadow: 0 4px 12px rgba(15, 36, 64, 0.18); |
| | | transition: border-color 0.3s ease; |
| | | } |
| | | |
| | | /* æ¬ååªè®©éè²æè¾¹å¾®å¾®æäº®ï¼ä¸åç¼©æ¾ */ |
| | | .group:hover .elite-icon-tile { |
| | | border-color: rgba(212, 175, 55, 0.6); |
| | | } |
| | | |
| | | .elite-label { |
| | | margin-bottom: 4px; |
| | | font-size: 12px; |
| | | font-weight: 500; |
| | | letter-spacing: 1px; |
| | | color: #8a94a6; |
| | | } |
| | | |
| | | .elite-value { |
| | | font-size: 24px; |
| | | font-weight: 700; |
| | | line-height: 1.2; |
| | | color: #0f2440; |
| | | } |
| | | |
| | | .dark .elite-value { |
| | | color: #e3c567; |
| | | } |
| | | |
| | | .elite-unit { |
| | | font-size: 12px; |
| | | color: #8a94a6; |
| | | } |
| | | |
| | | .elite-meta { |
| | | margin-top: 4px; |
| | | font-size: 12px; |
| | | color: #8a94a6; |
| | | } |
| | | </style> |
| | |
| | | title: string; |
| | | icon: string; |
| | | route: string; |
| | | gradient: string; |
| | | shadow: string; |
| | | } |
| | | |
| | | /** å¿«æ·å
¥å£ï¼å¯¹åºç³»ç»å®é
èåï¼å®¡æ¹ä¸å¿ / 课é¢ç®¡ç / è´¢å¡æ¥é / éç¥å
¬åï¼ */ |
| | | const actions: QuickAction[] = [ |
| | | { title: 'ç产工å', icon: 'lucide:file-text', route: 'MesProWorkOrder', gradient: 'from-blue-500 to-cyan-500', shadow: 'shadow-blue-500/15' }, |
| | | { title: 'ç产æ¥å·¥', icon: 'lucide:check-square', route: 'MesProFeedback', gradient: 'from-emerald-500 to-green-500', shadow: 'shadow-emerald-500/15' }, |
| | | { title: 'è´¨æ£ç®¡ç', icon: 'lucide:clipboard-check', route: 'MesQcRecord', gradient: 'from-orange-500 to-amber-500', shadow: 'shadow-orange-500/15' }, |
| | | { title: '设å¤ç¹æ£', icon: 'lucide:settings', route: 'MesDvMachinery', gradient: 'from-violet-500 to-purple-500', shadow: 'shadow-violet-500/15' }, |
| | | { title: 'ç产æç¨', icon: 'lucide:calendar-range', route: 'MesProScheduling', gradient: 'from-cyan-500 to-teal-500', shadow: 'shadow-cyan-500/15' }, |
| | | { title: 'ç©æåºå', icon: 'lucide:package-search', route: 'WlsMaterialStock', gradient: 'from-indigo-500 to-blue-500', shadow: 'shadow-indigo-500/15' }, |
| | | { title: 'å·¥èºè·¯çº¿', icon: 'lucide:route', route: 'MesPdRouting', gradient: 'from-teal-500 to-emerald-500', shadow: 'shadow-teal-500/15' }, |
| | | { title: 'æ°æ®æ¥è¡¨', icon: 'lucide:bar-chart-big', route: 'MesHomeIndex', gradient: 'from-pink-500 to-rose-500', shadow: 'shadow-pink-500/15' }, |
| | | { title: 'åèµ·æµç¨', icon: 'lucide:circle-plus', route: 'BpmProcessInstanceCreate' }, |
| | | { title: 'å¾
åä»»å¡', icon: 'lucide:list-todo', route: 'BpmTaskTodo' }, |
| | | { title: 'å·²åä»»å¡', icon: 'lucide:circle-check-big', route: 'BpmTaskDone' }, |
| | | { title: 'æçæµç¨', icon: 'lucide:git-branch', route: 'BpmProcessInstance' }, |
| | | { title: '课é¢ç³æ¥', icon: 'lucide:file-text', route: 'ResearchApplication' }, |
| | | { title: 'è¯¾é¢æå', icon: 'lucide:book-open', route: 'ResearchGuideline' }, |
| | | { title: 'è´¢å¡æ¥é', icon: 'lucide:receipt', route: 'FinanceReimbursement' }, |
| | | { title: 'éç¥å
Œ', icon: 'lucide:bell', route: 'BpmOaNotice' }, |
| | | ]; |
| | | </script> |
| | | |
| | | <template> |
| | | <div class="glass-card rounded-2xl p-5"> |
| | | <h3 class="mb-4 text-sm font-semibold text-gray-800 dark:text-gray-100">å¿«æ·æä½</h3> |
| | | <div class="grid grid-cols-4 gap-3 sm:grid-cols-8"> |
| | | <div class="elite-card p-5"> |
| | | <div class="elite-head"> |
| | | <h3 class="elite-title"> |
| | | <span class="elite-bar"></span> |
| | | å¿«æ·å
¥å£ |
| | | </h3> |
| | | </div> |
| | | <div class="grid grid-cols-4 gap-3"> |
| | | <div |
| | | v-for="action in actions" |
| | | :key="action.route" |
| | | class="group flex cursor-pointer flex-col items-center gap-2 rounded-xl p-3 transition-all duration-300 hover:-translate-y-1" |
| | | class="elite-action group flex cursor-pointer flex-col items-center gap-2 rounded-xl p-3 transition-colors duration-300" |
| | | @click="emit('navigate', action.route)" |
| | | > |
| | | <div |
| | | class="flex size-10 items-center justify-center rounded-xl bg-gradient-to-br transition-shadow duration-300 group-hover:shadow-lg" |
| | | :class="[action.gradient, action.shadow]" |
| | | class="elite-icon flex size-10 items-center justify-center rounded-xl transition-shadow duration-300" |
| | | > |
| | | <IconifyIcon :icon="action.icon" class="size-5 text-white" /> |
| | | <IconifyIcon :icon="action.icon" class="size-5 text-[#e3c567]" /> |
| | | </div> |
| | | <span class="text-xs font-medium text-gray-600 dark:text-gray-400">{{ action.title }}</span> |
| | | <span class="elite-action-label">{{ action.title }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <style scoped> |
| | | .glass-card { |
| | | background: linear-gradient(135deg, rgba(255,255,255,0.85), rgba(255,255,255,0.55)); |
| | | backdrop-filter: blur(20px); |
| | | -webkit-backdrop-filter: blur(20px); |
| | | border: 1px solid rgba(255,255,255,0.6); |
| | | box-shadow: |
| | | 0 4px 24px rgba(0,0,0,0.04), |
| | | 0 1px 4px rgba(0,0,0,0.02), |
| | | inset 0 1px 0 rgba(255,255,255,0.8); |
| | | .elite-card { |
| | | background: #fdfcfa; |
| | | border: 1px solid rgba(212, 175, 55, 0.22); |
| | | border-radius: 14px; |
| | | box-shadow: 0 2px 14px rgba(15, 36, 64, 0.05); |
| | | transition: |
| | | border-color 0.3s ease, |
| | | box-shadow 0.3s ease; |
| | | } |
| | | |
| | | .dark .glass-card { |
| | | background: linear-gradient(135deg, rgba(30,30,55,0.75), rgba(20,20,40,0.5)); |
| | | border: 1px solid rgba(255,255,255,0.08); |
| | | .elite-card:hover { |
| | | border-color: rgba(212, 175, 55, 0.5); |
| | | box-shadow: 0 6px 20px rgba(15, 36, 64, 0.09); |
| | | } |
| | | |
| | | .dark .elite-card { |
| | | background: #101a2b; |
| | | border-color: rgba(212, 175, 55, 0.2); |
| | | box-shadow: 0 2px 14px rgba(0, 0, 0, 0.3); |
| | | } |
| | | |
| | | .elite-head { |
| | | display: flex; |
| | | align-items: center; |
| | | margin-bottom: 16px; |
| | | } |
| | | |
| | | .elite-title { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | margin: 0; |
| | | font-size: 15px; |
| | | font-weight: 600; |
| | | letter-spacing: 1px; |
| | | color: #0f2440; |
| | | } |
| | | |
| | | .dark .elite-title { |
| | | color: #f0ead8; |
| | | } |
| | | |
| | | .elite-bar { |
| | | display: inline-block; |
| | | width: 3px; |
| | | height: 15px; |
| | | border-radius: 2px; |
| | | background: linear-gradient(180deg, #e3c567, #b8901f); |
| | | } |
| | | |
| | | /* 徿 åºï¼è鿏å + éè²å¾æ */ |
| | | .elite-icon { |
| | | color: #e3c567; |
| | | background: linear-gradient(135deg, #1e4571, #0f2440); |
| | | border: 1px solid rgba(212, 175, 55, 0.3); |
| | | box-shadow: 0 4px 12px rgba(15, 36, 64, 0.18); |
| | | } |
| | | |
| | | /* æ¬ååªæäº®éè²æè¾¹ï¼ä¸åä½ç§» */ |
| | | .elite-action:hover .elite-icon { |
| | | box-shadow: |
| | | 0 4px 24px rgba(0,0,0,0.3), |
| | | 0 1px 4px rgba(0,0,0,0.2), |
| | | inset 0 1px 0 rgba(255,255,255,0.04); |
| | | 0 0 0 1px rgba(212, 175, 55, 0.7), |
| | | 0 4px 14px rgba(15, 36, 64, 0.22); |
| | | } |
| | | |
| | | .elite-action-label { |
| | | font-size: 12px; |
| | | font-weight: 500; |
| | | letter-spacing: 0.5px; |
| | | color: #8a94a6; |
| | | } |
| | | |
| | | .elite-action:hover .elite-action-label { |
| | | color: #b8901f; |
| | | } |
| | | |
| | | .dark .elite-action:hover .elite-action-label { |
| | | color: #e3c567; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import { onMounted, ref } from 'vue'; |
| | | |
| | | import { IconifyIcon } from '@vben/icons'; |
| | | |
| | | import { getApplicationPage } from '#/api/research/application'; |
| | | |
| | | defineOptions({ name: 'DashboardRecentApplications' }); |
| | | |
| | | const emit = defineEmits<{ |
| | | navigate: [name: string]; |
| | | }>(); |
| | | |
| | | interface Row { |
| | | id?: number; |
| | | no?: string; |
| | | title: string; |
| | | applicantName: string; |
| | | status?: number; |
| | | createTime?: string; |
| | | } |
| | | |
| | | /** ç¶æç â å±ç¤ºææ¡ä¸é
è²ï¼ä¸ research_application_status åå
¸ä¿æä¸è´ */ |
| | | const STATUS_MAP: Record<number, { text: string; tone: string }> = { |
| | | 0: { text: 'è稿', tone: 'gray' }, |
| | | 15: { text: '审æ¹ä¸', tone: 'gold' }, |
| | | 20: { text: 'å·²ç«é¡¹', tone: 'green' }, |
| | | 35: { text: '已驳å', tone: 'red' }, |
| | | }; |
| | | |
| | | function statusOf(code?: number) { |
| | | if (code === undefined || code === null) { |
| | | return { text: 'â', tone: 'gray' }; |
| | | } |
| | | return STATUS_MAP[code] ?? { text: String(code), tone: 'gray' }; |
| | | } |
| | | |
| | | const loading = ref(true); |
| | | const rows = ref<Row[]>([]); |
| | | |
| | | async function load() { |
| | | loading.value = true; |
| | | try { |
| | | const result = await getApplicationPage({ pageNo: 1, pageSize: 6 }); |
| | | rows.value = (result.list ?? []) as Row[]; |
| | | } catch { |
| | | rows.value = []; |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | } |
| | | |
| | | onMounted(load); |
| | | </script> |
| | | |
| | | <template> |
| | | <div class="elite-section"> |
| | | <div class="elite-head"> |
| | | <h3 class="elite-title"> |
| | | <span class="elite-bar"></span> |
| | | æè¿è¯¾é¢ç³æ¥ |
| | | </h3> |
| | | <span class="elite-more" @click="emit('navigate', 'ResearchApplication')"> |
| | | å
¨é¨ |
| | | <IconifyIcon class="size-3.5" icon="lucide:chevron-right" /> |
| | | </span> |
| | | </div> |
| | | |
| | | <div v-if="loading" class="elite-empty">å è½½ä¸â¦</div> |
| | | |
| | | <div v-else-if="rows.length === 0" class="elite-empty"> |
| | | <IconifyIcon class="mb-2 size-8 opacity-40" icon="lucide:file-text" /> |
| | | <div>ææ è¯¾é¢ç³æ¥è®°å½</div> |
| | | </div> |
| | | |
| | | <table v-else class="elite-table"> |
| | | <thead> |
| | | <tr> |
| | | <th class="col-title">课é¢åç§°</th> |
| | | <th class="col-applicant">ç³æ¥äºº</th> |
| | | <th class="col-status">ç¶æ</th> |
| | | <th class="col-time">ç³æ¥æ¶é´</th> |
| | | </tr> |
| | | </thead> |
| | | <tbody> |
| | | <tr |
| | | v-for="row in rows" |
| | | :key="row.id ?? row.no" |
| | | @click="emit('navigate', 'ResearchApplication')" |
| | | > |
| | | <td class="col-title"> |
| | | <span class="elite-ellipsis">{{ row.title }}</span> |
| | | </td> |
| | | <td class="col-applicant">{{ row.applicantName || 'â' }}</td> |
| | | <td class="col-status"> |
| | | <span class="elite-chip" :class="`tone-${statusOf(row.status).tone}`"> |
| | | {{ statusOf(row.status).text }} |
| | | </span> |
| | | </td> |
| | | <td class="col-time">{{ row.createTime || 'â' }}</td> |
| | | </tr> |
| | | </tbody> |
| | | </table> |
| | | </div> |
| | | </template> |
| | | |
| | | <style scoped> |
| | | .elite-section { |
| | | border-radius: 14px; |
| | | padding: 20px; |
| | | background: #fdfcfa; |
| | | border: 1px solid rgba(212, 175, 55, 0.22); |
| | | box-shadow: 0 2px 14px rgba(15, 36, 64, 0.05); |
| | | } |
| | | |
| | | .dark .elite-section { |
| | | background: #101a2b; |
| | | border-color: rgba(212, 175, 55, 0.2); |
| | | box-shadow: 0 2px 14px rgba(0, 0, 0, 0.3); |
| | | } |
| | | |
| | | .elite-head { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | margin-bottom: 14px; |
| | | } |
| | | |
| | | .elite-title { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | margin: 0; |
| | | font-size: 15px; |
| | | font-weight: 600; |
| | | letter-spacing: 1px; |
| | | color: #0f2440; |
| | | } |
| | | |
| | | .dark .elite-title { |
| | | color: #f0ead8; |
| | | } |
| | | |
| | | .elite-bar { |
| | | display: inline-block; |
| | | width: 3px; |
| | | height: 15px; |
| | | border-radius: 2px; |
| | | background: linear-gradient(180deg, #e3c567, #b8901f); |
| | | } |
| | | |
| | | .elite-more { |
| | | display: inline-flex; |
| | | cursor: pointer; |
| | | align-items: center; |
| | | gap: 2px; |
| | | font-size: 12px; |
| | | color: #b8901f; |
| | | transition: color 0.2s ease; |
| | | } |
| | | |
| | | .elite-more:hover { |
| | | color: #8a6a12; |
| | | } |
| | | |
| | | .elite-empty { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | padding: 34px 0; |
| | | font-size: 13px; |
| | | color: #8a94a6; |
| | | } |
| | | |
| | | .elite-table { |
| | | width: 100%; |
| | | border-collapse: collapse; |
| | | table-layout: fixed; |
| | | } |
| | | |
| | | .elite-table th { |
| | | padding: 0 8px 10px; |
| | | font-size: 12px; |
| | | font-weight: 500; |
| | | letter-spacing: 1px; |
| | | color: #8a94a6; |
| | | text-align: left; |
| | | border-bottom: 1px solid rgba(15, 36, 64, 0.08); |
| | | } |
| | | |
| | | .elite-table td { |
| | | padding: 12px 8px; |
| | | font-size: 13.5px; |
| | | color: #0f2440; |
| | | border-bottom: 1px solid rgba(15, 36, 64, 0.05); |
| | | } |
| | | |
| | | .dark .elite-table td { |
| | | color: #e6e9ef; |
| | | } |
| | | |
| | | .elite-table tbody tr { |
| | | cursor: pointer; |
| | | transition: background-color 0.2s ease; |
| | | } |
| | | |
| | | .elite-table tbody tr:hover { |
| | | background: rgba(212, 175, 55, 0.07); |
| | | } |
| | | |
| | | .elite-table tbody tr:last-child td { |
| | | border-bottom: none; |
| | | } |
| | | |
| | | .col-title { |
| | | width: 44%; |
| | | } |
| | | |
| | | .col-applicant { |
| | | width: 18%; |
| | | } |
| | | |
| | | .col-status { |
| | | width: 16%; |
| | | } |
| | | |
| | | .col-time { |
| | | width: 22%; |
| | | color: #8a94a6; |
| | | } |
| | | |
| | | .elite-ellipsis { |
| | | display: block; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | /* ç¶ææ ç¾ï¼é/绿/红/ç°åæ¡£ */ |
| | | .elite-chip { |
| | | display: inline-block; |
| | | padding: 2px 10px; |
| | | font-size: 12px; |
| | | border-radius: 999px; |
| | | border: 1px solid transparent; |
| | | } |
| | | |
| | | .tone-gold { |
| | | color: #b8901f; |
| | | background: rgba(212, 175, 55, 0.1); |
| | | border-color: rgba(212, 175, 55, 0.35); |
| | | } |
| | | |
| | | .tone-green { |
| | | color: #2f6b4f; |
| | | background: rgba(47, 107, 79, 0.1); |
| | | border-color: rgba(47, 107, 79, 0.3); |
| | | } |
| | | |
| | | .tone-red { |
| | | color: #b4453c; |
| | | background: rgba(180, 69, 60, 0.1); |
| | | border-color: rgba(180, 69, 60, 0.3); |
| | | } |
| | | |
| | | .tone-gray { |
| | | color: #8a94a6; |
| | | background: rgba(138, 148, 166, 0.1); |
| | | border-color: rgba(138, 148, 166, 0.28); |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import { onMounted, ref } from 'vue'; |
| | | |
| | | import { IconifyIcon } from '@vben/icons'; |
| | | |
| | | import { getTaskTodoPage } from '#/api/bpm/task'; |
| | | |
| | | defineOptions({ name: 'DashboardTodoPanel' }); |
| | | |
| | | const emit = defineEmits<{ |
| | | navigate: [name: string]; |
| | | }>(); |
| | | |
| | | interface TodoItem { |
| | | id: string; |
| | | name: string; |
| | | createTime?: number; |
| | | } |
| | | |
| | | const loading = ref(true); |
| | | const items = ref<TodoItem[]>([]); |
| | | |
| | | /** createTime ä¸ºæ¯«ç§æ¶é´æ³ï¼æ ¼å¼åææè¯»ææ¬ */ |
| | | function formatTime(ts?: number) { |
| | | if (!ts) return '-'; |
| | | const d = new Date(ts); |
| | | const p = (n: number) => String(n).padStart(2, '0'); |
| | | return `${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`; |
| | | } |
| | | |
| | | async function load() { |
| | | loading.value = true; |
| | | try { |
| | | const result = await getTaskTodoPage({ pageNo: 1, pageSize: 6 }); |
| | | items.value = (result.list ?? []).map((t: any) => ({ |
| | | id: String(t.id), |
| | | name: t.name ?? 'æªå½åä»»å¡', |
| | | createTime: t.createTime, |
| | | })); |
| | | } catch { |
| | | items.value = []; |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | } |
| | | |
| | | onMounted(load); |
| | | </script> |
| | | |
| | | <template> |
| | | <div class="elite-section h-full"> |
| | | <div class="elite-head"> |
| | | <h3 class="elite-title"> |
| | | <span class="elite-bar"></span> |
| | | æçå¾
å |
| | | </h3> |
| | | <span class="elite-more" @click="emit('navigate', 'BpmTaskTodo')"> |
| | | å
¨é¨ |
| | | <IconifyIcon class="size-3.5" icon="lucide:chevron-right" /> |
| | | </span> |
| | | </div> |
| | | |
| | | <!-- éª¨æ¶ --> |
| | | <div v-if="loading" class="elite-empty">å è½½ä¸â¦</div> |
| | | |
| | | <!-- ç©ºç¶æ --> |
| | | <div v-else-if="items.length === 0" class="elite-empty"> |
| | | <IconifyIcon class="mb-2 size-8 opacity-40" icon="lucide:inbox" /> |
| | | <div>ææ å¾
åä»»å¡</div> |
| | | </div> |
| | | |
| | | <!-- å表 --> |
| | | <ul v-else class="elite-list"> |
| | | <li |
| | | v-for="item in items" |
| | | :key="item.id" |
| | | class="elite-row" |
| | | @click="emit('navigate', 'BpmTaskTodo')" |
| | | > |
| | | <span class="elite-dot"></span> |
| | | <span class="elite-row-name">{{ item.name }}</span> |
| | | <span class="elite-row-time">{{ formatTime(item.createTime) }}</span> |
| | | </li> |
| | | </ul> |
| | | </div> |
| | | </template> |
| | | |
| | | <style scoped> |
| | | .elite-section { |
| | | border-radius: 14px; |
| | | padding: 20px; |
| | | background: #fdfcfa; |
| | | border: 1px solid rgba(212, 175, 55, 0.22); |
| | | box-shadow: 0 2px 14px rgba(15, 36, 64, 0.05); |
| | | } |
| | | |
| | | .dark .elite-section { |
| | | background: #101a2b; |
| | | border-color: rgba(212, 175, 55, 0.2); |
| | | box-shadow: 0 2px 14px rgba(0, 0, 0, 0.3); |
| | | } |
| | | |
| | | .elite-head { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | margin-bottom: 14px; |
| | | } |
| | | |
| | | .elite-title { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | margin: 0; |
| | | font-size: 15px; |
| | | font-weight: 600; |
| | | letter-spacing: 1px; |
| | | color: #0f2440; |
| | | } |
| | | |
| | | .dark .elite-title { |
| | | color: #f0ead8; |
| | | } |
| | | |
| | | .elite-bar { |
| | | display: inline-block; |
| | | width: 3px; |
| | | height: 15px; |
| | | border-radius: 2px; |
| | | background: linear-gradient(180deg, #e3c567, #b8901f); |
| | | } |
| | | |
| | | .elite-more { |
| | | display: inline-flex; |
| | | cursor: pointer; |
| | | align-items: center; |
| | | gap: 2px; |
| | | font-size: 12px; |
| | | color: #b8901f; |
| | | transition: color 0.2s ease; |
| | | } |
| | | |
| | | .elite-more:hover { |
| | | color: #8a6a12; |
| | | } |
| | | |
| | | .elite-empty { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | padding: 34px 0; |
| | | font-size: 13px; |
| | | color: #8a94a6; |
| | | } |
| | | |
| | | .elite-list { |
| | | margin: 0; |
| | | padding: 0; |
| | | list-style: none; |
| | | } |
| | | |
| | | .elite-row { |
| | | display: flex; |
| | | cursor: pointer; |
| | | align-items: center; |
| | | gap: 10px; |
| | | padding: 11px 10px; |
| | | border-radius: 8px; |
| | | border-bottom: 1px solid rgba(15, 36, 64, 0.05); |
| | | transition: background-color 0.2s ease; |
| | | } |
| | | |
| | | .elite-row:last-child { |
| | | border-bottom: none; |
| | | } |
| | | |
| | | .elite-row:hover { |
| | | background: rgba(212, 175, 55, 0.07); |
| | | } |
| | | |
| | | .elite-dot { |
| | | width: 6px; |
| | | height: 6px; |
| | | flex-shrink: 0; |
| | | border-radius: 999px; |
| | | background: #d4af37; |
| | | } |
| | | |
| | | .elite-row-name { |
| | | overflow: hidden; |
| | | flex: 1; |
| | | font-size: 13.5px; |
| | | color: #0f2440; |
| | | text-overflow: ellipsis; |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | .dark .elite-row-name { |
| | | color: #e6e9ef; |
| | | } |
| | | |
| | | .elite-row-time { |
| | | flex-shrink: 0; |
| | | font-size: 12px; |
| | | color: #8a94a6; |
| | | } |
| | | </style> |