zhangwencui
2026-04-28 30a08223fbdcb3fa7b7d1ab3a0ad93e4bc949aeb
Merge branch 'dev_NEW_pro' of http://114.132.189.42:9002/r/product-inventory-management into dev_NEW_pro
已添加1个文件
已修改1个文件
177 ■■■■■ 文件已修改
src/components/AIChatSidebar/index.vue 153 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/PurchaseAIChatSidebar/index.vue 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/AIChatSidebar/index.vue
@@ -2,9 +2,9 @@
  <div class="ai-chat-sidebar-wrapper">
    <!-- æ‚¬æµ®å›¾æ ‡ -->
    <div class="ai-chat-trigger" @click="toggleSidebar" v-show="!visible">
      <el-tooltip content="AI åŠ©æ‰‹" placement="left">
      <el-tooltip :content="currentAssistant.tooltip" placement="left">
        <div class="trigger-icon">
          <el-icon :size="30" color="#fff"><Cpu /></el-icon>
          <el-icon :size="30" color="#fff"><component :is="currentAssistant.icon" /></el-icon>
        </div>
      </el-tooltip>
    </div>
@@ -25,8 +25,19 @@
      <template #header>
        <div class="drawer-header">
          <div class="header-left">
            <el-icon :size="20" class="header-icon"><Cpu /></el-icon>
            <span class="title">AI æ™ºèƒ½åŠ©æ‰‹</span>
            <el-icon :size="20" class="header-icon"><component :is="currentAssistant.icon" /></el-icon>
            <span class="title">{{ currentAssistant.title }}</span>
          </div>
          <div v-if="showAssistantSwitch" class="assistant-switcher">
            <el-radio-group v-model="selectedAssistantKey" size="small">
              <el-radio-button
                  v-for="assistant in assistants"
                  :key="assistant.key"
                  :label="assistant.key"
              >
                {{ assistant.label }}
              </el-radio-button>
            </el-radio-group>
          </div>
          <div class="header-actions">
            <el-tooltip content="会话历史" placement="bottom">
@@ -82,7 +93,7 @@
                  <el-icon><Delete /></el-icon>
                </el-button>
              </div>
              <el-empty v-if="sessions.length === 0" description="暂无历史会话" />
              <el-empty v-if="sessions.length === 0" :description="currentAssistant.emptySessionText" />
            </div>
          </el-skeleton>
        </div>
@@ -145,6 +156,7 @@
                <el-icon><VideoPause /></el-icon>停止生成
              </el-button>
              <el-upload
                  v-if="currentAssistant.allowFileUpload"
                  class="file-upload-trigger"
                  action="#"
                  :auto-upload="false"
@@ -167,7 +179,7 @@
                  v-model="inputMessage"
                  type="textarea"
                  :rows="selectedFile ? 2 : 3"
                  placeholder="请输入您的问题... (Enter å‘送, Shift+Enter æ¢è¡Œ)"
                  :placeholder="currentAssistant.placeholder"
                  resize="none"
                  @keydown.enter.exact.prevent="sendMessage"
              />
