Просмотр исходного кода

Merge branch 'dev_zyh' of United_Software/k_online_ui into dev

Jack Zhou 7 месяцев назад
Родитель
Сommit
d0b691c098

+ 2 - 2
.env.test

@@ -1,2 +1,2 @@
-VITE_API_HOST = 'https://ra.kerryapex.com/new/online_backend'
-VITE_BASE_URL = '/new/'
+VITE_API_HOST = '/kln/online_backend'
+VITE_BASE_URL = /kln/

+ 1 - 0
package.json

@@ -12,6 +12,7 @@
     "build-only": "vite build",
     "build:dev": "vite build --mode development",
     "build:pro": "vite build --mode product",
+    "build:test": "vite build --mode test",
     "type-check": "vue-tsc --build --force",
     "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
     "format": "prettier --write src/"

+ 7 - 4
src/components/NotificationMessageCard/src/components/EventCard.vue

@@ -23,6 +23,7 @@ interface EventCardPropsData {
   insert_date_format?: string // 用来跳转到System Message详情页
   frequency_type?: string // 用来跳转到System Message详情页
   rules_type?: string // 用来跳转到System Message详情页
+  is_display_hbol: boolean // 是否显示HBOL
   previous?: {
     date: string
     tag: string
@@ -110,7 +111,9 @@ const jumpTracking = (data: EventCardPropsData) => {
         <!-- 除了container类型,其他类型都显示运输方式图标 -->
         <div style="display: inline-block" v-if="data.type !== 'container'">
           <span class="font_family" :class="[`icon-${transportationMode?.[data.mode]}`]"></span>
-          <span @click="jumpTracking(data)" class="no no-link">HBOL: {{ data.no }}</span>
+          <span @click="jumpTracking(data)" class="no no-link"
+            >{{ data.is_display_hbol ? 'HBOL:' : 'MAWB:' }} {{ data.no }}</span
+          >
         </div>
         <!-- container类型显示图标 -->
         <div v-else>
@@ -157,7 +160,7 @@ const jumpTracking = (data: EventCardPropsData) => {
         <span style="margin-right: 3px">{{
           dayjs(data.info.time).format('MMM DD, YYYY hh:mm')
         }}</span>
-        <span>{{ getTimezone(data.info.timezone) }}</span>
+        <span>{{ getTimezone(data.info.timezone, data.info.time) }}</span>
       </div>
       <!-- <div class="change-time" v-if="data.type === 'change' && data.info?.time">
         <span style="margin-left: 1px" class="font_family icon-icon_time_b"></span>
@@ -167,13 +170,13 @@ const jumpTracking = (data: EventCardPropsData) => {
         <span class="font_family icon-icon_time_b"></span>
         <span style="margin-right: 3px" v-if="data.timeLabel">{{ data.timeLabel }}:</span>
         <span style="margin-right: 3px">{{ dayjs(data.time).format('MMM DD, YYYY hh:mm') }}</span>
-        <span>{{ getTimezone(data.timezone) }}</span>
+        <span>{{ getTimezone(data.timezone, data.time) }}</span>
       </div>
       <div class="previous" v-if="data.previous">
         <span class="previous-icon"></span>
         <span
           >{{ data.previous.tag }}&nbsp;({{ data.previous.time }}
-          {{ getTimezone(data.previous.timezone) }})</span
+          {{ getTimezone(data.previous.timezone, data.previous.time) }})</span
         >
       </div>
     </div>

+ 12 - 2
src/styles/elementui.scss

@@ -171,7 +171,7 @@ button.el-button.el-button--icon {
   }
 }
 // 初始为黑色
-.el-button.el-button--dark {
+button.el-button.el-button--dark {
   background-color: var(--color-btn-default-dark-bg);
   fill: var(--color-white);
   border: none;
@@ -183,7 +183,17 @@ button.el-button.el-button--icon {
     background-color: var(--color-btn-default-dark-hover-bg);
     fill: var(--color-btn-default-dark-hover-bg);
     span {
-      color: var(--color-btn-default-dark-hover) !important;
+      color: var(--color-btn-default-dark-hover);
+    }
+  }
+}
+button.el-button.el-button--dark.is-disabled {
+  opacity: 0.3;
+  &:hover {
+    background-color: var(--color-btn-default-dark-bg);
+    fill: var(--color-white);
+    span {
+      color: var(--color-white);
     }
   }
 }

+ 64 - 4
src/styles/icons/iconfont.css

@@ -1,9 +1,9 @@
 @font-face {
   font-family: "font_family"; /* Project id 4672385 */
-  src: url('iconfont.woff2?t=1740548496100') format('woff2'),
-       url('iconfont.woff?t=1740548496100') format('woff'),
-       url('iconfont.ttf?t=1740548496100') format('truetype'),
-       url('iconfont.svg?t=1740548496100#font_family') format('svg');
+  src: url('iconfont.woff2?t=1745286986564') format('woff2'),
+       url('iconfont.woff?t=1745286986564') format('woff'),
+       url('iconfont.ttf?t=1745286986564') format('truetype'),
+       url('iconfont.svg?t=1745286986564#font_family') format('svg');
 }
 
 .font_family {
@@ -14,6 +14,66 @@
   -moz-osx-font-smoothing: grayscale;
 }
 
+.icon-icon_good_b:before {
+  content: "\e713";
+}
+
+.icon-icon_notgood__filled_b:before {
+  content: "\e714";
+}
+
+.icon-icon_good__filled_b:before {
+  content: "\e715";
+}
+
+.icon-icon_notgood_b:before {
+  content: "\e716";
+}
+
+.icon-icon_send_b:before {
+  content: "\e712";
+}
+
+.icon-icon_sidebar__window_b:before {
+  content: "\e70f";
+}
+
+.icon-icon_maximized__window_b:before {
+  content: "\e710";
+}
+
+.icon-icon_collapsed__to_widget_b:before {
+  content: "\e711";
+}
+
+.icon-icon_theme_colors_b:before {
+  content: "\e70e";
+}
+
+.icon-icon_delay_b1:before {
+  content: "\e70b";
+}
+
+.icon-icon_collapse_b1:before {
+  content: "\e70c";
+}
+
+.icon-icon_cancel_b2:before {
+  content: "\e70d";
+}
+
+.icon-icon_ai_api_log_b:before {
+  content: "\e70a";
+}
+
+.icon-icon_collapse__sidebar_b:before {
+  content: "\e708";
+}
+
+.icon-icon_dashboard_title_b:before {
+  content: "\e709";
+}
+
 .icon-icon_collapse_b:before {
   content: "\e707";
 }

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
src/styles/icons/iconfont.js


+ 30 - 0
src/styles/icons/iconfont.svg

@@ -14,6 +14,36 @@
     />
       <missing-glyph />
       
+      <glyph glyph-name="icon_good_b" unicode="&#59155;" d="M549.376 780.48c-38.272 0-73.6-20.608-92.352-53.888L297.408 444.032H135.04a32 32 0 0 1-32-32v-392.384a32 32 0 0 1 32-32h98.56v-0.128h64v0.128h430.72a96 96 0 0 1 92.352 69.952l90.24 320.256a96 96 0 0 1-92.352 122.048h-162.944V674.368c0 58.624-47.552 106.112-106.176 106.112zM297.6 51.648v328.32h18.56a32 32 0 0 1 27.776 16.32L512.704 695.04a42.112 42.112 0 0 0 78.848-20.736v-206.464a32 32 0 0 1 32-32h194.944a32 32 0 0 0 30.72-40.64l-90.24-320.32a32 32 0 0 0-30.72-23.296h-430.72z m-64 328.32v-328.32H167.04v328.32h66.56z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_notgood__filled_b" unicode="&#59156;" d="M488.576-69.568c38.272 0 73.6 20.608 92.416 53.952l159.36 282.176V723.328H309.76a96 96 0 0 1-92.352-70.016l-90.24-320.256a96 96 0 0 1 92.352-122.048h162.944v-174.464c0-58.624 47.552-106.112 106.112-106.112z m315.776 792.896V266.88h98.624a32 32 0 0 1 32 32V691.2a32 32 0 0 1-32 32h-98.56z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_good__filled_b" unicode="&#59157;" d="M574.4 780.48c-38.272 0-73.6-20.608-92.416-53.888L322.432 444.032H160a32 32 0 0 1-32-32v-392.384a32 32 0 0 1 32-32h98.56v456.32h64v-456.32h430.656a96 96 0 0 1 92.416 69.952l90.24 320.256a96 96 0 0 1-92.416 122.048h-162.944V674.368c0 58.624-47.488 106.112-106.112 106.112z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_notgood_b" unicode="&#59158;" d="M488.576-69.568c38.272 0 73.6 20.608 92.416 53.952l159.616 282.56h162.368a32 32 0 0 1 32 32V691.2a32 32 0 0 1-32 32H309.76a96 96 0 0 1-92.352-69.952l-90.24-320.256a96 96 0 0 1 92.352-122.048h162.944v-174.464c0-58.624 47.552-106.112 106.112-106.112z m36.672 85.44a42.112 42.112 0 0 0-78.72 20.672v206.464a32 32 0 0 1-32 32H219.52a32 32 0 0 0-30.784 40.704l90.24 320.256a32 32 0 0 0 30.784 23.36h430.656V330.88h-18.56a32 32 0 0 1-27.84-16.256l-168.768-298.88zM804.48 330.88V659.2h66.56v-328.32h-66.56z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_send_b" unicode="&#59154;" d="M678.272 424.256l-460.928 225.92 56.32-225.92h404.608z m-403.712-76.8l-57.216-229.568 468.352 229.568H274.56z m601.664 65.28a32 32 0 0 0 0-57.472L164.672 6.592a32 32 0 0 0-45.184 36.48l83.072 333.184a32 32 0 0 1 0 15.488L119.488 724.992a32 32 0 0 0 45.184 36.48L876.16 412.736z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_sidebar__window_b" unicode="&#59151;" d="M740.352 809.28h132.16v-76.8h-132.16v76.8z m-72.448-102.592a25.6 25.6 0 0 0 25.6 25.6v76.8H119.04a102.4 102.4 0 0 1-102.4-102.4v-645.376a102.4 102.4 0 0 1 102.4-102.4H693.504v76.8a25.6 25.6 0 0 0-25.6 25.6v80.64h-76.8v-80.64c0-8.832 1.088-17.408 3.2-25.6H119.04a25.6 25.6 0 0 0-25.6 25.6V706.688c0 14.08 11.456 25.6 25.6 25.6h475.264c-2.112-8.192-3.2-16.768-3.2-25.6v-80.64h76.8v80.64z m251.072 25.6a25.6 25.6 0 0 0 25.6-25.6v-80.64h76.8v80.64a102.4 102.4 0 0 1-102.4 102.4v-76.8zM667.904 410.88V572.16h-76.8v-161.28h76.8z m276.672 161.28v-161.28h76.8V572.16h-76.8z m-276.672-376.384v161.28h-76.8v-161.28h76.8z m276.672 161.28v-161.28h76.8v161.28h-76.8z m0-215.04v-80.64a25.6 25.6 0 0 0-25.6-25.6v-76.8a102.4 102.4 0 0 1 102.4 102.4v80.64h-76.8zM541.632 406.592L433.28 514.944l-45.248-45.248 53.76-53.76H186.88v-64h254.784l-53.696-53.696 45.248-45.248 108.352 108.352a32 32 0 0 1 0 45.248z m330.88-370.88h-132.16v-76.8h132.16v76.8z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_maximized__window_b" unicode="&#59152;" d="M944.64 706.688a25.6 25.6 0 0 1-25.6 25.6h-224.96a25.6 25.6 0 0 1-25.6-25.6v-645.376c0-14.08 11.52-25.6 25.6-25.6h224.896a25.6 25.6 0 0 1 25.6 25.6V706.688z m-25.6 102.4a102.4 102.4 0 0 0 102.4-102.4v-645.376a102.4 102.4 0 0 0-102.4-102.4h-224.96a102.4 102.4 0 0 0-102.4 102.4V706.688a102.4 102.4 0 0 0 102.4 102.4h224.896z m-490.624-76.8h148.352v76.8H428.416v-76.8z m-220.48 0h162.944v76.8H207.936v-76.8z m-88.96 0h36.544v76.8h-36.48a102.4 102.4 0 0 1-102.4-102.4v-80.64h76.8v80.64c0 14.08 11.456 25.6 25.6 25.6z m-25.6-321.408V572.16H16.64v-161.28h76.8z m0-215.104v161.28H16.64v-161.28h76.8z m0-134.4v80.64H16.64v-80.64a102.4 102.4 0 0 1 102.4-102.4h36.544v76.8h-36.48a25.6 25.6 0 0 0-25.6 25.6z m483.392-25.6H428.416v-76.8h148.352v76.8z m-205.952 0H208v-76.8h162.944v76.8zM164.352 406.528L272.64 514.944l45.248-45.248-53.696-53.76h254.784v-64H264.192l53.696-53.696-45.248-45.248-108.352 108.352a32 32 0 0 0 0 45.248z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_collapsed__to_widget_b" unicode="&#59153;" d="M928.768 813.504c55.488 0 100.48-44.992 100.48-100.48v-327.808h-75.392V712.96a25.152 25.152 0 0 1-25.088 25.152H111.744a25.152 25.152 0 0 1-25.152-25.152v-659.84c0-13.888 11.264-25.152 25.152-25.152h488.768v-75.456H111.744A100.544 100.544 0 0 0 11.2 53.12V712.96c0 55.552 44.992 100.544 100.544 100.544h817.024z m-216.832-571.84c0 15.616 12.672 28.288 28.288 28.288h62.848v69.12h-62.848c-53.76 0-97.408-43.648-97.408-97.408v-62.848h69.12v62.848z m153.984 28.288h62.848c15.616 0 28.224-12.672 28.224-28.288v-62.848h69.12v62.848c0 53.76-43.52 97.408-97.344 97.408h-62.848v-69.12z m-153.984-216.832v62.848h-69.12v-62.848c0-53.76 43.584-97.408 97.408-97.408h62.848v69.12h-62.848a28.288 28.288 0 0 0-28.288 28.288z m245.12 62.848v-62.848a28.288 28.288 0 0 0-28.288-28.288h-62.848v-69.12h62.848c53.76 0 97.408 43.584 97.408 97.408v62.848h-69.12z m-418.304 258.56a31.36 31.36 0 0 1-0.256 2.56l-19.2 154.048-62.4-7.808 9.92-79.104L260.416 604.8l-38.592-49.6L428.16 394.56l-79.104-9.856 7.808-62.4 152.96 19.136a31.36 31.36 0 0 1 28.928 32.96z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_theme_colors_b" unicode="&#59150;" d="M454.4 722.432L388.096 788.8l54.336 54.336 492.288-492.288-425.92-425.92-425.856 425.92L454.4 722.432z m54.4-54.336L191.424 350.848l317.248-317.312 317.312 317.312-317.312 317.248zM880.32 350.848L508.8-20.736 137.216 350.848l53.056 53.12h636.992l53.12-53.12zM1033.152 166.592a76.416 76.416 0 1 0-152.832 0c0 42.24 76.416 131.584 76.416 131.584s76.416-89.408 76.416-131.584z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_delay_b1" unicode="&#59147;" d="M487.04 676.032a64 64 0 0 0 108.224-0.192l324.544-515.456a64 64 0 0 0-54.144-98.112H213.76a64 64 0 0 0-54.016 98.304l327.296 515.456z m54.08-34.304L213.76 126.272h651.84L541.12 641.728z m30.528-165.12v-203.456h-64v203.52h64z m0-309.376v52.992h-64v-52.992h64z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_collapse_b1" unicode="&#59148;" d="M160 769.088a102.4 102.4 0 0 1-102.4-102.4v-565.376a102.4 102.4 0 0 1 102.4-102.4h704a102.4 102.4 0 0 1 102.4 102.4V666.688a102.4 102.4 0 0 1-102.4 102.4h-704z m-25.6-102.4c0 14.08 11.52 25.6 25.6 25.6h244.288v-616.576H160a25.6 25.6 0 0 0-25.6 25.6V666.688z m346.688-590.976V692.288H864c14.08 0 25.6-11.52 25.6-25.6v-565.376a25.6 25.6 0 0 0-25.6-25.6H481.088z"  horiz-adv-x="1024" />
+      
+      <glyph glyph-name="icon_cancel_b2" unicode="&#59149;" d="M201.152 743.936c0 14.08 11.52 25.6 25.6 25.6h554.56c14.08 0 25.6-11.52 25.6-25.6v-458.24h76.8v458.24a102.4 102.4 0 0 1-102.4 102.4H226.752a102.4 102.4 0 0 1-102.4-102.4v-704a102.4 102.4 0 0 1 102.4-102.4h277.312v76.8H226.752a25.6 25.6 0 0 0-25.6 25.6v704z m428.224-497.728l107.968-107.968 107.968 107.904 54.336-54.272-107.968-107.968 107.968-107.968-54.336-54.272-107.968 107.968-107.968-108.032-54.336 54.336 108.032 107.968-108.032 107.968 54.336 54.336z"  horiz-adv-x="1024" />
+      
+      <glyph glyph-name="icon_ai_api_log_b" unicode="&#59146;" d="M305.28 659.84h429.952v76.8H305.28v-76.8z m-51.84-513.28h532.544V543.36H253.44v-396.864zM176.64 556.16a64 64 0 0 0 64 64h558.144a64 64 0 0 0 64-64v-422.464a64 64 0 0 0-64-64H240.64a64 64 0 0 0-64 64V556.16z m164.8-262.4v102.4h128v-102.4h-128z m241.728 102.4v-102.4h128v102.4h-128z m313.408-136V430.272h76.8v-170.176h-76.8z m-832 170.112v-170.176h76.8V430.336h-76.8z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_collapse__sidebar_b" unicode="&#59144;" d="M142.08 769.088a38.4 38.4 0 0 1-38.4-38.4v-693.376a38.4 38.4 0 0 1 38.4-38.4h832a38.4 38.4 0 0 1 38.4 38.4V730.688a38.4 38.4 0 0 1-38.4 38.4h-832z m38.4-693.376V692.288h269.824v-616.576H180.48z m346.624 0V692.288h408.512v-616.576H527.104z"  horiz-adv-x="1088" />
+      
+      <glyph glyph-name="icon_dashboard_title_b" unicode="&#59145;" d="M863.2064 384c0-183.808-148.992-332.8-332.8-332.8s-332.8 148.992-332.8 332.8 148.992 332.8 332.8 332.8 332.8-148.992 332.8-332.8z m-332.8-1.6128l262.3232 60.4416C765.952 562.9952 658.6624 652.8 530.432 652.8v-270.4128z"  horiz-adv-x="1049" />
+      
       <glyph glyph-name="icon_collapse_b" unicode="&#59143;" d="M199.111111 726.300444a91.022222 91.022222 0 0 1-91.022222-91.022222v-502.556444a91.022222 91.022222 0 0 1 91.022222-91.022222h625.777778a91.022222 91.022222 0 0 1 91.022222 91.022222V635.278222a91.022222 91.022222 0 0 1-91.022222 91.022222h-625.777778z m-22.755555-91.022222c0 12.515556 10.24 22.755556 22.755555 22.755556h217.144889v-548.067556H199.111111a22.755556 22.755556 0 0 0-22.755555 22.755556V635.278222z m308.167111-525.312V658.033778H824.888889c12.515556 0 22.755556-10.24 22.755555-22.755556v-502.556444a22.755556 22.755556 0 0 0-22.755555-22.755556H484.522667z"  horiz-adv-x="1024" />
       
       <glyph glyph-name="icon_cancel_b1" unicode="&#59142;" d="M235.690667 704c0 12.515556 10.24 22.755556 22.755555 22.755556h492.942222c12.515556 0 22.755556-10.24 22.755556-22.755556v-407.324444h68.266667v407.324444a91.022222 91.022222 0 0 1-91.022223 91.022222H258.446222a91.022222 91.022222 0 0 1-91.022222-91.022222v-625.777778a91.022222 91.022222 0 0 1 91.022222-91.022222h246.499556v68.266667H258.446222a22.755556 22.755556 0 0 0-22.755555 22.755555v625.777778z m380.643555-442.424889l95.971556-96.028444 95.971555 95.971555 48.241778-48.241778-95.914667-95.971555 95.971556-95.971556-48.298667-48.241777-95.971555 95.914666-95.971556-95.971555-48.298666 48.298666 96.028444 95.971556-96.028444 95.971555 48.298666 48.298667z"  horiz-adv-x="1024" />

BIN
src/styles/icons/iconfont.ttf


BIN
src/styles/icons/iconfont.woff


BIN
src/styles/icons/iconfont.woff2


+ 14 - 2
src/styles/theme.scss

@@ -76,8 +76,6 @@
   --color-border: #eaebed;
   --color-select-border: #eaebed;
   --border-color-2: #eaebed;
-  --color-border-1: #e8eaee;
-  --color-border-2: #eaebed;
 
   --color-mune-active-bg: #fdf5f1;
 
@@ -282,6 +280,13 @@
   --color-system-border-1: #e8eaee;
   --color-system-input-border: #e8eaee;
   --color-personal-preference-bg: #f5f7fa;
+
+  --color-ai-chat-header-bg-gradient-begin: #eaecff;
+  --color-ai-chat-header-bg-gradient-end: #fefdff;
+  --color-ai-user-bubble-bg-gradient-begin: #ffede6;
+  --color-ai-user-bubble-bg-gradient-end: #f2f4f7;
+  --input-border: #eaebed;
+  --color-pause-btn-bg: #fff1e6;
 }
 
 :root.dark {
@@ -456,4 +461,11 @@
       fill: var(--color-white);
     }
   }
+
+  --color-ai-chat-header-bg-gradient-begin: #484f82;
+  --color-ai-chat-header-bg-gradient-end: #31363d;
+  --color-ai-user-bubble-bg-gradient-begin: #716763;
+  --color-ai-user-bubble-bg-gradient-end: #5c6a7d;
+  --input-border: #656f7d;
+  --color-pause-btn-bg: #453b36;
 }

+ 6 - 4
src/utils/tools.ts

@@ -15,7 +15,8 @@ export const formatTimezone = (time: string, timezone?: string) => {
       return formattedTime
     }
     let utcOffset = ''
-    const timeZoneOffset = moment.tz(`${moment().year()}-01-01`, timezone).format('Z')
+
+    const timeZoneOffset = moment.tz(time, timezone).format('Z')
     // 替换 "+07:00" 为 "UTC+07"
     utcOffset = `(UTC${timeZoneOffset.slice(0, 3)})`
     return `${formattedTime} ${utcOffset}`
@@ -30,9 +31,10 @@ export const formatTimezone = (time: string, timezone?: string) => {
  * @param timezone
  * @returns
  */
-export const getTimezone = (timezone: string): string => {
+export const getTimezone = (timezone: string, time?: string): string => {
   if (!timezone) return ''
-  const offset = moment.tz(`${moment().year()}-01-01`, timezone).format('Z')
+  const computedTime = time ? time : moment(time).format(formatString.value)
+  const offset = moment.tz(computedTime, timezone).format('Z')
   return `UTC${offset.slice(0, 3)}`
 }
 
@@ -42,7 +44,7 @@ export const formatTimezoneByUTCorGMT = (time: string, timezone: string) => {
   formattedTime = moment(time).format(`${formatString.value} hh:mm A`)
   let gmtOffset = ''
   if (timezone != null) {
-    const timeZoneOffset = moment().tz(timezone).format('Z')
+    const timeZoneOffset = moment.tz(time, timezone).format('Z')
     // 替换 "+07:00" 为 "GMT+07"
     if (timezone.includes('Seoul')) {
       gmtOffset = `(UTC${timeZoneOffset.slice(0, 3)})`

+ 1 - 0
src/views/AIRobotChat/index.ts

@@ -0,0 +1 @@
+export { default } from './src/AIRobotChat.vue'

+ 455 - 0
src/views/AIRobotChat/src/AIRobotChat.vue

@@ -0,0 +1,455 @@
+<script setup lang="ts">
+import AutoResizeTextarea from './components/AutoResizeTextarea.vue'
+import userBubbleLight from './image/userBubbleLight.png'
+import userBubbleDark from './image/userBubbleDark.png'
+import robotBubbleLight from './image/robotBubbleLight.png'
+import robotBubbleDark from './image/robotBubbleDark.png'
+import { useThemeStore } from '@/stores/modules/theme'
+
+const themeStore = useThemeStore()
+const modalSize = ref('large')
+const userQuestion = ref()
+
+const isFooterInputFocus = ref(false)
+
+const userBubbleImg = computed(() => {
+  return themeStore.theme === 'light' ? userBubbleLight : userBubbleDark
+})
+const robotBubbleImg = computed(() => {
+  return themeStore.theme === 'light' ? robotBubbleLight : robotBubbleDark
+})
+
+interface MessageItem {
+  type: 'robot' | 'user'
+  content: string
+  feedback?: 'good' | 'noGood' | '' // 反馈结果
+  isShowFeedback?: boolean // 是否展示反馈样式
+  isAnswer?: boolean // 是否为用户问题的答案,是则才能展示反馈组件
+}
+const messages = ref<MessageItem[]>([
+  {
+    type: 'robot',
+    content: 'You can click on Frequently Asked Questions above or type your own question'
+  },
+  {
+    type: 'user',
+    content: 'Hi! I am your Freight Assistant. How can I help you?'
+  },
+  {
+    type: 'robot',
+    feedback: '',
+    isShowFeedback: false,
+    isAnswer: true,
+    content: 'Can you help me with my shipment?'
+  },
+  {
+    type: 'user',
+    content: 'Of course! Please provide me with the details of your shipment.'
+  }
+])
+
+// 用户问题请求时间
+const queryTime = ref(-1)
+// 当前用户问题回复进度
+const progressStatus = {
+  init: 'You can click on Frequently Asked Questions above or type your own question',
+  '0': 'Thinking about your question...',
+  '15': 'Searching for relevant data, please wait...',
+  '30': 'This query is complex and may take more time',
+  '60': 'You may try simplifying your question or selecting a Frequently Asked Question',
+  '120': 'Sorry, the query failed. Please try again later or select a Frequently Asked Question',
+  cancel: 'You have stopped this answer'
+}
+
+const progressInterval = ref()
+const handleSend = () => {
+  if (!userQuestion.value) return
+
+  messages.value.push({
+    type: 'user',
+    content: userQuestion.value
+  })
+  userQuestion.value = ''
+  queryTime.value = 0
+  messages.value.push({
+    type: 'robot',
+    content: progressStatus[0]
+  })
+  progressInterval.value = setInterval(() => {
+    queryTime.value++
+  }, 1000)
+}
+
+// 根据时间更新消息内容
+const updateMessageContent = (time) => {
+  const lastMessageIndex = messages.value.length - 1
+
+  // 确保消息数组不为空
+  if (lastMessageIndex >= 0) {
+    messages.value[lastMessageIndex].content = progressStatus[time]
+    if (time === 120) {
+      clearInterval(progressInterval.value)
+      queryTime.value = -3
+    }
+  }
+}
+watch(
+  () => queryTime.value,
+  (newVal) => {
+    // 定义时间点与对应状态的映射
+    const timeToStatusMap = {
+      15: 15,
+      30: 30,
+      60: 60,
+      120: 120
+    }
+
+    // 如果当前时间点在映射中,更新消息内容
+    if (timeToStatusMap[newVal] !== undefined) {
+      updateMessageContent(timeToStatusMap[newVal])
+    }
+  }
+)
+// 暂停回答
+const handlePause = () => {
+  clearInterval(progressInterval.value)
+  queryTime.value = -2
+  messages.value[messages.value.length - 1].content = progressStatus.cancel
+}
+
+const emit = defineEmits(['close'])
+// 关闭聊天窗口
+const handleClose = () => {
+  progressInterval.value && clearInterval(progressInterval.value)
+  emit('close')
+}
+</script>
+
+<template>
+  <div class="ai-robot" :style="{ width: modalSize === 'large' ? '1000px' : '484px' }">
+    <div class="top-section">
+      <div class="header">
+        <span class="welcome">Hi! I'm your Freight Assistant</span>
+        <div class="option-icon">
+          <span
+            v-if="modalSize === 'large'"
+            class="font_family icon-icon_sidebar__window_b"
+            @click="modalSize = 'small'"
+          ></span>
+          <span
+            v-else-if="modalSize !== 'large'"
+            class="font_family icon-icon_maximized__window_b"
+            @click="modalSize = 'large'"
+          ></span>
+          <span @click="handleClose" class="font_family icon-icon_collapsed__to_widget_b"></span>
+        </div>
+      </div>
+    </div>
+    <div class="chat-messages" ref="messagesRef">
+      <div
+        class="message-item"
+        :class="[
+          msg.type === 'user' ? 'user-bubble' : 'robot-bubble',
+          ((queryTime > -1 && queryTime < 120) || queryTime === -2) && index === messages.length - 1
+            ? 'query-style'
+            : ''
+        ]"
+        v-for="(msg, index) in messages"
+        :key="index"
+        @mouseenter="msg.isShowFeedback = true"
+        @mouseleave="msg.isShowFeedback = false"
+      >
+        <!-- 请求失败后的提示icon -->
+        <span
+          v-if="queryTime === -3 && index === messages.length - 1"
+          class="font_family icon-icon_warning_fill_b"
+          style="margin-top: 1px; color: #c9353f"
+        ></span>
+        <!-- loading icon -->
+        <img
+          class="loading-img"
+          v-if="queryTime > -1 && queryTime < 120 && index === messages.length - 1"
+          src="./image/icon_loading.png"
+          alt=""
+        />
+        {{ msg.content }}
+        <div class="review" v-if="msg.isShowFeedback && msg.isAnswer">
+          <el-button
+            v-if="msg.feedback !== 'good'"
+            class="el-button--text"
+            @click="msg.feedback = 'good'"
+          >
+            <span class="font_family icon-icon_good_b"></span>
+          </el-button>
+          <div v-if="msg.feedback === 'good'" style="width: 16px; text-align: center">
+            <span
+              style="color: var(--color-theme); font-size: 14px"
+              class="font_family icon-icon_good__filled_b"
+            ></span>
+          </div>
+          <el-button
+            v-if="msg.feedback !== 'noGood'"
+            class="el-button--text"
+            @click="msg.feedback = 'noGood'"
+          >
+            <span class="font_family icon-icon_notgood_b"></span>
+          </el-button>
+          <div v-if="msg.feedback === 'noGood'" style="width: 16px; text-align: center">
+            <span
+              style="color: var(--color-theme); font-size: 14px"
+              class="font_family icon-icon_notgood__filled_b"
+            ></span>
+          </div>
+        </div>
+
+        <img class="robot-bubble-img" v-if="msg.type === 'robot'" :src="robotBubbleImg" alt="" />
+        <img class="user-bubble-img" v-else-if="msg.type === 'user'" :src="userBubbleImg" alt="" />
+        <!-- 暂停回答 icon -->
+        <div
+          class="pause-btn"
+          v-if="index === messages.length - 1 && queryTime > 30 && queryTime < 120"
+          @click="handlePause"
+        >
+          <div class="dot"></div>
+        </div>
+      </div>
+    </div>
+
+    <div class="footer-input" :class="[isFooterInputFocus ? 'focus-style' : '']">
+      <AutoResizeTextarea
+        v-model="userQuestion"
+        :placeholder="'Type your question here...'"
+        @focus="isFooterInputFocus = true"
+        @blur="isFooterInputFocus = false"
+      />
+      <div
+        class="input-icon"
+        :class="[userQuestion ? 'input-style' : 'disable']"
+        @click="handleSend"
+      >
+        <span class="font_family icon-icon_send_b"></span>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.ai-robot {
+  position: absolute;
+  top: 74px;
+  right: 24px;
+  height: calc(100% - 98px);
+  z-index: 4000;
+  display: flex;
+  flex-direction: column;
+  border-radius: 12px;
+  border: 1px solid var(--color-border);
+  box-shadow: 4px 4px 32px 0px rgba(0, 0, 0, 0.2);
+  background-color: var(--color-dialog-body-bg);
+  overflow: hidden;
+  .top-section {
+    height: 150px;
+    background: linear-gradient(
+      to bottom,
+      var(--color-ai-chat-header-bg-gradient-begin) 10%,
+      var(--color-ai-chat-header-bg-gradient-begin) 10%,
+      var(--color-ai-chat-header-bg-gradient-end) 100%
+    );
+    .header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      height: 64px;
+      padding: 0 16px;
+      .welcome {
+        font-size: 18px;
+        font-weight: 700;
+      }
+      .option-icon {
+        display: flex;
+        align-items: center;
+        gap: 6px;
+        .font_family {
+          font-size: 16px;
+          cursor: pointer;
+          &:hover {
+            color: var(--color-theme);
+          }
+        }
+      }
+    }
+  }
+  .chat-messages {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    gap: 16px;
+    margin-top: 81px;
+    padding: 0 16px;
+    overflow: auto;
+    .message-item {
+      position: relative;
+      display: inline-block;
+      padding: 11px 8px;
+      margin-bottom: 7px;
+      border-radius: 12px;
+      background-color: var(--scoring-bg-color);
+      .review {
+        position: absolute;
+        bottom: -24px;
+        left: 0;
+        display: flex;
+        align-items: center;
+        gap: 13px;
+        width: 100%;
+        height: 30px;
+        margin-top: 10px;
+        padding-left: 30px;
+        padding-top: 10px;
+
+        button.el-button + .el-button {
+          margin-left: 0px;
+        }
+      }
+      .review-input-card {
+        margin-top: 6px;
+        padding: 8px;
+        text-align: right;
+        box-shadow: 1px 1px 12px 0px rgba(0, 0, 0, 0.05);
+        border-radius: 6px;
+      }
+
+      .el-button--text {
+        height: 16px;
+        width: 16px;
+        span {
+          color: var(--color-neutral-2);
+          font-size: 14px;
+        }
+        &:hover {
+          span {
+            color: var(--color-theme);
+          }
+        }
+      }
+
+      .loading-img {
+        width: 16px;
+        height: 16px;
+        margin-top: -1px;
+        margin-right: 2px;
+        animation: loading-rotate 2s linear infinite;
+      }
+
+      .pause-btn {
+        position: absolute;
+        right: -22px;
+        top: 13px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        height: 16px;
+        width: 16px;
+        border-radius: 50%;
+        background-color: var(--color-customize-column-right-section-bg);
+        .dot {
+          height: 5px;
+          width: 5px;
+          border-radius: 1px;
+          background-color: var(--color-theme);
+        }
+      }
+    }
+    .query-style {
+      color: #b5b9bf;
+    }
+    .robot-bubble {
+      background: var(--scoring-bg-color);
+      align-self: flex-start;
+      .robot-bubble-img {
+        position: absolute;
+        left: -1px;
+        bottom: -7px;
+      }
+    }
+    .user-bubble {
+      align-self: flex-end;
+      background: linear-gradient(
+        to right,
+        var(--color-ai-user-bubble-bg-gradient-begin),
+        var(--color-ai-user-bubble-bg-gradient-end)
+      );
+      .user-bubble-img {
+        position: absolute;
+        right: 0;
+        bottom: -7px;
+      }
+    }
+  }
+  .footer-input {
+    display: flex;
+    align-items: flex-end;
+    gap: 12px;
+    padding: 4px 12px;
+    padding-right: 4px;
+    margin: 12px 16px;
+    border: 1px solid var(--input-border);
+    border-radius: 20px;
+    box-sizing: border-box;
+    .input-icon {
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      height: 32px;
+      width: 32px;
+      padding: 1px 0 0 2px;
+      border-radius: 50%;
+      cursor: pointer;
+      &.disable {
+        cursor: not-allowed;
+      }
+    }
+    .input-style {
+      background-color: var(--color-theme);
+      span {
+        color: #fff;
+      }
+      &:hover {
+        background-color: #d56200;
+      }
+    }
+    &.focus-style {
+      border: 1px solid var(--color-theme);
+    }
+  }
+
+  // .input-area {
+  //   width: 100%;
+  //   font-size: 14px;
+  //   line-height: 21px;
+  //   padding: 4px;
+  //   resize: none;
+  //   overflow-y: hidden; // 默认不显示滚动条
+  //   height: 40px; // 初始高度(1 行)
+  //   max-height: 100px; // 最多 4 行
+  //   box-sizing: border-box;
+  //   border: none;
+  //   outline-color: #fff;
+  //   border-radius: 8px;
+  //   transition: height 0.1s ease;
+  //   &::placeholder {
+  //     color: #b5b9bf;
+  //     opacity: 1;
+  //   }
+  // }
+  @keyframes loading-rotate {
+    0% {
+      transform: rotate(0deg);
+    }
+
+    100% {
+      transform: rotate(360deg);
+    }
+  }
+}
+</style>

+ 70 - 0
src/views/AIRobotChat/src/components/AutoResizeTextarea.vue

@@ -0,0 +1,70 @@
+<script setup lang="ts">
+const inputVModel = defineModel({ type: String })
+const props = defineProps<{
+  placeholder?: string
+}>()
+
+watch(
+  () => inputVModel.value,
+  () => {
+    textareaRef.value.style.height = '30px' // 重置高度
+  }
+)
+const textareaRef = ref(null)
+// 实现自适应高度(最多 4 行)
+const resize = () => {
+  const el = textareaRef.value
+  if (!el) return
+
+  el.style.height = 'auto' // 先清空旧高度
+  const scrollHeight = el.scrollHeight
+
+  const maxHeight = 92 // 四行时高度
+
+  if (scrollHeight <= maxHeight) {
+    el.style.overflowY = 'hidden'
+    el.style.height = scrollHeight + 'px'
+  } else {
+    el.style.overflowY = 'auto'
+    el.style.height = maxHeight + 'px'
+  }
+}
+const emit = defineEmits(['focus', 'blur'])
+</script>
+
+<template>
+  <textarea
+    ref="textareaRef"
+    v-model="inputVModel"
+    class="input-area"
+    rows="1"
+    :placeholder="props.placeholder"
+    @input="resize"
+    @focus="emit('focus')"
+    @blur="emit('blur')"
+  />
+</template>
+
+<style lang="scss" scoped>
+.input-area {
+  width: 100%;
+  font-size: 14px;
+  line-height: 21px;
+  padding: 4px;
+  resize: none;
+  overflow-y: hidden; // 默认不显示滚动条
+  height: 30px; // 初始高度(1 行)
+  max-height: 92px; // 最多 4 行
+  box-sizing: border-box;
+  border: none;
+  outline-color: transparent;
+  outline: none;
+  background-color: transparent;
+  border-radius: 8px;
+  transition: height 0.1s ease;
+  &::placeholder {
+    color: #b5b9bf;
+    opacity: 1;
+  }
+}
+</style>

BIN
src/views/AIRobotChat/src/image/icon_loading.png


BIN
src/views/AIRobotChat/src/image/robotBubbleDark.png


BIN
src/views/AIRobotChat/src/image/robotBubbleLight.png


BIN
src/views/AIRobotChat/src/image/userBubbleDark.png


BIN
src/views/AIRobotChat/src/image/userBubbleLight.png


+ 11 - 0
src/views/Layout/src/LayoutView.vue

@@ -5,6 +5,7 @@ import Menu from './components/Menu/MenuView.vue'
 import Logo from './images/logo.png'
 import ScoringGrade from '@/components/ScoringGrade'
 import AIRobot from '@/components/AIRobot'
+import AIRobotChat from '@/views/AIRobotChat/src/AIRobotChat.vue'
 import LogoMenu from './images/logo_menu.png'
 
 const leftAsideWidth = ref('232px')
@@ -13,6 +14,14 @@ const handleMenuCollapse = (val: boolean) => {
   isCollapse.value = val
   val ? (leftAsideWidth.value = '64px') : (leftAsideWidth.value = '232px')
 }
+
+const isShowAIRobotChat = ref(false)
+const handleColseRobotChat = () => {
+  isShowAIRobotChat.value = false
+}
+const onClick = () => {
+  isShowAIRobotChat.value = true
+}
 </script>
 <template>
   <el-container class="layout-container">
@@ -32,6 +41,7 @@ const handleMenuCollapse = (val: boolean) => {
 
     <!-- 右侧整体布局 -->
     <el-container style="min-width: 900px">
+      <el-button @click="onClick">测试</el-button>
       <!-- 顶部Header -->
       <el-header class="layout-header">
         <Header></Header>
@@ -43,6 +53,7 @@ const handleMenuCollapse = (val: boolean) => {
       </el-main>
     </el-container>
     <!-- <AIRobot></AIRobot> -->
+    <AIRobotChat v-if="isShowAIRobotChat" @close="handleColseRobotChat"></AIRobotChat>
     <ScoringGrade></ScoringGrade>
   </el-container>
 </template>

+ 5 - 2
src/views/Layout/src/components/Header/components/TrainingCard.vue

@@ -27,7 +27,7 @@ const pollingNewMessage = () => {
     getNotificationList(dayjs().format('MM/DD/YYYY HH:mm:ss'))
   }, 300000)
 }
-pollingNewMessage()
+userStore.isLogin && pollingNewMessage()
 
 // 登录后自动轮播消息
 const trainingCardAfterLogin = () => {
@@ -60,6 +60,7 @@ const nextNotification = () => {
 
 // 轮询时的轮播定时器
 const initTrainingCard = () => {
+  // 类型为event时才设置定时器,不为event类型时需要手动关闭消息
   if (curCard.value?.notificationType === 'event') {
     trainingIntervalId = setInterval(nextNotification, 2000)
   }
@@ -147,7 +148,6 @@ const closeMessage = () => {
   z-index: 2300;
   width: 432px;
   padding: 16px;
-  padding-bottom: 0;
   background-color: var(--color-dialog-body-bg);
   border-radius: 12px;
   box-shadow: 4px 4px 32px 0px rgba(0, 0, 0, 0.2);
@@ -161,5 +161,8 @@ const closeMessage = () => {
     cursor: pointer;
     z-index: 3000;
   }
+  :deep(.notification-card) {
+    margin-bottom: 0px;
+  }
 }
 </style>

+ 52 - 50
vite.config.ts

@@ -1,5 +1,5 @@
 import { fileURLToPath, URL } from 'node:url'
-import { defineConfig } from 'vite'
+import { defineConfig, loadEnv } from 'vite'
 import vue from '@vitejs/plugin-vue'
 import AutoImport from 'unplugin-auto-import/vite'
 import Components from 'unplugin-vue-components/vite'
@@ -8,56 +8,58 @@ import Icons from 'unplugin-icons/vite'
 import IconsResolver from 'unplugin-icons/resolver'
 
 // https://vitejs.dev/config/
-export default defineConfig({
-  base: `/`,
-  // base: `/k_new_online/`,
-  resolve: {
-    alias: {
-      '@': fileURLToPath(new URL('./src', import.meta.url))
-    }
-  },
-  plugins: [
-    vue(),
-    AutoImport({
-      resolvers: [
-        ElementPlusResolver(),
-        IconsResolver({
-          prefix: 'Icon'
-        })
-      ],
-      imports: [
-        'vue',
-        // 自定义全局引入
-        {
-          // 全局引入"src/api/index.ts"的`default`导出,注册为全局变量`api`
-          // 相当于在每个ts/vue文件中执行了一次 `import api from "@/api/index"`;
-          '@/api/index': [['default', '$api']]
+export default defineConfig((mode) => {
+  const env = loadEnv(mode, process.cwd())
+  return {
+    base: env.VITE_BASE_URL || '/',
+    resolve: {
+      alias: {
+        '@': fileURLToPath(new URL('./src', import.meta.url))
+      }
+    },
+    plugins: [
+      vue(),
+      AutoImport({
+        resolvers: [
+          ElementPlusResolver(),
+          IconsResolver({
+            prefix: 'Icon'
+          })
+        ],
+        imports: [
+          'vue',
+          // 自定义全局引入
+          {
+            // 全局引入"src/api/index.ts"的`default`导出,注册为全局变量`api`
+            // 相当于在每个ts/vue文件中执行了一次 `import api from "@/api/index"`;
+            '@/api/index': [['default', '$api']]
+          }
+        ],
+        dts: './src/auto-imports.d.ts'
+      }),
+      Components({
+        resolvers: [
+          ElementPlusResolver(), // 自动注册图标组件
+          IconsResolver({
+            enabledCollections: ['ep']
+          })
+        ]
+      }),
+      Icons({
+        autoInstall: true
+      })
+    ],
+    server: {
+      port: 80,
+      hmr: true,
+      open: true,
+      // 设置 https 代理
+      proxy: {
+        '/api': {
+          target: 'http://192.168.0.161',
+          changeOrigin: true,
+          rewrite: (path: string) => path.replace(/^\/api/, '')
         }
-      ],
-      dts: './src/auto-imports.d.ts'
-    }),
-    Components({
-      resolvers: [
-        ElementPlusResolver(), // 自动注册图标组件
-        IconsResolver({
-          enabledCollections: ['ep']
-        })
-      ]
-    }),
-    Icons({
-      autoInstall: true
-    })
-  ],
-  server: {
-    port: 80,
-    hmr: true,
-    open: true,
-    // 设置 https 代理
-    proxy: {
-      '/api': {
-        target: 'http://192.168.0.161',
-        changeOrigin: true,
-        rewrite: (path: string) => path.replace(/^\/api/, '')
       }
     }
   }

Некоторые файлы не были показаны из-за большого количества измененных файлов