diff --git a/package.json b/package.json
index b450bf69..c8dab2b8 100644
--- a/package.json
+++ b/package.json
@@ -6,17 +6,17 @@
   "private": false,
   "scripts": {
     "i": "pnpm install",
-    "dev": "vite",
+    "dev": "vite --mode env.local",
     "dev-server": "vite --mode dev",
     "ts:check": "vue-tsc --noEmit",
     "build:local": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build",
-    "build:dev": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode local-dev",
+    "build:dev": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode dev",
     "build:test": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode test",
     "build:stage": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode stage",
     "build:prod": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode prod",
     "serve:dev": "vite preview --mode dev",
     "serve:prod": "vite preview --mode prod",
-    "preview": "pnpm build:local-dev && vite preview",
+    "preview": "pnpm build:local && vite preview",
     "clean": "npx rimraf node_modules",
     "clean:cache": "npx rimraf node_modules/.cache",
     "lint:eslint": "eslint --fix --ext .js,.ts,.vue ./src",
@@ -29,6 +29,7 @@
     "@form-create/designer": "^3.1.3",
     "@form-create/element-ui": "^3.1.24",
     "@iconify/iconify": "^3.1.1",
+    "@microsoft/fetch-event-source": "^2.0.1",
     "@videojs-player/vue": "^1.0.0",
     "@vueuse/core": "^10.9.0",
     "@wangeditor/editor": "^5.1.23",
@@ -51,6 +52,7 @@
     "highlight.js": "^11.9.0",
     "jsencrypt": "^3.3.2",
     "lodash-es": "^4.17.21",
+    "marked": "^12.0.2",
     "min-dash": "^4.1.1",
     "mitt": "^3.0.1",
     "nprogress": "^0.2.0",
diff --git a/src/api/ai/chat/conversation/index.ts b/src/api/ai/chat/conversation/index.ts
new file mode 100644
index 00000000..683646ea
--- /dev/null
+++ b/src/api/ai/chat/conversation/index.ts
@@ -0,0 +1,54 @@
+import request from '@/config/axios'
+
+// AI 聊天会话 VO
+export interface ChatConversationVO {
+  id: number // ID 编号
+  userId: number // 用户编号
+  title: string // 会话标题
+  pinned: boolean // 是否置顶
+  roleId: number // 角色编号
+  modelId: number // 模型编号
+  model: string // 模型标志
+  temperature: number // 温度参数
+  maxTokens: number // 单条回复的最大 Token 数量
+  maxContexts: number // 上下文的最大 Message 数量
+  updateTime: number // 更新时间
+  // 额外字段
+  modelName?: string // 模型名字
+  roleAvatar?: string // 角色头像
+  modelMaxTokens?: string // 模型的单条回复的最大 Token 数量
+  modelMaxContexts?: string // 模型的上下文的最大 Message 数量
+}
+
+// AI 聊天会话 API
+export const ChatConversationApi = {
+  // 获得【我的】聊天会话
+  getChatConversationMy: async (id: number) => {
+    return await request.get({ url: `/ai/chat/conversation/get-my?id=${id}` })
+  },
+
+  // 新增【我的】聊天会话
+  createChatConversationMy: async (data?: ChatConversationVO) => {
+    return await request.post({ url: `/ai/chat/conversation/create-my`, data })
+  },
+
+  // 更新【我的】聊天会话
+  updateChatConversationMy: async (data: ChatConversationVO) => {
+    return await request.put({ url: `/ai/chat/conversation/update-my`, data })
+  },
+
+  // 删除【我的】聊天会话
+  deleteChatConversationMy: async (id: number) => {
+    return await request.delete({ url: `/ai/chat/conversation/delete-my?id=${id}` })
+  },
+
+  // 删除【我的】所有对话,置顶除外
+  deleteMyAllExceptPinned: async () => {
+    return await request.delete({ url: `/ai/chat/conversation/delete-my-all-except-pinned` })
+  },
+
+  // 获得【我的】聊天会话列表
+  getChatConversationMyList: async () => {
+    return await request.get({ url: `/ai/chat/conversation/my-list` })
+  }
+}
diff --git a/src/api/ai/chat/message/index.ts b/src/api/ai/chat/message/index.ts
new file mode 100644
index 00000000..05b4d804
--- /dev/null
+++ b/src/api/ai/chat/message/index.ts
@@ -0,0 +1,67 @@
+import request from '@/config/axios'
+import { fetchEventSource } from '@microsoft/fetch-event-source'
+import { getAccessToken } from '@/utils/auth'
+import { config } from '@/config/axios/config'
+
+// 聊天VO
+export interface ChatMessageVO {
+  id: number // 编号
+  conversationId: number // 会话编号
+  type: string // 消息类型
+  userId: string // 用户编号
+  roleId: string // 角色编号
+  model: number // 模型标志
+  modelId: number // 模型编号
+  content: string // 聊天内容
+  tokens: number // 消耗 Token 数量
+  createTime: Date // 创建时间
+}
+
+export interface ChatMessageSendVO {
+  conversationId: string // 会话编号
+  content: number // 聊天内容
+}
+
+// AI chat 聊天
+export const ChatMessageApi = {
+  // 消息列表
+  messageList: async (conversationId: number | null) => {
+    return await request.get({
+      url: `/ai/chat/message/list-by-conversation-id?conversationId=${conversationId}`
+    })
+  },
+
+  // 发送 send stream 消息
+  // TODO axios 可以么? https://apifox.com/apiskills/how-to-create-axios-stream/
+  sendStream: async (
+    conversationId: number,
+    content: string,
+    ctrl,
+    onMessage,
+    onError,
+    onClose
+  ) => {
+    const token = getAccessToken()
+    return fetchEventSource(`${config.base_url}/ai/chat/message/send-stream`, {
+      method: 'post',
+      headers: {
+        'Content-Type': 'application/json',
+        Authorization: `Bearer ${token}`
+      },
+      openWhenHidden: true,
+      body: JSON.stringify({
+        conversationId,
+        content
+      }),
+      onmessage: onMessage,
+      onerror: onError,
+      onclose: onClose,
+      signal: ctrl.signal
+    })
+  },
+
+  // 发送 send 消息
+  delete: async (id: string) => {
+    return await request.delete({ url: `/ai/chat/message/delete?id=${id}` })
+  }
+}
diff --git a/src/api/ai/model/apiKey/index.ts b/src/api/ai/model/apiKey/index.ts
index 47e415e4..ed94836e 100644
--- a/src/api/ai/model/apiKey/index.ts
+++ b/src/api/ai/model/apiKey/index.ts
@@ -12,27 +12,32 @@ export interface ApiKeyVO {
 
 // AI API 密钥 API
 export const ApiKeyApi = {
-  // 查询AI API 密钥分页
+  // 查询 API 密钥分页
   getApiKeyPage: async (params: any) => {
     return await request.get({ url: `/ai/api-key/page`, params })
   },
 
-  // 查询AI API 密钥详情
+  // 获得 API 密钥列表
+  getApiKeySimpleList: async () => {
+    return await request.get({ url: `/ai/api-key/simple-list` })
+  },
+
+  // 查询 API 密钥详情
   getApiKey: async (id: number) => {
     return await request.get({ url: `/ai/api-key/get?id=` + id })
   },
 
-  // 新增AI API 密钥
+  // 新增 API 密钥
   createApiKey: async (data: ApiKeyVO) => {
     return await request.post({ url: `/ai/api-key/create`, data })
   },
 
-  // 修改AI API 密钥
+  // 修改 API 密钥
   updateApiKey: async (data: ApiKeyVO) => {
     return await request.put({ url: `/ai/api-key/update`, data })
   },
 
-  // 删除AI API 密钥
+  // 删除 API 密钥
   deleteApiKey: async (id: number) => {
     return await request.delete({ url: `/ai/api-key/delete?id=` + id })
   }
diff --git a/src/api/ai/model/chatModel/index.ts b/src/api/ai/model/chatModel/index.ts
new file mode 100644
index 00000000..c2ef4c8d
--- /dev/null
+++ b/src/api/ai/model/chatModel/index.ts
@@ -0,0 +1,53 @@
+import request from '@/config/axios'
+
+// AI 聊天模型 VO
+export interface ChatModelVO {
+  id: number // 编号
+  keyId: number // API 秘钥编号
+  name: string // 模型名字
+  model: string // 模型标识
+  platform: string // 模型平台
+  sort: number // 排序
+  status: number // 状态
+  temperature: number // 温度参数
+  maxTokens: number // 单条回复的最大 Token 数量
+  maxContexts: number // 上下文的最大 Message 数量
+}
+
+// AI 聊天模型 API
+export const ChatModelApi = {
+  // 查询聊天模型分页
+  getChatModelPage: async (params: any) => {
+    return await request.get({ url: `/ai/chat-model/page`, params })
+  },
+
+  // 获得聊天模型列表
+  getChatModelSimpleList: async (status?: number) => {
+    return await request.get({
+      url: `/ai/chat-model/simple-list`,
+      params: {
+        status
+      }
+    })
+  },
+
+  // 查询聊天模型详情
+  getChatModel: async (id: number) => {
+    return await request.get({ url: `/ai/chat-model/get?id=` + id })
+  },
+
+  // 新增聊天模型
+  createChatModel: async (data: ChatModelVO) => {
+    return await request.post({ url: `/ai/chat-model/create`, data })
+  },
+
+  // 修改聊天模型
+  updateChatModel: async (data: ChatModelVO) => {
+    return await request.put({ url: `/ai/chat-model/update`, data })
+  },
+
+  // 删除聊天模型
+  deleteChatModel: async (id: number) => {
+    return await request.delete({ url: `/ai/chat-model/delete?id=` + id })
+  }
+}
diff --git a/src/api/ai/model/chatRole/index.ts b/src/api/ai/model/chatRole/index.ts
new file mode 100644
index 00000000..a9fce13c
--- /dev/null
+++ b/src/api/ai/model/chatRole/index.ts
@@ -0,0 +1,80 @@
+import request from '@/config/axios'
+
+// AI 聊天角色 VO
+export interface ChatRoleVO {
+  id: number // 角色编号
+  modelId: number // 模型编号
+  name: string // 角色名称
+  avatar: string // 角色头像
+  category: string // 角色类别
+  sort: number // 角色排序
+  description: string // 角色描述
+  systemMessage: string // 角色设定
+  welcomeMessage: string // 角色设定
+  publicStatus: boolean // 是否公开
+  status: number // 状态
+}
+
+// AI 聊天角色 分页请求 vo
+export interface ChatRolePageReqVO {
+  name?: string // 角色名称
+  category?: string // 角色类别
+  publicStatus: boolean // 是否公开
+  pageNo: number // 是否公开
+  pageSize: number // 是否公开
+}
+
+// AI 聊天角色 API
+export const ChatRoleApi = {
+  // 查询聊天角色分页
+  getChatRolePage: async (params: any) => {
+    return await request.get({ url: `/ai/chat-role/page`, params })
+  },
+
+  // 查询聊天角色详情
+  getChatRole: async (id: number) => {
+    return await request.get({ url: `/ai/chat-role/get?id=` + id })
+  },
+
+  // 新增聊天角色
+  createChatRole: async (data: ChatRoleVO) => {
+    return await request.post({ url: `/ai/chat-role/create`, data })
+  },
+
+  // 修改聊天角色
+  updateChatRole: async (data: ChatRoleVO) => {
+    return await request.put({ url: `/ai/chat-role/update`, data })
+  },
+
+  // 删除聊天角色
+  deleteChatRole: async (id: number) => {
+    return await request.delete({ url: `/ai/chat-role/delete?id=` + id })
+  },
+
+  // ======= chat 聊天
+
+  // 获取 my role
+  getMyPage: async (params: ChatRolePageReqVO) => {
+    return await request.get({ url: `/ai/chat-role/my-page`, params})
+  },
+
+  // 获取角色分类
+  getCategoryList: async () => {
+    return await request.get({ url: `/ai/chat-role/category-list`})
+  },
+
+  // 创建角色
+  createMy: async (data: ChatRoleVO) => {
+    return await request.post({ url: `/ai/chat-role/create-my`, data})
+  },
+
+  // 更新角色
+  updateMy: async (data: ChatRoleVO) => {
+    return await request.put({ url: `/ai/chat-role/update-my`, data})
+  },
+
+  // 删除角色 my
+  deleteMy: async (id: number) => {
+    return await request.delete({ url: `/ai/chat-role/delete-my?id=` + id })
+  },
+}
diff --git a/src/assets/ai/copy-style2.svg b/src/assets/ai/copy-style2.svg
new file mode 100644
index 00000000..2d56a87f
--- /dev/null
+++ b/src/assets/ai/copy-style2.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1715606039621" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4256" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M878.250667 981.333333H375.338667a104.661333 104.661333 0 0 1-104.661334-104.661333V375.338667a104.661333 104.661333 0 0 1 104.661334-104.661334h502.912a104.661333 104.661333 0 0 1 104.661333 104.661334v502.912C981.333333 934.485333 934.485333 981.333333 878.250667 981.333333zM375.338667 364.373333a10.666667 10.666667 0 0 0-10.922667 10.965334v502.912c0 6.229333 4.693333 10.922667 10.922667 10.922666h502.912a10.666667 10.666667 0 0 0 10.922666-10.922666V375.338667a10.666667 10.666667 0 0 0-10.922666-10.922667H375.338667z" fill="#ffffff" p-id="4257"></path><path d="M192.597333 753.322667H147.328A104.661333 104.661333 0 0 1 42.666667 648.661333V147.328A104.661333 104.661333 0 0 1 147.328 42.666667H650.24a104.661333 104.661333 0 0 1 104.618667 104.661333v49.962667c0 26.538667-20.309333 46.848-46.848 46.848a46.037333 46.037333 0 0 1-46.848-46.848V147.328a10.666667 10.666667 0 0 0-10.922667-10.965333H147.328a10.666667 10.666667 0 0 0-10.965333 10.965333V650.24c0 6.229333 4.693333 10.922667 10.965333 10.922667h45.269333c26.538667 0 46.848 20.309333 46.848 46.848 0 26.538667-21.845333 45.312-46.848 45.312z" fill="#ffffff" p-id="4258"></path></svg>
\ No newline at end of file
diff --git a/src/assets/ai/copy.svg b/src/assets/ai/copy.svg
new file mode 100644
index 00000000..f51f8d81
--- /dev/null
+++ b/src/assets/ai/copy.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1715352878351" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1499" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M624.5 786.3c92.9 0 168.2-75.3 168.2-168.2V309c0-92.4-75.3-168.2-168.2-168.2H303.6c-92.4 0-168.2 75.3-168.2 168.2v309.1c0 92.4 75.3 168.2 168.2 168.2h320.9zM178.2 618.1V309c0-69.4 56.1-125.5 125.5-125.5h320.9c69.4 0 125.5 56.1 125.5 125.5v309.1c0 69.4-56.1 125.5-125.5 125.5h-321c-69.4 0-125.4-56.1-125.4-125.5z" p-id="1500" fill="#8a8a8a"></path><path d="M849.8 295.1v361.5c0 102.7-83.6 186.3-186.3 186.3H279.1v42.7h384.4c126.3 0 229.1-102.8 229.1-229.1V295.1h-42.8zM307.9 361.8h312.3c11.8 0 21.4-9.6 21.4-21.4 0-11.8-9.6-21.4-21.4-21.4H307.9c-11.8 0-21.4 9.6-21.4 21.4 0 11.9 9.6 21.4 21.4 21.4zM307.9 484.6h312.3c11.8 0 21.4-9.6 21.4-21.4 0-11.8-9.6-21.4-21.4-21.4H307.9c-11.8 0-21.4 9.6-21.4 21.4 0 11.9 9.6 21.4 21.4 21.4z" p-id="1501" fill="#8a8a8a"></path><path d="M620.2 607.4c11.8 0 21.4-9.6 21.4-21.4 0-11.8-9.6-21.4-21.4-21.4H307.9c-11.8 0-21.4 9.6-21.4 21.4 0 11.8 9.6 21.4 21.4 21.4h312.3z" p-id="1502" fill="#8a8a8a"></path></svg>
\ No newline at end of file
diff --git a/src/assets/ai/delete.svg b/src/assets/ai/delete.svg
new file mode 100644
index 00000000..d2ee18ed
--- /dev/null
+++ b/src/assets/ai/delete.svg
@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1715354120346" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3256" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M907.1 263.7H118.9c-9.1 0-16.4-7.3-16.4-16.4s7.3-16.4 16.4-16.4H907c9.1 0 16.4 7.3 16.4 16.4s-7.3 16.4-16.3 16.4z" fill="#8a8a8a" p-id="3257"></path><path d="M772.5 928.3H257.4c-27.7 0-50.2-22.5-50.2-50.2V247.2c0-9.1 7.3-16.4 16.4-16.4H801c12.1 0 21.9 9.8 21.9 21.9v625.2c0 27.8-22.6 50.4-50.4 50.4zM240 263.7v614.4c0 9.6 7.8 17.4 17.4 17.4h515.2c9.7 0 17.5-7.9 17.5-17.5V263.7H240zM657.4 131.1H368.6c-9.1 0-16.4-7.3-16.4-16.4s7.3-16.4 16.4-16.4h288.7c9.1 0 16.4 7.3 16.4 16.4s-7.3 16.4-16.3 16.4z" fill="#8a8a8a" p-id="3258"></path><path d="M416 754.5c-9.1 0-16.4-7.3-16.4-16.4V517.8c0-9.1 7.3-16.4 16.4-16.4s16.4 7.3 16.4 16.4V738c0.1 9.1-7.3 16.5-16.4 16.5z" fill="#8a8a8a" p-id="3259"></path><path d="M416 465.2c-9.1 0-16.4-7.3-16.4-16.4v-59.4c0-9.1 7.3-16.4 16.4-16.4s16.4 7.3 16.4 16.4v59.4c0.1 9.1-7.3 16.4-16.4 16.4zM604.9 754.5c-9.1 0-16.4-7.3-16.4-16.4v-67.2c0-9.1 7.3-16.4 16.4-16.4s16.4 7.3 16.4 16.4V738c0 9.1-7.3 16.5-16.4 16.5z" fill="#8a8a8a" opacity=".4" p-id="3260"></path><path d="M604.9 619.1c-9.1 0-16.4-7.3-16.4-16.4V389.4c0-9.1 7.3-16.4 16.4-16.4s16.4 7.3 16.4 16.4v213.3c0 9.1-7.3 16.4-16.4 16.4z" fill="#8a8a8a" p-id="3261"></path></svg>
\ No newline at end of file
diff --git a/src/components/MarkdownView/index.vue b/src/components/MarkdownView/index.vue
new file mode 100644
index 00000000..6ecb2ea5
--- /dev/null
+++ b/src/components/MarkdownView/index.vue
@@ -0,0 +1,213 @@
+
+<template>
+  <div v-html="contentHtml"></div>
+</template>
+
+<script setup lang="ts">
+import {marked} from 'marked'
+import 'highlight.js/styles/vs2015.min.css'
+import hljs from 'highlight.js'
+import {ref} from "vue";
+
+// 代码高亮:https://highlightjs.org/
+// 转换 markdown:marked
+
+// marked 渲染器
+const renderer = {
+  code(code, language, c) {
+    const highlightHtml = hljs.highlight(code, {language: language, ignoreIllegals: true}).value
+    const copyHtml = `<div id="copy" data-copy='${code}' style="position: absolute; right: 10px; top: 5px; color: #fff;cursor: pointer;">复制</div>`
+    return `<pre>${copyHtml}<code class="hljs">${highlightHtml}</code></pre>`
+  }
+}
+
+// 配置 marked
+marked.use({
+  renderer: renderer
+})
+
+// 渲染的html内容
+const contentHtml = ref<any>()
+
+// 定义组件属性
+const props = defineProps({
+  content: {
+    type: String,
+    required: true
+  }
+})
+
+// 将 props 变为引用类型
+const { content } = toRefs(props)
+
+// 监听 content 变化
+watch(content, async (newValue, oldValue) => {
+  await renderMarkdown(newValue);
+})
+
+// 渲染 markdown
+const renderMarkdown = async (content: string) => {
+  contentHtml.value = await marked(content)
+}
+
+// 组件挂在时
+onMounted(async ()  => {
+  // 解析转换 markdown
+  await renderMarkdown(props.content as string);
+})
+</script>
+
+
+<style scoped lang="scss">
+.markdown-view {
+  font-family: PingFang SC;
+  font-size: 0.95rem;
+  font-weight: 400;
+  line-height: 1.6rem;
+  letter-spacing: 0em;
+  text-align: left;
+  color: #3b3e55;
+  max-width: 100%;
+
+  pre {
+    position: relative;
+  }
+
+  pre code.hljs {
+    width: auto;
+  }
+
+  code.hljs {
+    border-radius: 6px;
+    padding-top: 20px;
+    width: auto;
+    @media screen and (min-width: 1536px) {
+      width: 960px;
+    }
+
+    @media screen and (max-width: 1536px) and (min-width: 1024px) {
+      width: calc(100vw - 400px - 64px - 32px * 2);
+    }
+
+    @media screen and (max-width: 1024px) and (min-width: 768px) {
+      width: calc(100vw - 32px * 2);
+    }
+
+    @media screen and (max-width: 768px) {
+      width: calc(100vw - 16px * 2);
+    }
+  }
+
+  p,
+  code.hljs {
+    margin-bottom: 16px;
+  }
+
+  p {
+    //margin-bottom: 1rem !important;
+    margin: 0;
+    margin-bottom: 3px;
+  }
+
+  /* 标题通用格式 */
+  h1,
+  h2,
+  h3,
+  h4,
+  h5,
+  h6 {
+    color: var(--color-G900);
+    margin: 24px 0 8px;
+    font-weight: 600;
+  }
+
+  h1 {
+    font-size: 22px;
+    line-height: 32px;
+  }
+
+  h2 {
+    font-size: 20px;
+    line-height: 30px;
+  }
+
+  h3 {
+    font-size: 18px;
+    line-height: 28px;
+  }
+
+  h4 {
+    font-size: 16px;
+    line-height: 26px;
+  }
+
+  h5 {
+    font-size: 16px;
+    line-height: 24px;
+  }
+
+  h6 {
+    font-size: 16px;
+    line-height: 24px;
+  }
+
+  /* 列表(有序,无序) */
+  ul,
+  ol {
+    margin: 0 0 8px 0;
+    padding: 0;
+    font-size: 16px;
+    line-height: 24px;
+    color: #3b3e55; // var(--color-CG600);
+  }
+
+  li {
+    margin: 4px 0 0 20px;
+    margin-bottom: 1rem;
+  }
+
+  ol > li {
+    list-style-type: decimal;
+    margin-bottom: 1rem;
+    // 表达式,修复有序列表序号展示不全的问题
+    // &:nth-child(n + 10) {
+    //     margin-left: 30px;
+    // }
+
+    // &:nth-child(n + 100) {
+    //     margin-left: 30px;
+    // }
+  }
+
+  ul > li {
+    list-style-type: disc;
+    font-size: 16px;
+    line-height: 24px;
+    margin-right: 11px;
+    margin-bottom: 1rem;
+    color: #3b3e55; // var(--color-G900);
+  }
+
+  ol ul,
+  ol ul > li,
+  ul ul,
+  ul ul li {
+    // list-style: circle;
+    font-size: 16px;
+    list-style: none;
+    margin-left: 6px;
+    margin-bottom: 1rem;
+  }
+
+  ul ul ul,
+  ul ul ul li,
+  ol ol,
+  ol ol > li,
+  ol ul ul,
+  ol ul ul > li,
+  ul ol,
+  ul ol > li {
+    list-style: square;
+  }
+}
+</style>
diff --git a/src/components/bpmnProcessDesigner/package/penal/flow-condition/FlowCondition.vue b/src/components/bpmnProcessDesigner/package/penal/flow-condition/FlowCondition.vue
index 345670ae..1715d73b 100644
--- a/src/components/bpmnProcessDesigner/package/penal/flow-condition/FlowCondition.vue
+++ b/src/components/bpmnProcessDesigner/package/penal/flow-condition/FlowCondition.vue
@@ -79,13 +79,13 @@ const resetFlowCondition = () => {
   bpmnElement.value = bpmnInstances().bpmnElement
   bpmnElementSource.value = bpmnElement.value.source
   bpmnElementSourceRef.value = bpmnElement.value.businessObject.sourceRef
+  // 初始化默认type为default
+  flowConditionForm.value = { type: 'default' }
   if (
     bpmnElementSourceRef.value &&
     bpmnElementSourceRef.value.default &&
-    bpmnElementSourceRef.value.default.id === bpmnElement.value.id &&
-    flowConditionForm.value.type == 'default'
+    bpmnElementSourceRef.value.default.id === bpmnElement.value.id
   ) {
-    // 默认
     flowConditionForm.value = { type: 'default' }
   } else if (!bpmnElement.value.businessObject.conditionExpression) {
     // 普通
diff --git a/src/utils/download.ts b/src/utils/download.ts
index ab200149..fe24ee27 100644
--- a/src/utils/download.ts
+++ b/src/utils/download.ts
@@ -29,7 +29,7 @@ const download = {
   html: (data: Blob, fileName: string) => {
     download0(data, fileName, 'text/html')
   },
-  // 下载 Markdown 方法
+  // 下载 MarkdownView 方法
   markdown: (data: Blob, fileName: string) => {
     download0(data, fileName, 'text/markdown')
   }
diff --git a/src/views/Login/SocialLogin.vue b/src/views/Login/SocialLogin.vue
index 1eb9293c..eff59ba6 100644
--- a/src/views/Login/SocialLogin.vue
+++ b/src/views/Login/SocialLogin.vue
@@ -64,7 +64,7 @@
                   </el-form-item>
                 </el-col>
                 <el-col :span="24" style="padding-right: 10px; padding-left: 10px">
-                  <el-form-item v-if="loginData.tenantEnable === 'true'" prop="tenantName">
+                  <el-form-item v-if="loginData.tenantEnable" prop="tenantName">
                     <el-input
                       v-model="loginData.loginForm.tenantName"
                       :placeholder="t('login.tenantNamePlaceholder')"
@@ -207,7 +207,7 @@ const loginData = reactive({
 // 获取验证码
 const getCode = async () => {
   // 情况一,未开启:则直接登录
-  if (loginData.captchaEnable) {
+  if (!loginData.captchaEnable) {
     await handleLogin({})
   } else {
     // 情况二,已开启:则展示验证码;只有完成验证码的情况,才进行登录
diff --git a/src/views/ai/chat/components/ChatConversationUpdateForm.vue b/src/views/ai/chat/components/ChatConversationUpdateForm.vue
new file mode 100644
index 00000000..fe08ffb2
--- /dev/null
+++ b/src/views/ai/chat/components/ChatConversationUpdateForm.vue
@@ -0,0 +1,141 @@
+<template>
+  <Dialog title="设定" v-model="dialogVisible">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="130px"
+      v-loading="formLoading"
+    >
+      <el-form-item label="角色设定" prop="systemMessage">
+        <el-input type="textarea" v-model="formData.systemMessage" placeholder="请输入角色设定" />
+      </el-form-item>
+      <el-form-item label="模型" prop="modelId">
+        <el-select v-model="formData.modelId" placeholder="请选择模型">
+          <el-option
+            v-for="chatModel in chatModelList"
+            :key="chatModel.id"
+            :label="chatModel.name"
+            :value="chatModel.id"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="温度参数" prop="temperature">
+        <el-input-number
+          v-model="formData.temperature"
+          placeholder="请输入温度参数"
+          :min="0"
+          :max="2"
+          :precision="2"
+        />
+      </el-form-item>
+      <el-form-item label="回复数 Token 数" prop="maxTokens">
+        <el-input-number
+          v-model="formData.maxTokens"
+          placeholder="请输入回复数 Token 数"
+          :min="0"
+          :max="4096"
+        />
+      </el-form-item>
+      <el-form-item label="上下文数量" prop="maxContexts">
+        <el-input-number
+          v-model="formData.maxContexts"
+          placeholder="请输入上下文数量"
+          :min="0"
+          :max="20"
+        />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script setup lang="ts">
+import { CommonStatusEnum } from '@/utils/constants'
+import { ChatModelApi, ChatModelVO } from '@/api/ai/model/chatModel'
+import { ChatConversationApi, ChatConversationVO } from '@/api/ai/chat/conversation'
+
+/** AI 聊天角色 表单 */
+defineOptions({ name: 'ChatConversationUpdateForm' })
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formData = ref({
+  id: undefined,
+  systemMessage: undefined,
+  modelId: undefined,
+  temperature: undefined,
+  maxTokens: undefined,
+  maxContexts: undefined
+})
+const formRules = reactive({
+  modelId: [{ required: true, message: '模型不能为空', trigger: 'blur' }],
+  status: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
+  temperature: [{ required: true, message: '温度参数不能为空', trigger: 'blur' }],
+  maxTokens: [{ required: true, message: '回复数 Token 数不能为空', trigger: 'blur' }],
+  maxContexts: [{ required: true, message: '上下文数量不能为空', trigger: 'blur' }]
+})
+const formRef = ref() // 表单 Ref
+const chatModelList = ref([] as ChatModelVO[]) // 聊天模型列表
+
+/** 打开弹窗 */
+const open = async (id: number) => {
+  dialogVisible.value = true
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      const data = await ChatConversationApi.getChatConversationMy(id)
+      formData.value = Object.keys(formData.value).reduce((obj, key) => {
+        if (data.hasOwnProperty(key)) {
+          obj[key] = data[key]
+        }
+        return obj
+      }, {})
+    } finally {
+      formLoading.value = false
+    }
+  }
+  // 获得下拉数据
+  chatModelList.value = await ChatModelApi.getChatModelSimpleList(CommonStatusEnum.ENABLE)
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  await formRef.value.validate()
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as unknown as ChatConversationVO
+    await ChatConversationApi.updateChatConversationMy(data)
+    message.success('对话配置已更新')
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    systemMessage: undefined,
+    modelId: undefined,
+    temperature: undefined,
+    maxTokens: undefined,
+    maxContexts: undefined
+  }
+  formRef.value?.resetFields()
+}
+</script>
diff --git a/src/views/ai/chat/components/Header.vue b/src/views/ai/chat/components/Header.vue
new file mode 100644
index 00000000..7d859c2b
--- /dev/null
+++ b/src/views/ai/chat/components/Header.vue
@@ -0,0 +1,50 @@
+<!-- header -->
+<template>
+  <el-header class="chat-header">
+    <div class="title">
+      {{ title }}
+    </div>
+    <div class="title-right">
+      <slot></slot>
+    </div>
+  </el-header>
+</template>
+
+<script setup lang="ts">
+// 设置组件属性
+defineProps({
+  title: {
+    type: String,
+    required: true
+  }
+})
+</script>
+
+<style scoped lang="scss">
+.chat-header {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  align-items: center;
+  padding: 0 10px;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  background-color: #ececec;
+  width: 100%;
+
+  .title {
+    font-size: 20px;
+    font-weight: bold;
+    overflow: hidden;
+    color: #3e3e3e;
+    max-width: 220px;
+  }
+
+  .title-right {
+    display: flex;
+    flex-direction: row;
+  }
+}
+
+
+</style>
diff --git a/src/views/ai/chat/components/MessageList.vue b/src/views/ai/chat/components/MessageList.vue
new file mode 100644
index 00000000..0f77f8ec
--- /dev/null
+++ b/src/views/ai/chat/components/MessageList.vue
@@ -0,0 +1,156 @@
+
+<template>
+  <div class="message-container" ref="messageContainer">
+    <div class="chat-list" v-for="(item, index) in list" :key="index">
+      <!--  靠左 message  -->
+      <div class="left-message message-item" v-if="item.type === 'system'">
+        <div class="avatar" >
+          <el-avatar
+            src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"
+          />
+        </div>
+        <div class="message">
+          <div>
+            <el-text class="time">{{formatDate(item.createTime)}}</el-text>
+          </div>
+          <div class="left-text-container">
+            <el-text class="left-text">
+              {{item.content}}
+            </el-text>
+          </div>
+          <div class="left-btns">
+            <div class="btn-cus" @click="noCopy(item.content)">
+              <img class="btn-image" src="@/assets/ai/copy.svg"/>
+              <el-text class="btn-cus-text">复制</el-text>
+            </div>
+            <div class="btn-cus" style="margin-left: 20px;" @click="onDelete(item.id)">
+              <img class="btn-image" src="@/assets/ai/delete.svg" style="height: 17px;"/>
+              <el-text class="btn-cus-text">删除</el-text>
+            </div>
+          </div>
+        </div>
+      </div>
+      <!--  靠右 message  -->
+      <div class="right-message message-item" v-if="item.type === 'user'">
+        <div class="avatar">
+          <el-avatar
+            src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"
+          />
+        </div>
+        <div class="message">
+          <div>
+            <el-text class="time">{{formatDate(item.createTime)}}</el-text>
+          </div>
+          <div class="right-text-container">
+            <el-text class="right-text">
+              {{item.content}}
+            </el-text>
+          </div>
+          <div class="right-btns">
+            <div class="btn-cus"  @click="noCopy(item.content)">
+              <img class="btn-image" src="@/assets/ai/copy.svg"/>
+              <el-text class="btn-cus-text">复制</el-text>
+            </div>
+            <div class="btn-cus" style="margin-left: 20px;" @click="onDelete(item.id)">
+              <img class="btn-image" src="@/assets/ai/delete.svg" style="height: 17px;"/>
+              <el-text class="btn-cus-text">删除</el-text>
+            </div>
+          </div>
+        </div>
+
+      </div>
+    </div>
+  </div>
+</template>
+<script setup lang="ts">
+
+import { useClipboard } from '@vueuse/core'
+import { ChatMessageApi, ChatMessageVO, ChatMessageSendVO} from '@/api/ai/chat/message'
+import { formatDate } from '@/utils/formatTime'
+
+// 初始化 copy 到粘贴板
+const { copy, isSupported } = useClipboard();
+/** chat message 列表 */
+defineOptions({ name: 'chatMessageList' })
+const list = ref<ChatMessageVO[]>([]) // 列表的数据
+
+// 对话id TODO @范 先写死
+const conversationId = '1781604279872581648'
+const content = '苹果是什么颜色?'
+
+/** 查询列表 */
+const messageList = async () => {
+  try {
+    // 获取列表数据
+    const res = await ChatMessageApi.messageList(conversationId)
+    list.value = res;
+    // 滚动到最下面
+    scrollToBottom();
+  } finally {
+  }
+}
+// ref
+const messageContainer: any = ref(null);
+const isScrolling = ref(false)//用于判断用户是否在滚动
+
+function scrollToBottom() {
+  nextTick(() => {
+    //注意要使用nexttick以免获取不到dom
+    console.log('isScrolling.value', isScrolling.value)
+    if (!isScrolling.value) {
+      messageContainer.value.scrollTop = messageContainer.value.scrollHeight - messageContainer.value.offsetHeight
+    }
+  })
+}
+
+function handleScroll() {
+  const scrollContainer = messageContainer.value
+  const scrollTop = scrollContainer.scrollTop
+  const scrollHeight = scrollContainer.scrollHeight
+  const offsetHeight = scrollContainer.offsetHeight
+
+  if (scrollTop + offsetHeight < scrollHeight) {
+    // 用户开始滚动并在最底部之上,取消保持在最底部的效果
+    isScrolling.value = true
+  } else {
+    // 用户停止滚动并滚动到最底部,开启保持到最底部的效果
+    isScrolling.value = false
+  }
+}
+
+function noCopy(content) {
+  copy(content)
+  ElMessage({
+    message: '复制成功!',
+    type: 'success',
+  })
+}
+
+const onDelete = async (id) => {
+  // 删除 message
+  await ChatMessageApi.delete(id)
+  ElMessage({
+    message: '删除成功!',
+    type: 'success',
+  })
+  // 重新获取 message 列表
+  await messageList();
+}
+
+/** 初始化 **/
+onMounted(async () => {
+  // 获取列表数据
+  messageList();
+
+  // scrollToBottom();
+  // await nextTick
+  // 监听滚动事件,判断用户滚动状态
+  messageContainer.value.addEventListener('scroll', handleScroll)
+
+})
+</script>
+
+<style scoped lang="scss">
+
+
+</style>
diff --git a/src/views/ai/chat/index.vue b/src/views/ai/chat/index.vue
index ec49b774..0da53f1e 100644
--- a/src/views/ai/chat/index.vue
+++ b/src/views/ai/chat/index.vue
@@ -4,8 +4,8 @@
     <el-aside width="260px" class="conversation-container">
       <div>
         <!-- 左顶部:新建对话 -->
-        <el-button class="w-1/1 btn-new-conversation" type="primary">
-          <Icon icon="ep:plus" class="mr-5px" />
+        <el-button class="w-1/1 btn-new-conversation" type="primary" @click="createConversation">
+          <Icon icon="ep:plus" class="mr-5px"/>
           新建对话
         </el-button>
         <!-- 左顶部:搜索对话 -->
@@ -17,45 +17,51 @@
           @keyup="searchConversation"
         >
           <template #prefix>
-            <Icon icon="ep:search" />
+            <Icon icon="ep:search"/>
           </template>
         </el-input>
         <!-- 左中间:对话列表 -->
-        <div class="conversation-list" >
-          <!-- TODO @芋艿,置顶、聊天记录、一星期钱、30天前,前端对数据重新做一下分组,或者后端接口改一下 -->
-          <div>
-            <el-text class="mx-1" size="small" tag="b">置顶</el-text>
-          </div>
-          <el-row v-for="conversation in conversationList" :key="conversation.id">
-            <div
-              :class="conversation.id === conversationId ? 'conversation active' : 'conversation'"
-              @click="changeConversation(conversation)"
-            >
-              <div class="title-wrapper">
-                <img class="avatar" :src="conversation.avatar" />
-                <span class="title">{{ conversation.title }}</span>
-              </div>
-              <div class="button-wrapper">
-                <el-icon title="编辑" @click="updateConversationTitle(conversation)">
-                  <Icon icon="ep:edit" />
-                </el-icon>
-                <el-icon title="删除会话" @click="deleteConversationTitle(conversation)">
-                  <Icon icon="ep:delete" />
-                </el-icon>
-              </div>
+        <div class="conversation-list">
+          <!-- TODO @fain:置顶、聊天记录、一星期钱、30天前,前端对数据重新做一下分组,或者后端接口改一下 -->
+          <div v-for="conversationKey in Object.keys(conversationMap)" :key="conversationKey" >
+            <div v-if="conversationMap[conversationKey].length">
+              <el-text class="mx-1" size="small" tag="b">{{conversationKey}}</el-text>
             </div>
-          </el-row>
+            <el-row
+              v-for="conversation in conversationMap[conversationKey]" 
+              :key="conversation.id"
+              @click="handleConversationClick(conversation.id)">
+              <div
+                :class="conversation.id === conversationId ? 'conversation active' : 'conversation'"
+                @click="changeConversation(conversation.id)"
+              >
+                <div class="title-wrapper">
+                  <img class="avatar" :src="conversation.roleAvatar"/>
+                  <span class="title">{{ conversation.title }}</span>
+                </div>
+                <!-- TODO @fan:缺一个【置顶】按钮,效果改成 hover 上去展示 -->
+                <div class="button-wrapper">
+                  <el-icon title="编辑" @click="updateConversationTitle(conversation)">
+                    <Icon icon="ep:edit"/>
+                  </el-icon>
+                  <el-icon title="删除会话" @click="deleteChatConversation(conversation)">
+                    <Icon icon="ep:delete"/>
+                  </el-icon>
+                </div>
+              </div>
+            </el-row>
+          </div>
         </div>
       </div>
       <!-- 左底部:工具栏 -->
       <div class="tool-box">
-        <div>
-          <Icon icon="ep:user" />
+        <div @click="handleRoleRepository">
+          <Icon icon="ep:user"/>
           <el-text size="small">角色仓库</el-text>
         </div>
-        <div>
-          <Icon icon="ep:delete" />
-          <el-text size="small" >清空未置顶对话</el-text>
+        <div @click="handleClearConversation">
+          <Icon icon="ep:delete"/>
+          <el-text size="small">清空未置顶对话</el-text>
         </div>
       </div>
     </el-aside>
@@ -64,73 +70,579 @@
       <!-- 右顶部 TODO 芋艿:右对齐 -->
       <el-header class="header">
         <div class="title">
-          标题......
+          {{ useConversation?.title }}
         </div>
         <div>
-          <el-button>3.5-turbo-0125 <Icon icon="ep:setting" /></el-button>
-          <el-button>
-            <Icon icon="ep:user" />
+          <!-- TODO @fan:样式改下;这里我已经改成点击后,弹出了 -->
+          <el-button type="primary" @click="openChatConversationUpdateForm">
+            <span v-html="useConversation?.modelName"></span>
+            <Icon icon="ep:setting" style="margin-left: 10px"/>
           </el-button>
           <el-button>
-            <Icon icon="ep:download" />
+            <Icon icon="ep:user"/>
           </el-button>
           <el-button>
-            <Icon icon="ep:arrow-up" />
+            <Icon icon="ep:download"/>
+          </el-button>
+          <el-button>
+            <Icon icon="ep:arrow-up"/>
           </el-button>
         </div>
       </el-header>
-      <el-main>对话列表</el-main>
-      <el-footer>
-        <el-input
-          class="prompt-input"
-          type="textarea"
-          placeholder="请输入提示词..."
-        />
+
+      <!--  main    -->
+      <el-main class="main-container">
+        <div class="message-container" ref="messageContainer">
+          <div class="chat-list" v-for="(item, index) in list" :key="index">
+            <!--  靠左 message  -->
+            <!-- TODO 芋艿:类型判断 -->
+            <div class="left-message message-item" v-if="item.type === 'system'">
+              <div class="avatar">
+                <el-avatar
+                  src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"
+                />
+              </div>
+              <div class="message">
+                <div>
+                  <el-text class="time">{{ formatDate(item.createTime) }}</el-text>
+                </div>
+                <div class="left-text-container" ref="markdownViewRef">
+<!--                  <div class="left-text markdown-view" v-html="item.content"></div>-->
+                  <!--                  <mdPreview :content="item.content" :delay="false" />-->
+                  <MarkdownView class="left-text" :content="item.content" />
+                </div>
+                <div class="left-btns">
+                  <div class="btn-cus" @click="noCopy(item.content)">
+                    <img class="btn-image" src="../../../assets/ai/copy.svg"/>
+                    <el-text class="btn-cus-text">复制</el-text>
+                  </div>
+                  <div class="btn-cus" style="margin-left: 20px" @click="onDelete(item.id)">
+                    <img class="btn-image" src="@/assets/ai/delete.svg" style="height: 17px"/>
+                    <el-text class="btn-cus-text">删除</el-text>
+                  </div>
+                </div>
+              </div>
+            </div>
+            <!--  靠右 message  -->
+            <div class="right-message message-item" v-if="item.type === 'user'">
+              <div class="avatar">
+                <el-avatar
+                  src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"
+                />
+              </div>
+              <div class="message">
+                <div>
+                  <el-text class="time">{{ formatDate(item.createTime) }}</el-text>
+                </div>
+                <div class="right-text-container">
+                  <div class="right-text">{{ item.content }}</div>
+<!--                  <MarkdownView class="right-text" :content="item.content" />-->
+                </div>
+                <div class="right-btns">
+                  <div class="btn-cus" @click="noCopy(item.content)">
+                    <img class="btn-image" src="@/assets/ai/copy.svg"/>
+                    <el-text class="btn-cus-text">复制</el-text>
+                  </div>
+                  <div class="btn-cus" style="margin-left: 20px" @click="onDelete(item.id)">
+                    <img class="btn-image" src="@/assets/ai/delete.svg" style="height: 17px"/>
+                    <el-text class="btn-cus-text">删除</el-text>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+        <!--   角色仓库抽屉  -->
+        <el-drawer v-model="drawer" title="角色仓库" size="50%">
+          <Role/>
+        </el-drawer>
+      </el-main>
+      <el-footer class="footer-container">
+        <form @submit.prevent="onSend" class="prompt-from">
+          <textarea
+            class="prompt-input"
+            v-model="prompt"
+            @keyup.enter="onSend"
+            @input="onPromptInput"
+            @compositionstart="onCompositionstart"
+            @compositionend="onCompositionend"
+            placeholder="问我任何问题...(Shift+Enter 换行,按下 Enter 发送)"
+          ></textarea>
+          <div class="prompt-btns">
+            <el-switch/>
+            <el-button
+              type="primary"
+              size="default"
+              @click="onSend()"
+              :loading="conversationInProgress"
+              v-if="conversationInProgress == false"
+            >
+              {{ conversationInProgress ? '进行中' : '发送' }}
+            </el-button>
+            <el-button
+              type="danger"
+              size="default"
+              @click="stopStream()"
+              v-if="conversationInProgress == true"
+            >
+              停止
+            </el-button>
+          </div>
+        </form>
       </el-footer>
     </el-container>
   </el-container>
+
+  <ChatConversationUpdateForm
+    ref="chatConversationUpdateFormRef"
+    @success="getChatConversationList"
+  />
 </template>
+
 <script setup lang="ts">
-const conversationList = [
-  {
-    id: 1,
-    title: '测试标题',
-    avatar:
-      'http://test.yudao.iocoder.cn/96c787a2ce88bf6d0ce3cd8b6cf5314e80e7703cd41bf4af8cd2e2909dbd6b6d.png'
-  },
-  {
-    id: 2,
-    title: '测试对话',
-    avatar:
-      'http://test.yudao.iocoder.cn/96c787a2ce88bf6d0ce3cd8b6cf5314e80e7703cd41bf4af8cd2e2909dbd6b6d.png'
+import MarkdownView from '@/components/MarkdownView/index.vue'
+import {ChatMessageApi, ChatMessageVO} from '@/api/ai/chat/message'
+import {ChatConversationApi, ChatConversationVO} from '@/api/ai/chat/conversation'
+import ChatConversationUpdateForm from './components/ChatConversationUpdateForm.vue'
+import Role from '@/views/ai/chat/role/index.vue'
+import {formatDate} from '@/utils/formatTime'
+import {useClipboard} from '@vueuse/core'
+
+const route = useRoute() // 路由
+const message = useMessage() // 消息弹窗
+
+const conversationList = ref([] as ChatConversationVO[])
+const conversationMap = ref<any>({})
+// 初始化 copy 到粘贴板
+const {copy} = useClipboard()
+
+const drawer = ref<boolean>(false) // 角色仓库抽屉
+const searchName = ref('') // 查询的内容
+const inputTimeout = ref<any>() // 处理输入中回车的定时器
+const conversationId = ref<number | null>(null) // 选中的对话编号
+const conversationInProgress = ref(false) // 对话进行中
+const conversationInAbortController = ref<any>() // 对话进行中 abort 控制器(控制 stream 对话)
+
+const prompt = ref<string>() // prompt
+
+// 判断 消息列表 滚动的位置(用于判断是否需要滚动到消息最下方)
+const messageContainer: any = ref(null)
+const isScrolling = ref(false) //用于判断用户是否在滚动
+const isComposing = ref(false) // 判断用户是否在输入
+
+/** chat message 列表 */
+// defineOptions({ name: 'chatMessageList' })
+const list = ref<ChatMessageVO[]>([]) // 列表的数据
+const useConversation = ref<ChatConversationVO | null>(null) // 使用的 Conversation
+
+/** 新建对话 */
+const createConversation = async () => {
+  // 新建对话
+  const conversationId = await ChatConversationApi.createChatConversationMy(
+    {} as unknown as ChatConversationVO
+  )
+  changeConversation(conversationId)
+  // 刷新对话列表
+  await getChatConversationList()
+}
+
+const changeConversation = (id: number) => {
+  // 切换对话
+  conversationId.value = id
+  // TODO 芋艿:待实现
+  // 刷新 message 列表
+  messageList()
+}
+
+/** 更新聊天会话的标题 */
+const updateConversationTitle = async (conversation: ChatConversationVO) => {
+  // 二次确认
+  const {value} = await ElMessageBox.prompt('修改标题', {
+    inputPattern: /^[\s\S]*.*\S[\s\S]*$/, // 判断非空,且非空格
+    inputErrorMessage: '标题不能为空',
+    inputValue: conversation.title
+  })
+  // 发起修改
+  await ChatConversationApi.updateChatConversationMy({
+    id: conversation.id,
+    title: value
+  } as ChatConversationVO)
+  message.success('重命名成功')
+  // 刷新列表
+  await getChatConversationList()
+}
+
+/** 删除聊天会话 */
+const deleteChatConversation = async (conversation: ChatConversationVO) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm(`是否确认删除会话 - ${conversation.title}?`)
+    // 发起删除
+    await ChatConversationApi.deleteChatConversationMy(conversation.id)
+    message.success('会话已删除')
+    // 刷新列表
+    await getChatConversationList()
+  } catch {
   }
-]
-const conversationId = ref(1)
-const searchName = ref('')
-const leftHeight = window.innerHeight - 240 // TODO 芋艿:这里还不太对
-
-const changeConversation = (conversation) => {
-  console.log(conversation)
-  conversationId.value = conversation.id
-  // TODO 芋艿:待实现
-}
-
-const updateConversationTitle = (conversation) => {
-  console.log(conversation)
-  // TODO 芋艿:待实现
-}
-
-const deleteConversationTitle = (conversation) => {
-  console.log(conversation)
-  // TODO 芋艿:待实现
 }
 
 const searchConversation = () => {
-  // TODO 芋艿:待实现
+  // TODO fan:待实现
 }
+
+/** send */
+const onSend = async () => {
+  // 判断用户是否在输入
+  if (isComposing.value) {
+    return
+  }
+  // 进行中不允许发送
+  if (conversationInProgress.value) {
+    return
+  }
+  const content = prompt.value?.trim() + ''
+  if (content.length < 2) {
+    ElMessage({
+      message: '请输入内容!',
+      type: 'error'
+    })
+    return
+  }
+  // TODO 芋艿:这块交互要在优化;应该是先插入到 UI 界面,里面会有当前的消息,和正在思考中;之后发起请求;
+  // 清空输入框
+  prompt.value = ''
+  // const requestParams = {
+  //   conversationId: conversationId.value,
+  //   content: content
+  // } as unknown as ChatMessageSendVO
+  // // 添加 message
+  const userMessage = {
+    conversationId: conversationId.value,
+    content: content
+  } as ChatMessageVO
+  // list.value.push(userMessage)
+  // // 滚动到住下面
+  // scrollToBottom()
+  // stream
+  await doSendStream(userMessage)
+  //
+}
+
+const doSendStream = async (userMessage: ChatMessageVO) => {
+  // 创建AbortController实例,以便中止请求
+  conversationInAbortController.value = new AbortController()
+  // 标记对话进行中
+  conversationInProgress.value = true
+  try {
+    // 发送 event stream
+    let isFirstMessage = true
+    let content = ''
+    ChatMessageApi.sendStream(
+      userMessage.conversationId, // TODO 芋艿:这里可能要在优化;
+      userMessage.content,
+      conversationInAbortController.value,
+      (message) => {
+        console.log('message', message)
+        const data = JSON.parse(message.data) // TODO 芋艿:类型处理;
+        // debugger
+        // 如果没有内容结束链接
+        if (data.receive.content === '') {
+          // 标记对话结束
+          conversationInProgress.value = false
+          // 结束 stream 对话
+          conversationInAbortController.value.abort()
+        }
+        // 首次返回需要添加一个 message 到页面,后面的都是更新
+        if (isFirstMessage) {
+          isFirstMessage = false
+          // debugger
+          list.value.push(data.send)
+          list.value.push(data.receive)
+        } else {
+          // debugger
+          content = content + data.receive.content
+          const lastMessage = list.value[list.value.length - 1]
+          lastMessage.content = content
+          list.value[list.value - 1] = lastMessage
+        }
+        // 滚动到最下面
+        scrollToBottom()
+      },
+      (error) => {
+        console.log('error', error)
+        // 标记对话结束
+        conversationInProgress.value = false
+        // 结束 stream 对话
+        conversationInAbortController.value.abort()
+      },
+      () => {
+        console.log('close')
+        // 标记对话结束
+        conversationInProgress.value = false
+        // 结束 stream 对话
+        conversationInAbortController.value.abort()
+      }
+    )
+  } finally {
+  }
+}
+
+/** 查询列表 */
+const messageList = async () => {
+  try {
+    if (conversationId.value === null) {
+      return
+    }
+    // 获取列表数据
+    const res = await ChatMessageApi.messageList(conversationId.value)
+    list.value = res
+
+    // 滚动到最下面
+    scrollToBottom()
+  } finally {
+  }
+}
+
+function scrollToBottom() {
+  nextTick(() => {
+    //注意要使用nexttick以免获取不到dom
+    console.log('isScrolling.value', isScrolling.value)
+    if (!isScrolling.value) {
+      messageContainer.value.scrollTop =
+        messageContainer.value.scrollHeight - messageContainer.value.offsetHeight
+    }
+  })
+}
+
+function handleScroll() {
+  const scrollContainer = messageContainer.value
+  const scrollTop = scrollContainer.scrollTop
+  const scrollHeight = scrollContainer.scrollHeight
+  const offsetHeight = scrollContainer.offsetHeight
+
+  if (scrollTop + offsetHeight < scrollHeight) {
+    // 用户开始滚动并在最底部之上,取消保持在最底部的效果
+    isScrolling.value = true
+  } else {
+    // 用户停止滚动并滚动到最底部,开启保持到最底部的效果
+    isScrolling.value = false
+  }
+}
+
+function noCopy(content) {
+  copy(content)
+  ElMessage({
+    message: '复制成功!',
+    type: 'success'
+  })
+}
+
+const onDelete = async (id) => {
+  // 删除 message
+  await ChatMessageApi.delete(id)
+  ElMessage({
+    message: '删除成功!',
+    type: 'success'
+  })
+  // tip:如果 stream 进行中的 message,就需要调用 controller 结束
+  stopStream()
+  // 重新获取 message 列表
+  await messageList()
+}
+
+const stopStream = async () => {
+  // tip:如果 stream 进行中的 message,就需要调用 controller 结束
+  if (conversationInAbortController.value) {
+    conversationInAbortController.value.abort()
+  }
+  // 设置为 false
+  conversationInProgress.value = false
+}
+
+/** 修改聊天会话 */
+const chatConversationUpdateFormRef = ref()
+const openChatConversationUpdateForm = async () => {
+  chatConversationUpdateFormRef.value.open(conversationId.value)
+}
+
+// 输入
+const onCompositionstart = () => {
+  console.log('onCompositionstart。。。.')
+  isComposing.value = true
+}
+
+const onCompositionend = () => {
+  // console.log('输入结束...')
+  setTimeout(() => {
+    console.log('输入结束...')
+    isComposing.value = false
+  }, 200)
+}
+
+const onPromptInput = (event) => {
+  // 非输入法 输入设置为 true
+  if (!isComposing.value) {
+    // 回车 event data 是 null
+    if (event.data == null) {
+      return
+    }
+    console.log('setTimeout 输入开始...')
+    isComposing.value = true
+  }
+  // 清理定时器
+  if (inputTimeout.value) {
+    clearTimeout(inputTimeout.value)
+  }
+  // 重置定时器
+  inputTimeout.value = setTimeout(() => {
+    console.log('setTimeout 输入结束...')
+    isComposing.value = false
+  }, 400)
+}
+
+const getConversation = async (conversationId: number | null) => {
+  if (!conversationId) {
+    return
+  }
+  // 获取对话信息
+  useConversation.value = await ChatConversationApi.getChatConversationMy(conversationId)
+  console.log('useConversation.value', useConversation.value)
+}
+
+/** 获得聊天会话列表 */
+const getChatConversationList = async () => {
+  conversationList.value = await ChatConversationApi.getChatConversationMyList()
+  // 默认选中第一条
+  if (conversationList.value.length === 0) {
+    conversationId.value = null
+    list.value = []
+  } else {
+    if (conversationId.value === null) {
+      conversationId.value = conversationList.value[0].id
+      changeConversation(conversationList.value[0].id)
+    }
+  }
+  // map  
+  const groupRes = await conversationTimeGroup(conversationList.value)
+  conversationMap.value = groupRes
+}
+
+const conversationTimeGroup = async (list: ChatConversationVO[]) => {
+  // 排序、指定、时间分组(今天、一天前、三天前、七天前、30天前)
+  const groupMap = {
+    '置顶': [],
+    '今天': [],
+    '一天前': [],
+    '三天前': [],
+    '七天前': [],
+    '三十天前': []
+  }
+  // 当前时间的时间戳
+  const now = Date.now();
+  // 定义时间间隔常量(单位:毫秒)
+  const oneDay = 24 * 60 * 60 * 1000;
+  const threeDays = 3 * oneDay;
+  const sevenDays = 7 * oneDay;
+  const thirtyDays = 30 * oneDay;
+  console.log('listlistlist', list)
+  for (const conversation: ChatConversationVO of list) {
+    // 置顶
+    if (conversation.pinned) {
+      groupMap['置顶'].push(conversation)
+      continue
+    }
+    // 计算时间差(单位:毫秒)
+    const diff = now - conversation.updateTime;
+    // 根据时间间隔判断
+    if (diff < oneDay) {
+      groupMap['今天'].push(conversation)
+    } else if (diff < threeDays) {
+      groupMap['一天前'].push(conversation)
+    } else if (diff < sevenDays) {
+      groupMap['三天前'].push(conversation)
+    } else if (diff < thirtyDays) {
+      groupMap['七天前'].push(conversation)
+    } else {
+      groupMap['三十天前'].push(conversation)
+    }
+  }
+  return groupMap
+}
+
+
+// 对话点击
+const handleConversationClick = async (id: number) => {
+  // 切换对话
+  conversationId.value = id
+  console.log('conversationId.value', conversationId.value)
+  // 获取列表数据
+  await messageList()
+}
+
+// 角色仓库
+const handleRoleRepository = async () => {
+  drawer.value = !drawer.value
+}
+
+// 清空对话
+const handleClearConversation = async () => {
+  ElMessageBox.confirm(
+    '确认后对话会全部清空,置顶的对话除外。',
+    '确认提示',
+    {
+      confirmButtonText: '确认',
+      cancelButtonText: '取消',
+      type: 'warning',
+    }
+  )
+  .then(async () => {
+    await ChatConversationApi.deleteMyAllExceptPinned()
+    ElMessage({
+      message: '操作成功!',
+      type: 'success'
+    })
+    // 清空选中的对话
+    useConversation.value = null
+    conversationId.value = null
+    list.value = []
+    // 获得聊天会话列表
+    await getChatConversationList()
+  })
+  .catch(() => {
+  })
+}
+
+
+/** 初始化 **/
+onMounted(async () => {
+  // 设置当前对话
+  if (route.query.conversationId) {
+    conversationId.value = route.query.conversationId as number
+  }
+  // 获得聊天会话列表
+  await getChatConversationList()
+  // 获取对话信息
+  await getConversation(conversationId.value)
+  // 获取列表数据
+  await messageList()
+  // scrollToBottom();
+  // await nextTick
+  // 监听滚动事件,判断用户滚动状态
+  messageContainer.value.addEventListener('scroll', handleScroll)
+  // 添加 copy 监听
+  messageContainer.value.addEventListener('click', (e: any) => {
+    console.log(e)
+    if (e.target.id === 'copy') {
+      copy(e.target?.dataset?.copy)
+      ElMessage({
+        message: '复制成功!',
+        type: 'success'
+      })
+    }
+  })
+})
 </script>
 <style lang="scss" scoped>
-
 .ai-layout {
   // TODO @范 这里height不能 100% 先这样临时处理
   position: absolute;
@@ -172,8 +684,10 @@ const searchConversation = () => {
       border-radius: 5px;
       align-items: center;
       line-height: 30px;
+
       &.active {
         background-color: #e6e6e6;
+
         .button {
           display: inline-block;
         }
@@ -184,6 +698,7 @@ const searchConversation = () => {
         flex-direction: row;
         align-items: center;
       }
+
       .title {
         padding: 5px 10px;
         max-width: 220px;
@@ -192,6 +707,7 @@ const searchConversation = () => {
         white-space: nowrap;
         text-overflow: ellipsis;
       }
+
       .avatar {
         width: 28px;
         height: 28px;
@@ -199,6 +715,7 @@ const searchConversation = () => {
         flex-direction: row;
         justify-items: center;
       }
+
       // 对话编辑、删除
       .button-wrapper {
         right: 2px;
@@ -206,6 +723,7 @@ const searchConversation = () => {
         flex-direction: row;
         justify-items: center;
         color: #606266;
+
         .el-icon {
           margin-right: 5px;
         }
@@ -227,6 +745,8 @@ const searchConversation = () => {
       color: #606266;
       padding: 0;
       margin: 0;
+      cursor: pointer;
+
       > span {
         margin-left: 5px;
       }
@@ -234,6 +754,7 @@ const searchConversation = () => {
   }
 }
 
+// 头部
 .detail-container {
   background: #ffffff;
 
@@ -243,16 +764,176 @@ const searchConversation = () => {
     align-items: center;
     justify-content: space-between;
     background: #fbfbfb;
+    box-shadow: 0 0 0 0 #dcdfe6;
 
     .title {
-      font-size: 23px;
+      font-size: 18px;
       font-weight: bold;
     }
   }
+}
 
+// main 容器
+.main-container {
+  margin: 0;
+  padding: 0;
+  position: relative;
+}
+
+.message-container {
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  //width: 100%;
+  //height: 100%;
+  overflow-y: scroll;
+  padding: 0 15px;
+}
+
+// 中间
+.chat-list {
+  display: flex;
+  flex-direction: column;
+  overflow-y: hidden;
+
+  .message-item {
+    margin-top: 50px;
+  }
+
+  .left-message {
+    display: flex;
+    flex-direction: row;
+  }
+
+  .right-message {
+    display: flex;
+    flex-direction: row-reverse;
+    justify-content: flex-start;
+  }
+
+  .avatar {
+    //height: 170px;
+    //width: 170px;
+  }
+
+  .message {
+    display: flex;
+    flex-direction: column;
+    text-align: left;
+    margin: 0 15px;
+
+    .time {
+      text-align: left;
+      line-height: 30px;
+    }
+
+    .left-text-container {
+      display: flex;
+      flex-direction: column;
+      overflow-wrap: break-word;
+      background-color: rgba(228, 228, 228, 0.8);
+      box-shadow: 0 0 0 1px rgba(228, 228, 228, 0.8);
+      border-radius: 10px;
+      padding: 10px 10px 5px 10px;
+
+      .left-text {
+        color: #393939;
+        font-size: 0.95rem;
+      }
+    }
+
+    .right-text-container {
+      display: flex;
+      flex-direction: row-reverse;
+
+      .right-text {
+        font-size: 0.95rem;
+        color: #fff;
+        display: inline;
+        background-color: #267fff;
+        color: #fff;
+        box-shadow: 0 0 0 1px #267fff;
+        border-radius: 10px;
+        padding: 10px;
+        width: auto;
+        overflow-wrap: break-word;
+      }
+    }
+
+    .left-btns,
+    .right-btns {
+      display: flex;
+      flex-direction: row;
+      margin-top: 8px;
+    }
+  }
+
+  // 复制、删除按钮
+  .btn-cus {
+    display: flex;
+    background-color: transparent;
+    align-items: center;
+
+    .btn-image {
+      height: 20px;
+      margin-right: 5px;
+    }
+
+    .btn-cus-text {
+      color: #757575;
+    }
+  }
+
+  .btn-cus:hover {
+    cursor: pointer;
+  }
+
+  .btn-cus:focus {
+    background-color: #8c939d;
+  }
+}
+
+// 底部
+.footer-container {
+  display: flex;
+  flex-direction: column;
+  height: auto;
+  margin: 0;
+  padding: 0;
+
+  .prompt-from {
+    display: flex;
+    flex-direction: column;
+    height: auto;
+    border: 1px solid #e3e3e3;
+    border-radius: 10px;
+    margin: 20px 20px;
+    padding: 9px 10px;
+  }
 
   .prompt-input {
+    height: 80px;
+    //box-shadow: none;
+    border: none;
+    box-sizing: border-box;
+    resize: none;
+    padding: 0px 2px;
+    //padding: 5px 5px;
 
+    overflow: hidden;
+  }
+
+  .prompt-input:focus {
+    outline: none;
+  }
+
+  .prompt-btns {
+    display: flex;
+    justify-content: space-between;
+    padding-bottom: 0px;
+    padding-top: 5px;
   }
 }
 </style>
diff --git a/src/views/ai/chat/role/RoleCategoryList.vue b/src/views/ai/chat/role/RoleCategoryList.vue
new file mode 100644
index 00000000..549a4416
--- /dev/null
+++ b/src/views/ai/chat/role/RoleCategoryList.vue
@@ -0,0 +1,46 @@
+<template>
+  <div class="category-list">
+    <div class="category" v-for="category in categoryList" :key="category">
+      <el-button plain v-if="category !== active" @click="handleCategoryClick(category)">{{ category }}</el-button>
+      <el-button plain type="primary" v-else @click="handleCategoryClick(category)">{{ category }}</el-button>
+    </div>
+  </div>
+</template>
+<script setup lang="ts">
+import {PropType} from "vue";
+
+// 定义属性
+defineProps({
+  categoryList: {
+    type: Array as PropType<string[]>,
+    required: true
+  },
+  active: {
+    type: String,
+    required: false
+  }
+})
+
+// 定义回调
+const emits = defineEmits(['onCategoryClick'])
+
+// 处理分类点击事件
+const handleCategoryClick = async (category) => {
+  emits('onCategoryClick', category)
+}
+
+</script>
+<style scoped lang="scss">
+.category-list {
+  display: flex;
+  flex-direction: row;
+  flex-wrap: wrap;
+  align-items: center;
+
+  .category {
+    display: flex;
+    flex-direction: row;
+    margin-right: 20px;
+  }
+}
+</style>
diff --git a/src/views/ai/chat/role/RoleList.vue b/src/views/ai/chat/role/RoleList.vue
new file mode 100644
index 00000000..834eb3cb
--- /dev/null
+++ b/src/views/ai/chat/role/RoleList.vue
@@ -0,0 +1,155 @@
+<template>
+  <div class="card-list">
+    <el-card class="card" body-class="card-body" v-for="role in roleList" :key="role.id">
+      <!--  更多 -->
+      <div class="more-container">
+        <el-dropdown @command="handleMoreClick">
+          <span class="el-dropdown-link">
+             <el-button type="text" >
+                <el-icon><More /></el-icon>
+              </el-button>
+          </span>
+          <template #dropdown>
+            <el-dropdown-menu>
+              <el-dropdown-item :command="['edit', role]" >
+                <el-icon><EditPen /></el-icon>编辑
+              </el-dropdown-item>
+              <el-dropdown-item :command="['delete', role]"  style="color: red;" >
+                <el-icon><Delete /></el-icon>
+                <span>删除</span>
+              </el-dropdown-item>
+            </el-dropdown-menu>
+          </template>
+        </el-dropdown>
+      </div>
+      <!--  头像 -->
+      <div>
+        <img class="avatar" :src="role.avatar"/>
+      </div>
+      <div class="right-container">
+        <div class="content-container">
+          <div class="title">{{ role.name }}</div>
+          <div class="description">{{ role.description }}</div>
+
+        </div>
+        <div class="btn-container">
+          <el-button type="primary" size="small" @click="handleUseClick(role)">使用</el-button>
+        </div>
+      </div>
+    </el-card>
+  </div>
+</template>
+
+<script setup lang="ts">
+import {ChatRoleVO} from '@/api/ai/model/chatRole'
+import {PropType} from "vue";
+import {Delete, EditPen, More} from "@element-plus/icons-vue";
+
+// 定义属性
+const props = defineProps({
+  roleList: {
+    type: Array as PropType<ChatRoleVO[]>,
+    required: true
+  }
+})
+// 定义钩子
+const emits = defineEmits(['onDelete', 'onEdit', 'onUse'])
+
+// more 点击
+const handleMoreClick = async (data) => {
+  const type = data[0]
+  const role = data[1]
+  if (type === 'delete') {
+    emits('onDelete', role)
+  } else {
+    emits('onEdit', role)
+  }
+}
+
+// 使用
+const handleUseClick = (role) => {
+  emits('onUse', role)
+}
+
+onMounted(() => {
+  console.log('props', props.roleList)
+})
+
+</script>
+
+<style lang="scss">
+// 重写 card 组件 body 样式
+.card-body {
+  max-width: 300px;
+  width: 300px;
+  padding: 15px;
+
+  display: flex;
+  flex-direction: row;
+  justify-content: flex-start;
+  position: relative;
+
+}
+</style>
+<style scoped lang="scss">
+
+// 卡片列表
+.card-list {
+  display: flex;
+  flex-direction: row;
+  flex-wrap: wrap;
+  position: relative;
+
+  .card {
+    margin-right: 20px;
+    border-radius: 10px;
+    margin-bottom: 30px;
+    position: relative;
+
+    .more-container {
+      position: absolute;
+      right: 12px;
+      top: 0px;
+    }
+
+    .avatar {
+      width: 40px;
+      height: 40px;
+      border-radius: 10px;
+      overflow: hidden;
+    }
+
+    .right-container {
+      margin-left: 10px;
+      width: 100%;
+      //height: 100px;
+
+      .content-container {
+        height: 85px;
+        overflow: hidden;
+
+        .title {
+          font-size: 18px;
+          font-weight: bold;
+          color: #3e3e3e;
+        }
+
+        .description {
+          margin-top: 10px;
+          font-size: 14px;
+          color: #6a6a6a;
+        }
+      }
+
+      .btn-container {
+        display: flex;
+        flex-direction: row-reverse;
+        margin-top: 15px;
+      }
+    }
+
+  }
+
+}
+
+</style>
diff --git a/src/views/ai/chat/role/index.vue b/src/views/ai/chat/role/index.vue
new file mode 100644
index 00000000..924a6784
--- /dev/null
+++ b/src/views/ai/chat/role/index.vue
@@ -0,0 +1,219 @@
+<!-- chat 角色仓库 -->
+<template>
+  <el-container class="role-container">
+    <ChatRoleForm  ref="formRef" @success="handlerAddRoleSuccess"  />
+
+    <Header title="角色仓库"/>
+    <el-main class="role-main">
+      <div class="search-container">
+        <!-- 搜索按钮 -->
+        <el-input
+          v-model="search"
+          class="search-input"
+          size="default"
+          placeholder="请输入搜索的内容"
+          :suffix-icon="Search"
+          @change="getActiveTabsRole"
+        />
+        <el-button type="primary" @click="handlerAddRole" style="margin-left: 20px;">
+          <el-icon><User /></el-icon>
+          添加角色
+        </el-button>
+      </div>
+      <!-- tabs -->
+      <el-tabs v-model="activeRole" class="tabs" @tab-click="handleTabsClick">
+        <el-tab-pane class="role-pane" label="我的角色" name="my-role">
+          <RoleList :role-list="myRoleList" @onDelete="handlerCardDelete" @onEdit="handlerCardEdit" @onUse="handlerCardUse" style="margin-top: 20px;" />
+        </el-tab-pane>
+        <el-tab-pane label="公共角色" name="public-role">
+          <RoleCategoryList :category-list="categoryList" :active="activeCategory" @onCategoryClick="handlerCategoryClick" />
+          <RoleList :role-list="publicRoleList" @onDelete="handlerCardDelete" @onEdit="handlerCardEdit" style="margin-top: 20px;" />
+        </el-tab-pane>
+      </el-tabs>
+    </el-main>
+  </el-container>
+
+</template>
+
+<!--  setup  -->
+<script setup lang="ts">
+import {ref} from "vue";
+import Header from '@/views/ai/chat/components/Header.vue'
+import RoleList from './RoleList.vue'
+import ChatRoleForm from '@/views/ai/model/chatRole/ChatRoleForm.vue'
+import RoleCategoryList from './RoleCategoryList.vue'
+import {ChatRoleApi, ChatRolePageReqVO, ChatRoleVO} from '@/api/ai/model/chatRole'
+import {ChatConversationApi, ChatConversationVO} from '@/api/ai/chat/conversation'
+import {TabsPaneContext} from "element-plus";
+import {Search, User} from "@element-plus/icons-vue";
+
+// 获取路由
+const router = useRouter()
+
+// 属性定义
+const activeRole = ref<string>('my-role') // 选中的角色
+const loadding = ref<boolean>(true) // 加载中
+const search = ref<string>('') // 加载中
+const myPageNo = ref<number>(1) // my 分页下标
+const myPageSize = ref<number>(50) // my 分页大小
+const myRoleList = ref<ChatRoleVO[]>([]) // my 分页大小
+const publicPageNo = ref<number>(1) // public 分页下标
+const publicPageSize = ref<number>(50) // public 分页大小
+const publicRoleList = ref<ChatRoleVO[]>([]) // public 分页大小
+const activeCategory = ref<string>('') // 选择中的分类
+const categoryList = ref<string[]>([]) // 角色分类类别
+/** 添加/修改操作 */
+const formRef = ref()
+// tabs 点击
+const handleTabsClick = async (tab: TabsPaneContext) => {
+  // 设置切换状态
+  const activeTabs = tab.paneName + ''
+  activeRole.value = activeTabs;
+  // 切换的时候重新加载数据
+  await getActiveTabsRole()
+}
+
+// 获取 my role
+const getMyRole = async () => {
+  const params:ChatRolePageReqVO = {
+    pageNo: myPageNo.value,
+    pageSize: myPageSize.value,
+    category: activeCategory.value,
+    name: search.value,
+    publicStatus: false
+  }
+  const { total, list } = await ChatRoleApi.getMyPage(params)
+  myRoleList.value = list
+}
+
+// 获取 public role
+const getPublicRole = async () => {
+  const params:ChatRolePageReqVO = {
+    pageNo: publicPageNo.value,
+    pageSize: publicPageSize.value,
+    category: activeCategory.value,
+    name: search.value,
+    publicStatus: true
+  }
+  const { total, list } = await ChatRoleApi.getMyPage(params)
+  publicRoleList.value = list
+}
+
+// 获取选中的 tabs 角色
+const getActiveTabsRole = async () => {
+  if (activeRole.value === 'my-role') {
+    await getMyRole()
+  } else {
+    await getPublicRole()
+  }
+}
+
+// 获取角色分类列表
+const getRoleCategoryList = async () => {
+  categoryList.value = await ChatRoleApi.getCategoryList()
+}
+
+// 处理分类点击
+const handlerCategoryClick = async (category: string) => {
+  if (activeCategory.value === category) {
+    activeCategory.value = ''
+  } else {
+    activeCategory.value = category
+  }
+  await getActiveTabsRole()
+}
+
+// 添加角色
+const handlerAddRole = async () => {
+  formRef.value.open('my-create', null, '添加角色')
+}
+
+// card 删除
+const handlerCardDelete = async (role) => {
+  await ChatRoleApi.deleteMy(role.id)
+  // 刷新数据
+  await getActiveTabsRole()
+}
+
+// card 编辑
+const handlerCardEdit = async (role) => {
+  formRef.value.open('my-update', role.id, '编辑角色')
+}
+
+// card 使用
+const handlerCardUse = async (role) => {
+  const data : ChatConversationVO = {
+    roleId: role.id
+  } as unknown as ChatConversationVO
+  // 创建对话
+  const conversation = await ChatConversationApi.createChatConversationMy(data)
+  // 调整页面
+  router.push({
+    path: `/ai/chat/index`,
+    query: {
+      conversationId: conversation,
+    }
+  })
+}
+
+// 添加角色成功
+const handlerAddRoleSuccess = async  (e) => {
+  console.log(e)
+  // 刷新数据
+  await getActiveTabsRole()
+}
+
+//
+onMounted( async () => {
+  // 获取分类
+  await getRoleCategoryList()
+  // 获取 role 数据
+  await getActiveTabsRole()
+})
+</script>
+<!-- 样式 -->
+<style scoped lang="scss">
+
+// 跟容器
+.role-container {
+  position: absolute;
+  margin: 0;
+  padding: 0;
+  width: 100%;
+  height: 100%;
+  left: 0;
+  right: 0;
+  top: 0;
+  bottom: 0;
+  background-color: #ffffff;
+
+  display: flex;
+  flex-direction: column;
+
+  .role-main {
+    position: relative;
+
+    .search-container {
+      //position: absolute;
+      //right: 20px;
+      //top: 10px;
+      //z-index: 100;
+    }
+
+    .search-input {
+      width: 240px;
+    }
+
+    .tabs {
+      position: relative;
+    }
+
+    .role-pane {
+      display: flex;
+      flex-direction: column;
+    }
+  }
+}
+
+
+</style>
diff --git a/src/views/ai/model/apiKey/ApiKeyForm.vue b/src/views/ai/model/apiKey/ApiKeyForm.vue
index 2f9ba580..a8fc0128 100644
--- a/src/views/ai/model/apiKey/ApiKeyForm.vue
+++ b/src/views/ai/model/apiKey/ApiKeyForm.vue
@@ -7,13 +7,7 @@
       label-width="120px"
       v-loading="formLoading"
     >
-      <el-form-item label="名称" prop="name">
-        <el-input v-model="formData.name" placeholder="请输入名称" />
-      </el-form-item>
-      <el-form-item label="密钥" prop="apiKey">
-        <el-input v-model="formData.apiKey" placeholder="请输入密钥" />
-      </el-form-item>
-      <el-form-item label="平台" prop="platform">
+      <el-form-item label="所属平台" prop="platform">
         <el-select v-model="formData.platform" placeholder="请输入平台" clearable>
           <el-option
             v-for="dict in getStrDictOptions(DICT_TYPE.AI_PLATFORM)"
@@ -23,8 +17,14 @@
           />
         </el-select>
       </el-form-item>
-      <el-form-item label="自定义 API 地址" prop="url">
-        <el-input v-model="formData.url" placeholder="请输入自定义 API 地址" />
+      <el-form-item label="名称" prop="name">
+        <el-input v-model="formData.name" placeholder="请输入名称" />
+      </el-form-item>
+      <el-form-item label="密钥" prop="apiKey">
+        <el-input v-model="formData.apiKey" placeholder="请输入密钥" />
+      </el-form-item>
+      <el-form-item label="自定义 API URL" prop="url">
+        <el-input v-model="formData.url" placeholder="请输入自定义 API URL" />
       </el-form-item>
       <el-form-item label="状态" prop="status">
         <el-radio-group v-model="formData.status">
diff --git a/src/views/ai/model/apiKey/index.vue b/src/views/ai/model/apiKey/index.vue
index fcafc463..6daf6a7d 100644
--- a/src/views/ai/model/apiKey/index.vue
+++ b/src/views/ai/model/apiKey/index.vue
@@ -60,15 +60,14 @@
   <!-- 列表 -->
   <ContentWrap>
     <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
-      <el-table-column label="编号" align="center" prop="id" />
-      <el-table-column label="名称" align="center" prop="name" />
-      <el-table-column label="密钥" align="center" prop="apiKey" />
-      <el-table-column label="平台" align="center" prop="platform">
+      <el-table-column label="所属平台" align="center" prop="platform">
         <template #default="scope">
           <dict-tag :type="DICT_TYPE.AI_PLATFORM" :value="scope.row.platform" />
         </template>
       </el-table-column>
-      <el-table-column label="自定义 API 地址" align="center" prop="url" />
+      <el-table-column label="名称" align="center" prop="name" />
+      <el-table-column label="密钥" align="center" prop="apiKey" />
+      <el-table-column label="自定义 API URL" align="center" prop="url" />
       <el-table-column label="状态" align="center" prop="status">
         <template #default="scope">
           <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
diff --git a/src/views/ai/model/chatModel/ChatModelForm.vue b/src/views/ai/model/chatModel/ChatModelForm.vue
new file mode 100644
index 00000000..cac3f772
--- /dev/null
+++ b/src/views/ai/model/chatModel/ChatModelForm.vue
@@ -0,0 +1,165 @@
+<template>
+  <Dialog :title="dialogTitle" v-model="dialogVisible">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="120px"
+      v-loading="formLoading"
+    >
+      <el-form-item label="所属平台" prop="platform">
+        <el-select v-model="formData.platform" placeholder="请输入平台" clearable>
+          <el-option
+            v-for="dict in getStrDictOptions(DICT_TYPE.AI_PLATFORM)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="API 秘钥" prop="keyId">
+        <el-select v-model="formData.keyId" placeholder="请选择 API 秘钥" clearable>
+          <el-option
+            v-for="apiKey in apiKeyList"
+            :key="apiKey.id"
+            :label="apiKey.name"
+            :value="apiKey.id"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="模型名字" prop="name">
+        <el-input v-model="formData.name" placeholder="请输入模型名字" />
+      </el-form-item>
+      <el-form-item label="模型标识" prop="model">
+        <el-input v-model="formData.model" placeholder="请输入模型标识" />
+      </el-form-item>
+      <el-form-item label="模型排序" prop="sort">
+        <el-input-number v-model="formData.sort" placeholder="请输入模型排序" class="!w-1/1" />
+      </el-form-item>
+      <el-form-item label="开启状态" prop="status">
+        <el-radio-group v-model="formData.status">
+          <el-radio
+            v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
+            :key="dict.value"
+            :label="dict.value"
+          >
+            {{ dict.label }}
+          </el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item label="温度参数" prop="temperature">
+        <el-input v-model="formData.temperature" placeholder="请输入温度参数" />
+      </el-form-item>
+      <el-form-item label="回复数 Token 数" prop="maxTokens">
+        <el-input v-model="formData.maxTokens" placeholder="请输入回复数 Token 数" />
+      </el-form-item>
+      <el-form-item label="上下文数量" prop="maxContexts">
+        <el-input v-model="formData.maxContexts" placeholder="请输入上下文数量" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script setup lang="ts">
+import { ChatModelApi, ChatModelVO } from '@/api/ai/model/chatModel'
+import { ApiKeyApi, ApiKeyVO } from '@/api/ai/model/apiKey'
+import { CommonStatusEnum } from '@/utils/constants'
+import { DICT_TYPE, getIntDictOptions, getStrDictOptions } from '@/utils/dict'
+
+/** API 聊天模型 表单 */
+defineOptions({ name: 'ChatModelForm' })
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const dialogTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formData = ref({
+  id: undefined,
+  keyId: undefined,
+  name: undefined,
+  model: undefined,
+  platform: undefined,
+  sort: undefined,
+  status: CommonStatusEnum.ENABLE,
+  temperature: undefined,
+  maxTokens: undefined,
+  maxContexts: undefined
+})
+const formRules = reactive({
+  keyId: [{ required: true, message: 'API 秘钥不能为空', trigger: 'blur' }],
+  name: [{ required: true, message: '模型名字不能为空', trigger: 'blur' }],
+  model: [{ required: true, message: '模型标识不能为空', trigger: 'blur' }],
+  platform: [{ required: true, message: '所属平台不能为空', trigger: 'blur' }],
+  sort: [{ required: true, message: '排序不能为空', trigger: 'blur' }],
+  status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
+})
+const formRef = ref() // 表单 Ref
+const apiKeyList = ref([] as ApiKeyVO[]) // API 密钥列表
+
+/** 打开弹窗 */
+const open = async (type: string, id?: number) => {
+  dialogVisible.value = true
+  dialogTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await ChatModelApi.getChatModel(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+  // 获得下拉数据
+  apiKeyList.value = await ApiKeyApi.getApiKeySimpleList(CommonStatusEnum.ENABLE)
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  await formRef.value.validate()
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as unknown as ChatModelVO
+    if (formType.value === 'create') {
+      await ChatModelApi.createChatModel(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await ChatModelApi.updateChatModel(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    keyId: undefined,
+    name: undefined,
+    model: undefined,
+    platform: undefined,
+    sort: undefined,
+    status: CommonStatusEnum.ENABLE,
+    temperature: undefined,
+    maxTokens: undefined,
+    maxContexts: undefined
+  }
+  formRef.value?.resetFields()
+}
+</script>
diff --git a/src/views/ai/model/chatModel/index.vue b/src/views/ai/model/chatModel/index.vue
new file mode 100644
index 00000000..c5506746
--- /dev/null
+++ b/src/views/ai/model/chatModel/index.vue
@@ -0,0 +1,185 @@
+<template>
+  <ContentWrap>
+    <!-- 搜索工作栏 -->
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
+      <el-form-item label="模型名字" prop="name">
+        <el-input
+          v-model="queryParams.name"
+          placeholder="请输入模型名字"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="模型标识" prop="model">
+        <el-input
+          v-model="queryParams.model"
+          placeholder="请输入模型标识"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="模型平台" prop="platform">
+        <el-input
+          v-model="queryParams.platform"
+          placeholder="请输入模型平台"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+        <el-button
+          type="primary"
+          plain
+          @click="openForm('create')"
+          v-hasPermi="['ai:chat-model:create']"
+        >
+          <Icon icon="ep:plus" class="mr-5px" /> 新增
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
+      <el-table-column label="所属平台" align="center" prop="platform">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.AI_PLATFORM" :value="scope.row.platform" />
+        </template>
+      </el-table-column>
+      <el-table-column label="模型名字" align="center" prop="name" />
+      <el-table-column label="模型标识" align="center" prop="model" />
+      <el-table-column label="API 秘钥" align="center" prop="keyId" min-width="140">
+        <template #default="scope">
+          <span>{{ apiKeyList.find((item) => item.id === scope.row.keyId)?.name }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="排序" align="center" prop="sort" />
+      <el-table-column label="状态" align="center" prop="status">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
+        </template>
+      </el-table-column>
+      <el-table-column label="温度参数" align="center" prop="temperature" />
+      <el-table-column label="回复数 Token 数" align="center" prop="maxTokens" min-width="140" />
+      <el-table-column label="上下文数量" align="center" prop="maxContexts" />
+      <el-table-column label="操作" align="center">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openForm('update', scope.row.id)"
+            v-hasPermi="['ai:chat-model:update']"
+          >
+            编辑
+          </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="handleDelete(scope.row.id)"
+            v-hasPermi="['ai:chat-model:delete']"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </ContentWrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <ChatModelForm ref="formRef" @success="getList" />
+</template>
+
+<script setup lang="ts">
+import { ChatModelApi, ChatModelVO } from '@/api/ai/model/chatModel'
+import ChatModelForm from './ChatModelForm.vue'
+import { DICT_TYPE } from '@/utils/dict'
+import { ApiKeyApi, ApiKeyVO } from '@/api/ai/model/apiKey'
+
+/** API 聊天模型 列表 */
+defineOptions({ name: 'AiChatModel' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const list = ref<ChatModelVO[]>([]) // 列表的数据
+const total = ref(0) // 列表的总页数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  name: undefined,
+  model: undefined,
+  platform: undefined
+})
+const queryFormRef = ref() // 搜索的表单
+const apiKeyList = ref([] as ApiKeyVO[]) // API 密钥列表
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await ChatModelApi.getChatModelPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await ChatModelApi.deleteChatModel(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 初始化 **/
+onMounted(async () => {
+  getList()
+  // 获得下拉数据
+  apiKeyList.value = await ApiKeyApi.getApiKeySimpleList()
+})
+</script>
diff --git a/src/views/ai/model/chatRole/ChatRoleForm.vue b/src/views/ai/model/chatRole/ChatRoleForm.vue
new file mode 100644
index 00000000..a3fbb891
--- /dev/null
+++ b/src/views/ai/model/chatRole/ChatRoleForm.vue
@@ -0,0 +1,205 @@
+<template>
+  <Dialog :title="dialogTitle" v-model="dialogVisible">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="100px"
+      v-loading="formLoading"
+    >
+      <el-form-item label="角色名称" prop="name">
+        <el-input v-model="formData.name" placeholder="请输入角色名称"/>
+      </el-form-item>
+      <el-form-item label="角色头像" prop="avatar">
+        <UploadImg v-model="formData.avatar" height="60px" width="60px"/>
+      </el-form-item>
+      <el-form-item label="绑定模型" prop="modelId" v-if="!isUser(formType)">
+        <el-select v-model="formData.modelId" placeholder="请选择模型" clearable>
+          <el-option
+            v-for="chatModel in chatModelList"
+            :key="chatModel.id"
+            :label="chatModel.name"
+            :value="chatModel.id"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="角色类别" prop="category" v-if="!isUser(formType)">
+        <el-input v-model="formData.category" placeholder="请输入角色类别"/>
+      </el-form-item>
+      <el-form-item label="角色描述" prop="description">
+        <el-input type="textarea" v-model="formData.description" placeholder="请输入角色描述"/>
+      </el-form-item>
+      <el-form-item label="角色设定" prop="systemMessage">
+        <el-input type="textarea" v-model="formData.systemMessage" placeholder="请输入角色设定" />
+      </el-form-item>
+      <el-form-item label="欢迎语👏🏻" prop="welcomeMessage" v-if="isUser(formType)">
+        <el-input type="textarea" v-model="formData.welcomeMessage" placeholder="请输入欢迎语"/>
+      </el-form-item>
+      <el-form-item label="是否公开" prop="publicStatus" v-if="!isUser(formType)">
+        <el-radio-group v-model="formData.publicStatus">
+          <el-radio
+            v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
+            :key="dict.value"
+            :label="dict.value"
+          >
+            {{ dict.label }}
+          </el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item label="角色排序" prop="sort" v-if="!isUser(formType)">
+        <el-input-number v-model="formData.sort" placeholder="请输入角色排序" class="!w-1/1"/>
+      </el-form-item>
+      <el-form-item label="开启状态" prop="status" v-if="!isUser(formType)">
+        <el-radio-group v-model="formData.status">
+          <el-radio
+            v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
+            :key="dict.value"
+            :label="dict.value"
+          >
+            {{ dict.label }}
+          </el-radio>
+        </el-radio-group>
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script setup lang="ts">
+import {getIntDictOptions, getBoolDictOptions, DICT_TYPE} from '@/utils/dict'
+import {ChatRoleApi, ChatRoleVO} from '@/api/ai/model/chatRole'
+import {CommonStatusEnum} from '@/utils/constants'
+import {ChatModelApi, ChatModelVO} from '@/api/ai/model/chatModel'
+
+/** AI 聊天角色 表单 */
+defineOptions({name: 'ChatRoleForm'})
+
+const {t} = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const dialogTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formData = ref({
+  id: undefined,
+  modelId: undefined,
+  name: undefined,
+  avatar: undefined,
+  category: undefined,
+  sort: undefined,
+  description: undefined,
+  systemMessage: undefined,
+  welcomeMessage: undefined,
+  publicStatus: true,
+  status: CommonStatusEnum.ENABLE
+})
+
+// 是否
+const isUser = (type: string) => {
+  return (type === 'my-create' || type === 'my-update')
+}
+
+const formRules = ref() // reactive(formRulesObj)
+const formRef = ref() // 表单 Ref
+const chatModelList = ref([] as ChatModelVO[]) // 聊天模型列表
+
+const getFormRules = async (type: string) => {
+  let formRulesObj = {
+    name: [{required: true, message: '角色名称不能为空', trigger: 'blur'}],
+    avatar: [{required: true, message: '角色头像不能为空', trigger: 'blur'}],
+    category: [{required: true, message: '角色类别不能为空', trigger: 'blur'}],
+    sort: [{required: true, message: '角色排序不能为空', trigger: 'blur'}],
+    description: [{required: true, message: '角色描述不能为空', trigger: 'blur'}],
+    systemMessage: [{required: true, message: '角色设定不能为空', trigger: 'blur'}],
+    // welcomeMessage: [{ required: true, message: '欢迎语不能为空', trigger: 'blur' }],
+    publicStatus: [{required: true, message: '是否公开不能为空', trigger: 'blur'}]
+  }
+
+  if (isUser(type)) {
+    formRulesObj['welcomeMessage'] = [{
+      required: true,
+      message: '欢迎语不能为空',
+      trigger: 'blur'
+    }]
+  }
+
+  formRules.value = reactive(formRulesObj)
+}
+
+/** 打开弹窗 */
+const open = async (type: string, id?: number, title?: string) => {
+  dialogVisible.value = true
+  dialogTitle.value = title || t('action.' + type)
+  formType.value = type
+  getFormRules(type)
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await ChatRoleApi.getChatRole(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+  // 获得下拉数据
+  chatModelList.value = await ChatModelApi.getChatModelSimpleList(CommonStatusEnum.ENABLE)
+}
+defineExpose({open}) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  await formRef.value.validate()
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as unknown as ChatRoleVO
+
+    // tip: my-create、my-update 是 chat 角色仓库调用
+    // tip: create、else 是后台管理调用
+
+    if (formType.value === 'my-create') {
+      await ChatRoleApi.createMy(data)
+      message.success(t('common.createSuccess'))
+    } else if (formType.value === 'my-update') {
+      await ChatRoleApi.updateMy(data)
+      message.success(t('common.updateSuccess'))
+    } else if (formType.value === 'create') {
+      await ChatRoleApi.createChatRole(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await ChatRoleApi.updateChatRole(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    modelId: undefined,
+    name: undefined,
+    avatar: undefined,
+    category: undefined,
+    sort: undefined,
+    description: undefined,
+    systemMessage: undefined,
+    welcomeMessage: undefined,
+    publicStatus: true,
+    status: CommonStatusEnum.ENABLE
+  }
+  formRef.value?.resetFields()
+}
+</script>
diff --git a/src/views/ai/model/chatRole/index.vue b/src/views/ai/model/chatRole/index.vue
new file mode 100644
index 00000000..e870a556
--- /dev/null
+++ b/src/views/ai/model/chatRole/index.vue
@@ -0,0 +1,187 @@
+<template>
+  <ContentWrap>
+    <!-- 搜索工作栏 -->
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
+      <el-form-item label="角色名称" prop="name">
+        <el-input
+          v-model="queryParams.name"
+          placeholder="请输入角色名称"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="角色类别" prop="category">
+        <el-input
+          v-model="queryParams.category"
+          placeholder="请输入角色类别"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="是否公开" prop="publicStatus">
+        <el-select
+          v-model="queryParams.publicStatus"
+          placeholder="请选择是否公开"
+          clearable
+          class="!w-240px"
+        >
+          <el-option
+            v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+        <el-button
+          type="primary"
+          plain
+          @click="openForm('create')"
+          v-hasPermi="['ai:chat-role:create']"
+        >
+          <Icon icon="ep:plus" class="mr-5px" /> 新增
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
+      <el-table-column label="角色名称" align="center" prop="name" />
+      <el-table-column label="绑定模型" align="center" prop="modelName" />
+      <el-table-column label="角色头像" align="center" prop="avatar">
+        <template #default="scope">
+          <el-image :src="scope?.row.avatar" class="w-32px h-32px" />
+        </template>
+      </el-table-column>
+      <el-table-column label="角色类别" align="center" prop="category" />
+      <el-table-column label="角色描述" align="center" prop="description" />
+      <el-table-column label="角色设定" align="center" prop="systemMessage" />
+      <el-table-column label="是否公开" align="center" prop="publicStatus">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.publicStatus" />
+        </template>
+      </el-table-column>
+      <el-table-column label="状态" align="center" prop="status">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
+        </template>
+      </el-table-column>
+      <el-table-column label="角色排序" align="center" prop="sort" />
+      <el-table-column label="操作" align="center">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openForm('update', scope.row.id)"
+            v-hasPermi="['ai:chat-role:update']"
+          >
+            编辑
+          </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="handleDelete(scope.row.id)"
+            v-hasPermi="['ai:chat-role:delete']"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </ContentWrap>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <ChatRoleForm ref="formRef" @success="getList" />
+</template>
+
+<script setup lang="ts">
+import { getBoolDictOptions, DICT_TYPE } from '@/utils/dict'
+import { ChatRoleApi, ChatRoleVO } from '@/api/ai/model/chatRole'
+import ChatRoleForm from './ChatRoleForm.vue'
+
+/** AI 聊天角色 列表 */
+defineOptions({ name: 'AiChatRole' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const list = ref<ChatRoleVO[]>([]) // 列表的数据
+const total = ref(0) // 列表的总页数
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  name: undefined,
+  category: undefined,
+  publicStatus: true
+})
+const queryFormRef = ref() // 搜索的表单
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await ChatRoleApi.getChatRolePage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await ChatRoleApi.deleteChatRole(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+})
+</script>
diff --git a/src/views/bpm/oa/leave/index.vue b/src/views/bpm/oa/leave/index.vue
index bd41104a..2cb53247 100644
--- a/src/views/bpm/oa/leave/index.vue
+++ b/src/views/bpm/oa/leave/index.vue
@@ -83,7 +83,7 @@
       <el-table-column align="center" label="申请编号" prop="id" />
       <el-table-column align="center" label="状态" prop="result">
         <template #default="scope">
-          <dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="scope.row.result" />
+          <dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="scope.row.status" />
         </template>
       </el-table-column>
       <el-table-column
diff --git a/src/views/crm/statistics/performance/components/ContractCountPerformance.vue b/src/views/crm/statistics/performance/components/ContractCountPerformance.vue
index f911bb2d..fa5a897b 100644
--- a/src/views/crm/statistics/performance/components/ContractCountPerformance.vue
+++ b/src/views/crm/statistics/performance/components/ContractCountPerformance.vue
@@ -1,5 +1,4 @@
 <!-- 员工业绩统计 -->
-<!-- TODO @scholar:参考 ReceivablePricePerformance 建议改 -->
 <template>
   <!-- Echarts图 -->
   <el-card shadow="never">
@@ -64,13 +63,13 @@ const echartsOption = reactive<EChartsOption>({
       data: []
     },
     {
-      name: '同比增长率(%)',
+      name: '环比增长率(%)',
       type: 'line',
       yAxisIndex: 1,
       data: []
     },
     {
-      name: '环比增长率(%)',
+      name: '同比增长率(%)',
       type: 'line',
       yAxisIndex: 1,
       data: []
@@ -173,7 +172,9 @@ const loadData = async () => {
       (s: StatisticsPerformanceRespVO) => s.lastMonthCount
     )
     echartsOption.series[3]['data'] = performanceList.map((s: StatisticsPerformanceRespVO) =>
-      s.lastMonthCount !== 0 ? ((s.currentMonthCount / s.lastMonthCount) * 100).toFixed(2) : 'NULL'
+      s.lastMonthCount !== 0
+        ? (((s.currentMonthCount - s.lastMonthCount) / s.lastMonthCount) * 100).toFixed(2)
+        : 'NULL'
     )
   }
   if (echartsOption.series && echartsOption.series[2] && echartsOption.series[2]['data']) {
@@ -181,7 +182,9 @@ const loadData = async () => {
       (s: StatisticsPerformanceRespVO) => s.lastYearCount
     )
     echartsOption.series[4]['data'] = performanceList.map((s: StatisticsPerformanceRespVO) =>
-      s.lastYearCount !== 0 ? ((s.currentMonthCount / s.lastYearCount) * 100).toFixed(2) : 'NULL'
+      s.lastYearCount !== 0
+        ? (((s.currentMonthCount - s.lastYearCount) / s.lastYearCount) * 100).toFixed(2)
+        : 'NULL'
     )
   }
 
@@ -197,8 +200,8 @@ const tableData = reactive([
   { title: '当月合同数量统计(个)' },
   { title: '上月合同数量统计(个)' },
   { title: '去年当月合同数量统计(个)' },
-  { title: '同比增长率(%)' },
-  { title: '环比增长率(%)' }
+  { title: '环比增长率(%)' },
+  { title: '同比增长率(%)' }
 ])
 
 // 定义 convertListData 方法,数据行列转置,展示每月数据
@@ -214,9 +217,13 @@ const convertListData = () => {
     tableData[1]['prop' + index] = item.lastMonthCount
     tableData[2]['prop' + index] = item.lastYearCount
     tableData[3]['prop' + index] =
-      item.lastMonthCount !== 0 ? (item.currentMonthCount / item.lastMonthCount).toFixed(2) : 'NULL'
+      item.lastMonthCount !== 0
+        ? (((item.currentMonthCount - item.lastMonthCount) / item.lastMonthCount) * 100).toFixed(2)
+        : 'NULL'
     tableData[4]['prop' + index] =
-      item.lastYearCount !== 0 ? (item.currentMonthCount / item.lastYearCount).toFixed(2) : 'NULL'
+      item.lastYearCount !== 0
+        ? (((item.currentMonthCount - item.lastYearCount) / item.lastYearCount) * 100).toFixed(2)
+        : 'NULL'
   })
 }
 
diff --git a/src/views/crm/statistics/performance/components/ContractPricePerformance.vue b/src/views/crm/statistics/performance/components/ContractPricePerformance.vue
index f97b612c..dd52d9fb 100644
--- a/src/views/crm/statistics/performance/components/ContractPricePerformance.vue
+++ b/src/views/crm/statistics/performance/components/ContractPricePerformance.vue
@@ -1,5 +1,4 @@
 <!-- 员工业绩统计 -->
-<!-- TODO @scholar:参考 ReceivablePricePerformance 建议改 -->
 <template>
   <!-- Echarts图 -->
   <el-card shadow="never">
@@ -64,13 +63,13 @@ const echartsOption = reactive<EChartsOption>({
       data: []
     },
     {
-      name: '同比增长率(%)',
+      name: '环比增长率(%)',
       type: 'line',
       yAxisIndex: 1,
       data: []
     },
     {
-      name: '环比增长率(%)',
+      name: '同比增长率(%)',
       type: 'line',
       yAxisIndex: 1,
       data: []
@@ -173,7 +172,9 @@ const loadData = async () => {
       (s: StatisticsPerformanceRespVO) => s.lastMonthCount
     )
     echartsOption.series[3]['data'] = performanceList.map((s: StatisticsPerformanceRespVO) =>
-      s.lastMonthCount !== 0 ? ((s.currentMonthCount / s.lastMonthCount) * 100).toFixed(2) : 'NULL'
+      s.lastMonthCount !== 0
+        ? (((s.currentMonthCount - s.lastMonthCount) / s.lastMonthCount) * 100).toFixed(2)
+        : 'NULL'
     )
   }
   if (echartsOption.series && echartsOption.series[2] && echartsOption.series[2]['data']) {
@@ -181,7 +182,9 @@ const loadData = async () => {
       (s: StatisticsPerformanceRespVO) => s.lastYearCount
     )
     echartsOption.series[4]['data'] = performanceList.map((s: StatisticsPerformanceRespVO) =>
-      s.lastYearCount !== 0 ? ((s.currentMonthCount / s.lastYearCount) * 100).toFixed(2) : 'NULL'
+      s.lastYearCount !== 0
+        ? (((s.currentMonthCount - s.lastYearCount) / s.lastYearCount) * 100).toFixed(2)
+        : 'NULL'
     )
   }
 
@@ -197,8 +200,8 @@ const tableData = reactive([
   { title: '当月合同金额统计(元)' },
   { title: '上月合同金额统计(元)' },
   { title: '去年当月合同金额统计(元)' },
-  { title: '同比增长率(%)' },
-  { title: '环比增长率(%)' }
+  { title: '环比增长率(%)' },
+  { title: '同比增长率(%)' }
 ])
 
 // 定义 init 方法
@@ -214,9 +217,13 @@ const convertListData = () => {
     tableData[1]['prop' + index] = item.lastMonthCount
     tableData[2]['prop' + index] = item.lastYearCount
     tableData[3]['prop' + index] =
-      item.lastMonthCount !== 0 ? (item.currentMonthCount / item.lastMonthCount).toFixed(2) : 'NULL'
+      item.lastMonthCount !== 0
+        ? (((item.currentMonthCount - item.lastMonthCount) / item.lastMonthCount) * 100).toFixed(2)
+        : 'NULL'
     tableData[4]['prop' + index] =
-      item.lastYearCount !== 0 ? (item.currentMonthCount / item.lastYearCount).toFixed(2) : 'NULL'
+      item.lastYearCount !== 0
+        ? (((item.currentMonthCount - item.lastYearCount) / item.lastYearCount) * 100).toFixed(2)
+        : 'NULL'
   })
 }
 
diff --git a/src/views/crm/statistics/performance/components/ReceivablePricePerformance.vue b/src/views/crm/statistics/performance/components/ReceivablePricePerformance.vue
index 14f59909..169f074b 100644
--- a/src/views/crm/statistics/performance/components/ReceivablePricePerformance.vue
+++ b/src/views/crm/statistics/performance/components/ReceivablePricePerformance.vue
@@ -17,7 +17,6 @@
         :prop="item.prop"
         align="center"
       >
-        <!-- TODO @scholar:IDEA 爆红的处理 -->
         <template #default="scope">
           {{ scope.row[item.prop] }}
         </template>
@@ -64,13 +63,13 @@ const echartsOption = reactive<EChartsOption>({
       data: []
     },
     {
-      name: '同比增长率(%)',
+      name: '环比增长率(%)',
       type: 'line',
       yAxisIndex: 1,
       data: []
     },
     {
-      name: '环比增长率(%)',
+      name: '同比增长率(%)',
       type: 'line',
       yAxisIndex: 1,
       data: []
@@ -121,7 +120,6 @@ const echartsOption = reactive<EChartsOption>({
       type: 'value',
       name: '',
       axisTick: {
-        // TODO @scholar:IDEA 爆红的处理
         alignWithLabel: true,
         lineStyle: {
           width: 0
@@ -174,7 +172,9 @@ const loadData = async () => {
       (s: StatisticsPerformanceRespVO) => s.lastMonthCount
     )
     echartsOption.series[3]['data'] = performanceList.map((s: StatisticsPerformanceRespVO) =>
-      s.lastMonthCount !== 0 ? ((s.currentMonthCount / s.lastMonthCount) * 100).toFixed(2) : 'NULL'
+      s.lastMonthCount !== 0
+        ? (((s.currentMonthCount - s.lastMonthCount) / s.lastMonthCount) * 100).toFixed(2)
+        : 'NULL'
     )
   }
   if (echartsOption.series && echartsOption.series[2] && echartsOption.series[1]['data']) {
@@ -182,7 +182,9 @@ const loadData = async () => {
       (s: StatisticsPerformanceRespVO) => s.lastYearCount
     )
     echartsOption.series[4]['data'] = performanceList.map((s: StatisticsPerformanceRespVO) =>
-      s.lastYearCount !== 0 ? ((s.currentMonthCount / s.lastYearCount) * 100).toFixed(2) : 'NULL'
+      s.lastYearCount !== 0
+        ? (((s.currentMonthCount - s.lastYearCount) / s.lastYearCount) * 100).toFixed(2)
+        : 'NULL'
     )
   }
 
@@ -193,14 +195,13 @@ const loadData = async () => {
 }
 
 // 初始化数据
-// TODO @scholar:加个 as any[],避免 idea 爆红
-const columnsData = reactive([] as any[])
+const columnsData = reactive([])
 const tableData = reactive([
   { title: '当月回款金额统计(元)' },
   { title: '上月回款金额统计(元)' },
   { title: '去年当月回款金额统计(元)' },
-  { title: '同比增长率(%)' },
-  { title: '环比增长率(%)' }
+  { title: '环比增长率(%)' },
+  { title: '同比增长率(%)' }
 ])
 
 // 定义 init 方法
@@ -215,11 +216,14 @@ const convertListData = () => {
     tableData[0]['prop' + index] = item.currentMonthCount
     tableData[1]['prop' + index] = item.lastMonthCount
     tableData[2]['prop' + index] = item.lastYearCount
-    // TODO @scholar:百分比,使用 erpCalculatePercentage 直接计算;如果是 0,则返回 0,统一就好哈;
     tableData[3]['prop' + index] =
-      item.lastMonthCount !== 0 ? (item.currentMonthCount / item.lastMonthCount).toFixed(2) : 'NULL'
+      item.lastMonthCount !== 0
+        ? (((item.currentMonthCount - item.lastMonthCount) / item.lastMonthCount) * 100).toFixed(2)
+        : 'NULL'
     tableData[4]['prop' + index] =
-      item.lastYearCount !== 0 ? (item.currentMonthCount / item.lastYearCount).toFixed(2) : 'NULL'
+      item.lastYearCount !== 0
+        ? (((item.currentMonthCount - item.lastYearCount) / item.lastYearCount) * 100).toFixed(2)
+        : 'NULL'
   })
 }
 
diff --git a/src/views/crm/statistics/performance/index.vue b/src/views/crm/statistics/performance/index.vue
index 4a443c55..822afec9 100644
--- a/src/views/crm/statistics/performance/index.vue
+++ b/src/views/crm/statistics/performance/index.vue
@@ -73,7 +73,7 @@
 import * as DeptApi from '@/api/system/dept'
 import * as UserApi from '@/api/system/user'
 import { useUserStore } from '@/store/modules/user'
-import { beginOfDay, formatDate } from '@/utils/formatTime'
+import { beginOfDay, endOfDay, formatDate } from '@/utils/formatTime'
 import { defaultProps, handleTree } from '@/utils/tree'
 import ContractCountPerformance from './components/ContractCountPerformance.vue'
 import ContractPricePerformance from './components/ContractPricePerformance.vue'
@@ -85,8 +85,8 @@ const queryParams = reactive({
   deptId: useUserStore().getUser.deptId,
   userId: undefined,
   times: [
-    // 默认显示当年的数据
-    formatDate(beginOfDay(new Date(new Date().getTime() - 3600 * 1000 * 24 * 7)))
+    formatDate(beginOfDay(new Date(new Date().getFullYear(), 0, 1))),
+    formatDate(endOfDay(new Date(new Date().getFullYear(), 11, 31)))
   ]
 })
 
@@ -100,31 +100,20 @@ const userListByDeptId = computed(() =>
     : []
 )
 
-// TODO @scholar:改成尾注释,保证 vue 内容短一点;变量名小写
 // 活跃标签
 const activeTab = ref('ContractCountPerformance')
-// 1.员工合同数量统计
-const ContractCountPerformanceRef = ref()
-// 2.员工合同金额统计
-const ContractPricePerformanceRef = ref()
-// 3.员工回款金额统计
-const ReceivablePricePerformanceRef = ref()
+const ContractCountPerformanceRef = ref() // 员工合同数量统计
+const ContractPricePerformanceRef = ref() // 员工合同金额统计
+const ReceivablePricePerformanceRef = ref() // 员工回款金额统计
 
 /** 搜索按钮操作 */
 const handleQuery = () => {
   // 从 queryParams.times[0] 中获取到了年份
   const selectYear = parseInt(queryParams.times[0])
+  queryParams.times[0] = formatDate(beginOfDay(new Date(selectYear, 0, 1)))
+  queryParams.times[1] = formatDate(endOfDay(new Date(selectYear, 11, 31)))
 
-  // 创建一个新的 Date 对象,设置为指定的年份的第一天
-  const fullDate = new Date(selectYear, 0, 1, 0, 0, 0)
-
-  // 将完整的日期时间格式化为需要的字符串形式,比如 2004-01-01 00:00:00
-  // TODO @scholar:看看,是不是可以使用 year 哈
-  queryParams.times[0] = `${fullDate.getFullYear()}-${String(fullDate.getMonth() + 1).padStart(
-    2,
-    '0'
-  )}-${String(fullDate.getDate()).padStart(2, '0')} ${String(fullDate.getHours()).padStart(2, '0')}:${String(fullDate.getMinutes()).padStart(2, '0')}:${String(fullDate.getSeconds()).padStart(2, '0')}`
-
+  // 执行查询
   switch (activeTab.value) {
     case 'ContractCountPerformance':
       ContractCountPerformanceRef.value?.loadData?.()
diff --git a/src/views/system/dept/index.vue b/src/views/system/dept/index.vue
index e99d7f8a..4757e5c0 100644
--- a/src/views/system/dept/index.vue
+++ b/src/views/system/dept/index.vue
@@ -8,7 +8,7 @@
       :inline="true"
       label-width="68px"
     >
-      <el-form-item label="部门名称" prop="title">
+      <el-form-item label="部门名称" prop="name">
         <el-input
           v-model="queryParams.name"
           placeholder="请输入部门名称"