@@ -191,8 +203,53 @@
import { ref, onMounted, onUnmounted, nextTick, watch, computed } from 'vue'
import request from '@/utils/request'
import * as echarts from 'echarts'
import { Cpu, User, Plus, Loading, Timer, Delete, ChatDotSquare, VideoPause, Upload, Document, Close } from '@element-plus/icons-vue'
import { Cpu, User, Plus, Timer, Delete, ChatDotSquare, VideoPause, Upload, Document, Close, ShoppingCart } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
const props = defineProps({
  assistants: {
    type: Array,
    default: () => []
  },
  defaultAssistant: {
    type: String,
    default: ''
  }
})
const builtInAssistants = [
  {
    key: 'general',
    label: '待办助理',
    title: '待办智能助理',
    tooltip: '待办助手',
    icon: Cpu,
    apiBase: '/xiaozhi',
    storageKey: 'ai_chat_uuid',
    placeholder: '请输入您的问题... (Enter å‘送, Shift+Enter æ¢è¡Œ)',
    welcomeMessage: '你好',
    allowFileUpload: true,
    emptySessionText: '暂无历史会话'
  },
  {
    key: 'purchase',
    label: '采购助理',
    title: '采购智能助理',
    tooltip: '采购智能助理',
    icon: ShoppingCart,
    apiBase: '/purchase-ai',
    storageKey: 'purchase_ai_chat_uuid',
    placeholder: '请输入采购问题... (Enter å‘送, Shift+Enter æ¢è¡Œ)',
    welcomeMessage: '你好',
    allowFileUpload: false,
    emptySessionText: '暂无采购会话'
  }
]
const assistants = computed(() => props.assistants?.length ? props.assistants : builtInAssistants)
const selectedAssistantKey = ref(props.defaultAssistant || assistants.value[0]?.key || 'general')
const currentAssistant = computed(() => assistants.value.find(item => item.key === selectedAssistantKey.value) || assistants.value[0] || builtInAssistants[0])
const showAssistantSwitch = computed(() => assistants.value.length > 1)
const visible = ref(false)
const windowWidth = ref(window.innerWidth)
@@ -227,7 +284,7 @@
const loadSessions = async () => {
  loadingSessions.value = true
  try {
    const res = await request.get('/xiaozhi/history/sessions')
    const res = await request.get(`${currentAssistant.value.apiBase}/history/sessions`)
    if (res.code === 200) {
      sessions.value = res.data || []
    }
@@ -241,11 +298,11 @@
const selectSession = async (session) => {
  showHistory.value = false
  uuid.value = session.memoryId
  localStorage.setItem('ai_chat_uuid', uuid.value)
  localStorage.setItem(currentAssistant.value.storageKey, uuid.value)
  // åŠ è½½ä¼šè¯æ¶ˆæ¯
  try {
    const res = await request.get(`/xiaozhi/history/messages/${uuid.value}`)
    const res = await request.get(`${currentAssistant.value.apiBase}/history/messages/${uuid.value}`)
    if (res.code === 200) {
      disposeCharts()
      messages.value = []
@@ -299,7 +356,7 @@
const handleDeleteSession = async (memoryId) => {
  try {
    const res = await request.delete(`/xiaozhi/history/${memoryId}`)
    const res = await request.delete(`${currentAssistant.value.apiBase}/history/${memoryId}`)
    if (res.code === 200) {
      loadSessions()
      if (uuid.value === memoryId) {
@@ -335,6 +392,26 @@
  window.removeEventListener('resize', handleWindowResize)
})
watch(selectedAssistantKey, (nextKey, prevKey) => {
  if (!prevKey || nextKey === prevKey) return
  if (currentAbortController.value) {
    currentAbortController.value.abort()
    currentAbortController.value = null
  }
  isSending.value = false
  disposeCharts()
  messages.value = []
  outputState.value = {}
  sessions.value = []
  showHistory.value = false
  selectedFile.value = null
  inputMessage.value = ''
  initUUID()
  hello()
})
const handleWindowResize = () => {
  windowWidth.value = window.innerWidth
}
@@ -351,23 +428,26 @@
}
const initUUID = () => {
  let storedUUID = localStorage.getItem('ai_chat_uuid')
  let storedUUID = localStorage.getItem(currentAssistant.value.storageKey)
  if (!storedUUID) {
    storedUUID = Math.random().toString(36).substring(2, 10) + Date.now().toString(36).substring(4)
    localStorage.setItem('ai_chat_uuid', storedUUID)
    localStorage.setItem(currentAssistant.value.storageKey, storedUUID)
  }
  uuid.value = storedUUID
}
const hello = () => {
  sendRequest('你好')
  sendRequest(currentAssistant.value.welcomeMessage || '你好')
}
const newChat = () => {
  disposeCharts()
  messages.value = []
  outputState.value = {}
  localStorage.removeItem('ai_chat_uuid')
  sessions.value = []
  showHistory.value = false
  selectedFile.value = null
  localStorage.removeItem(currentAssistant.value.storageKey)
  initUUID()
  hello()
}
@@ -527,7 +607,7 @@
    formData.append('message', message.trim())
  }
  request.post('/xiaozhi/analyze-file', formData, {
  request.post(`${currentAssistant.value.apiBase}/analyze-file`, formData, {
    headers: {
      'Content-Type': 'multipart/form-data'
    },
@@ -642,7 +722,7 @@
  scrollToBottom()
  request.post('/xiaozhi/chat',
  request.post(`${currentAssistant.value.apiBase}/chat`,
      { memoryId: uuid.value, message },
      {
        signal: currentAbortController.value.signal,
@@ -1297,6 +1377,45 @@
      }
    }
  }
  .assistant-switcher {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 1;
    padding: 0 12px;
    position: relative;
    z-index: 1;
    :deep(.el-radio-group) {
      display: flex;
      gap: 6px;
      flex-wrap: wrap;
      justify-content: center;
    }
    :deep(.el-radio-button__inner) {
      border-radius: 999px;
      border: 1px solid rgba(255, 255, 255, 0.18);
      background: rgba(255, 255, 255, 0.12);
      color: rgba(255, 255, 255, 0.86);
      box-shadow: none;
      padding: 7px 14px;
      font-weight: 500;
    }
    :deep(.el-radio-button:first-child .el-radio-button__inner),
    :deep(.el-radio-button:last-child .el-radio-button__inner) {
      border-radius: 999px;
    }
    :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) {
      background: #fff;
      color: $primary-blue;
      border-color: #fff;
      box-shadow: 0 6px 14px rgba(0, 40, 120, 0.16);
    }
  }
}
@keyframes headerGlow {
src/components/PurchaseAIChatSidebar/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
<template>
  <AIChatSidebar :assistants="assistants" default-assistant="purchase" />
</template>
<script setup>
import { ShoppingCart } from '@element-plus/icons-vue'
import AIChatSidebar from '@/components/AIChatSidebar/index.vue'
const assistants = [
  {
    key: 'purchase',
    label: '采购助理',
    title: '采购智能助理',
    tooltip: '采购智能助理',
    icon: ShoppingCart,
    apiBase: '/purchase-ai',
    storageKey: 'purchase_ai_chat_uuid',
    placeholder: '请输入采购问题... (Enter å‘送, Shift+Enter æ¢è¡Œ)',
    welcomeMessage: '你好',
    allowFileUpload: false,
    emptySessionText: '暂无采购会话'
  }
]
</script>