index.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802
  1. <template>
  2. <div class="dialog-overlay">
  3. <!-- 左侧折叠区域 -->
  4. <div class="left-side" :class="{ collapsed: isFold }" id="leftSide">
  5. <div
  6. class="addBtn"
  7. :style="{
  8. backgroundImage: `url(${isFold ? '/src/assets/images/vent/home/add.svg' : ''})`,
  9. backgroundColor: isFold ? '' : '#2cb6ff',
  10. width: isFold ? '20px' : 'auto',
  11. }"
  12. @click="addNew"
  13. >
  14. <span
  15. class="btn-text-bg"
  16. :style="{
  17. backgroundImage: `url(${!isFold ? '/src/assets/images/vent/home/addB.svg' : ''})`,
  18. }"
  19. ></span>
  20. <span v-if="!isFold" class="btn-text">添加新对话</span>
  21. </div>
  22. <div class="divider0"></div>
  23. <div
  24. v-if="isFold"
  25. class="historyBtn"
  26. :style="{ backgroundImage: `url(${isFold ? '/src/assets/images/vent/home/history.svg' : '/src/assets/images/vent/home/history.svg'})` }"
  27. @click="addNew"
  28. ></div>
  29. <div v-else class="historyBtn1">
  30. <span
  31. class="btn-text-bg"
  32. :style="{
  33. backgroundImage: `url(${!isFold ? '/src/assets/images/vent/home/history.svg' : ''})`,
  34. }"
  35. ></span>
  36. <span v-if="!isFold" class="btn-text">历史对话</span>
  37. <a-list style="width: 110px" :split="false" :data-source="historySessions" :scroll="200" class="custom-list">
  38. <template #renderItem="{ item }">
  39. <a-list-item
  40. :style="{
  41. padding: '8px 10px 0 8px',
  42. color: '#5e7081',
  43. fontSize: '10px',
  44. position: 'relative', // 新增定位
  45. }"
  46. @click="sessionsHistory(item.id)"
  47. >
  48. <!-- 新增flex布局容器 -->
  49. <div style="display: flex; justify-content: space-between; width: 100%">
  50. <div v-if="editingId !== item.id" class="text-container">
  51. <span class="edit-text">{{ item.title || '新会话' }}</span>
  52. <edit-outlined class="edit-icon" @click="startEditing(item)" />
  53. </div>
  54. <!-- 输入框 -->
  55. <a-input
  56. size="small"
  57. v-else
  58. v-model:value="editText"
  59. v-focus
  60. @blur="handleSave(item)"
  61. @keyup.enter="handleSave(item)"
  62. class="edit-input"
  63. />
  64. </div>
  65. </a-list-item>
  66. </template>
  67. </a-list>
  68. </div>
  69. <div
  70. class="foldBtn"
  71. :style="{ backgroundImage: `url(${isFold ? '/src/assets/images/vent/home/Fold.svg' : '/src/assets/images/vent/home/unfold.svg'})` }"
  72. @click="fold"
  73. ></div>
  74. </div>
  75. <!-- 右侧对话框 -->
  76. <div class="right-side">
  77. <div class="input-content">
  78. <!-- 对话区域 -->
  79. <div class="dialog-area">
  80. <div v-for="message in sortedMessages" :key="message.id" :class="['message-item', message.type]">
  81. <!-- 用户提问样式 -->
  82. <div v-if="message.type === 'user'" class="ask-message">
  83. <span>{{ message.content }}</span>
  84. </div>
  85. <!-- 系统回答样式 -->
  86. <div v-else class="system-message">
  87. <div class="answerIcon"></div>
  88. <div class="answer-message">
  89. <div v-html="formatMessage(message.content)"></div>
  90. </div>
  91. </div>
  92. </div>
  93. </div>
  94. <!-- 文本输入区域 -->
  95. <div v-if="spinning" class="thinking-area">
  96. <span style="color: #fff">思考中···</span>
  97. <a-spin :spinning="spinning"></a-spin>
  98. </div>
  99. <div class="input-area">
  100. <textarea v-model="inputText" placeholder="请输入你的问题"> </textarea>
  101. <!-- 底部操作栏 -->
  102. <div class="action-bar">
  103. <!-- 文件上传按钮 -->
  104. <label class="upload-btn">
  105. <div class="send-file" />
  106. <span class="divider"> | </span>
  107. <div class="send-img" />
  108. </label>
  109. <!-- 发送按钮 -->
  110. <div class="send-btn" @click="handleSend"></div>
  111. </div>
  112. </div>
  113. </div>
  114. </div>
  115. </div>
  116. </template>
  117. <script lang="ts" setup>
  118. import { ref, onMounted, unref, nextTick, computed } from 'vue';
  119. import { useUserStore } from '/@/store/modules/user';
  120. import { EditOutlined } from '@ant-design/icons-vue';
  121. // 响应式变量声明
  122. const dialogVisible = ref(false);
  123. const isFold = ref(true); // 是否折叠
  124. const inputText = ref(''); // 输入框内容
  125. const historySessions = ref([]); // 消会话历史
  126. const spinning = ref(false); // 加载状态
  127. const systemMessage = ref(''); // 系统返回信息
  128. const session_id = ref(''); // 会话id
  129. const hasCreated = ref(false); // 标志位,防止重复调用create接口
  130. const hasAdd = ref(false); // 标志位,防止重复调用create接口
  131. const userStore = useUserStore(); //获取用户信息
  132. const editingId = ref<number | null>(null);
  133. const editText = ref('');
  134. interface ListItem {
  135. id: number;
  136. title?: string;
  137. }
  138. let userId = unref(userStore.getUserInfo).id;
  139. // const userId = ref(0);
  140. type MessageItem = {
  141. id: string; // 唯一标识(可用时间戳生成)
  142. type: 'user' | 'system';
  143. content: string;
  144. timestamp: number; // 排序依据
  145. };
  146. const messageList = ref<MessageItem[]>([]);
  147. const sortedMessages = computed(() => {
  148. return messageList.value.sort((a, b) => a.timestamp - b.timestamp);
  149. });
  150. const vFocus = {
  151. mounted: (el: HTMLElement) => el.querySelector('input')?.focus(),
  152. };
  153. const scrollToBottom = () => {
  154. const dialogArea = document.querySelector('.dialog-area');
  155. if (dialogArea) {
  156. dialogArea.scrollTop = dialogArea.scrollHeight;
  157. }
  158. };
  159. const openMenu = () => {
  160. dialogVisible.value = !dialogVisible.value;
  161. if (dialogVisible.value) {
  162. // addNew();
  163. hasCreated.value = true;
  164. }
  165. };
  166. const fold = () => {
  167. isFold.value = !isFold.value;
  168. if (!isFold.value) {
  169. sessionsHistoryList();
  170. }
  171. };
  172. //创建新对话
  173. async function addNew() {
  174. hasAdd.value = !hasAdd.value;
  175. const params = {
  176. user_id: userId,
  177. };
  178. let response = await fetch('http://182.92.126.35:6005/sessions/create', {
  179. method: 'post',
  180. headers: {
  181. 'Content-Type': 'application/json',
  182. },
  183. body: JSON.stringify(params),
  184. });
  185. const data = await response.json();
  186. session_id.value = data.id;
  187. messageList.value = [];
  188. }
  189. //编辑标题
  190. const startEditing = (item: ListItem) => {
  191. editingId.value = item.id;
  192. editText.value = item.title || '';
  193. };
  194. // 保存修改
  195. const handleSave = async (item: ListItem) => {
  196. const params = {
  197. chat_session_id: item.id,
  198. new_title: editText.value,
  199. };
  200. try {
  201. let response = await fetch('http://182.92.126.35:6005/sessions/change_title', {
  202. method: 'POST',
  203. headers: {
  204. 'Content-Type': 'application/json',
  205. },
  206. body: JSON.stringify(params),
  207. });
  208. if (!response.ok) {
  209. throw new Error('Network response was not ok');
  210. }
  211. item.title = editText.value;
  212. } catch (error) {
  213. console.error('保存失败:', error);
  214. }
  215. editingId.value = null;
  216. };
  217. //获取消息列表
  218. async function handleSend() {
  219. if (session_id.value === '') {
  220. await addNew();
  221. createSessionTitle({ session_id: session_id.value, title: inputText.value });
  222. sendMessage1();
  223. } else {
  224. createSessionTitle({ session_id: session_id.value, title: inputText.value });
  225. sendMessage1();
  226. }
  227. }
  228. //发送消息
  229. async function sendMessage() {
  230. spinning.value = true;
  231. // 添加用户消息
  232. messageList.value.push({
  233. id: `user_${Date.now()}`,
  234. type: 'user',
  235. content: inputText.value,
  236. timestamp: Date.now(),
  237. });
  238. const params = {
  239. chat_session_id: session_id.value,
  240. prompt: inputText.value,
  241. ref_file_ids: [],
  242. thinking_enabled: false,
  243. };
  244. inputText.value = ''; // 清空输入框
  245. //将用户输入的内容发送到后端
  246. try {
  247. // 将用户输入的内容发送到后端
  248. let response = await fetch('http://182.92.126.35:6005/chat', {
  249. method: 'POST',
  250. headers: {
  251. 'Content-Type': 'application/json',
  252. },
  253. body: JSON.stringify(params),
  254. });
  255. if (!response.ok) {
  256. throw new Error('Network response was not ok');
  257. }
  258. const data = await response.json();
  259. const assistantReply = data.reply.content; // 获取助手回复
  260. // formatMessage(assistantReply);
  261. systemMessage.value = assistantReply;
  262. // 添加系统回答
  263. messageList.value.push({
  264. id: `system_${Date.now()}`,
  265. type: 'system',
  266. content: systemMessage.value,
  267. timestamp: Date.now(),
  268. });
  269. } catch (error) {
  270. // 请求失败时设置系统消息为"服务器异常"
  271. systemMessage.value = '服务器异常';
  272. console.error('请求失败:', error);
  273. } finally {
  274. spinning.value = false; // 无论请求成功与否,都停止加载指示器
  275. }
  276. }
  277. //发送消息 流式响应
  278. const sendMessage1 = async () => {
  279. spinning.value = true; // 开始加载
  280. messageList.value.push({
  281. id: `user_${Date.now()}`,
  282. type: 'user',
  283. content: inputText.value,
  284. timestamp: Date.now(),
  285. });
  286. // 构造请求参数
  287. const params = {
  288. chat_session_id: session_id.value, // 替换为实际的会话 ID
  289. prompt: inputText.value,
  290. ref_file_ids: [],
  291. thinking_enabled: false,
  292. };
  293. inputText.value = ''; // 清空输入框
  294. try {
  295. // 发送 POST 请求
  296. const response = await fetch('http://182.92.126.35:6005/chat_stream', {
  297. method: 'POST',
  298. headers: {
  299. 'Content-Type': 'application/json',
  300. },
  301. body: JSON.stringify(params),
  302. });
  303. // 检查响应是否成功
  304. if (!response.ok) {
  305. throw new Error('Network response was not ok');
  306. }
  307. // 获取可读流
  308. const reader = response.body.getReader();
  309. // 创建一条新的消息对象
  310. const newMessage = {
  311. id: `response_${Date.now()}`,
  312. type: 'response', // 消息类型
  313. content: '',
  314. timestamp: Date.now(), // 时间戳用来排序
  315. };
  316. // 将新消息添加到消息列表
  317. messageList.value.push(newMessage);
  318. // 读取流式数据
  319. while (true) {
  320. const { done, value } = await reader.read();
  321. if (done) {
  322. console.log('Stream complete');
  323. break;
  324. }
  325. // 将流数据转换为字符串
  326. const chunk = new TextDecoder().decode(value);
  327. console.log('Received chunk:', chunk);
  328. const jsonRegex = /{.*?}/g;
  329. const matches = chunk.match(jsonRegex);
  330. if (matches) {
  331. matches.forEach((match) => {
  332. try {
  333. const data = JSON.parse(match);
  334. if (data.type === 'text') {
  335. // 找到当前消息对象并更新 content
  336. const targetMessage = messageList.value.find((msg) => msg.id === newMessage.id);
  337. if (targetMessage) {
  338. targetMessage.content += data.content; // 追加内容
  339. scrollToBottom();
  340. }
  341. }
  342. } catch (error) {
  343. console.error('Failed to parse JSON:', error);
  344. }
  345. });
  346. }
  347. }
  348. } catch (error) {
  349. // 请求失败时设置系统消息
  350. systemMessage.value = '服务器异常';
  351. messageList.value.push({
  352. id: `system_${Date.now()}`,
  353. type: 'system',
  354. content: systemMessage.value,
  355. timestamp: Date.now(),
  356. });
  357. console.error('请求失败:', error);
  358. } finally {
  359. spinning.value = false; // 停止加载
  360. }
  361. };
  362. //创建标题
  363. async function createSessionTitle({ session_id, title }) {
  364. const params = {
  365. chat_session_id: session_id,
  366. prompt: title,
  367. };
  368. let response = await fetch('http://182.92.126.35:6005/sessions/title', {
  369. method: 'post',
  370. headers: {
  371. 'Content-Type': 'application/json',
  372. },
  373. body: JSON.stringify(params),
  374. });
  375. const data = await response.json();
  376. }
  377. //获取会话历史
  378. async function sessionsHistoryList() {
  379. const params = {
  380. user_id: userId,
  381. };
  382. let response = await fetch(`http://182.92.126.35:6005/sessions`, {
  383. method: 'post',
  384. headers: {
  385. 'Content-Type': 'application/json',
  386. },
  387. body: JSON.stringify(params),
  388. });
  389. const data = await response.json();
  390. historySessions.value = data.chat_sessions;
  391. }
  392. //获取具体会话记录
  393. async function sessionsHistory(id: string) {
  394. let response = await fetch(`http://182.92.126.35:6005/sessions/history_chat/?chat_session_id=${id}`, {
  395. method: 'get',
  396. headers: {
  397. 'Content-Type': 'application/json',
  398. },
  399. });
  400. const data = await response.json();
  401. if (data.chat_messages.length > 0) {
  402. messageList.value = [];
  403. data.chat_messages.forEach((item: any) => {
  404. // role== user 用户提问
  405. if (item.role === 'user') {
  406. messageList.value.push({
  407. id: `user_${Date.now()}`,
  408. type: 'user',
  409. content: item.content,
  410. timestamp: Date.now(),
  411. });
  412. } else {
  413. // role== assistant 机器回答
  414. messageList.value.push({
  415. id: `system_${Date.now()}`,
  416. type: 'system',
  417. content: item.content,
  418. timestamp: Date.now(),
  419. });
  420. }
  421. });
  422. }
  423. }
  424. //格式化消息
  425. function formatMessage(text: string) {
  426. let formatted = text
  427. // 处理换行
  428. .replace(/\n\n/g, '<br>')
  429. .replace(/\n###/g, '<br> ')
  430. .replace(/###/g, '')
  431. .replace(/---/g, '')
  432. // 处理粗体
  433. .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
  434. // 处理斜体
  435. .replace(/\*(.*?)\*/g, '<em>$1</em>')
  436. // 处理行内代码
  437. .replace(/`([^`]+)`/g, '<code>$1</code>');
  438. return formatted;
  439. }
  440. // 初始化按钮定位
  441. onMounted(() => {
  442. sessionsHistoryList();
  443. });
  444. </script>
  445. <style lang="less" scoped>
  446. @keyframes menuShow {
  447. 0% {
  448. width: 0;
  449. height: 0;
  450. }
  451. 100% {
  452. width: 480px;
  453. height: 100vh;
  454. }
  455. }
  456. .custom-list {
  457. height: 360px;
  458. overflow-y: auto;
  459. }
  460. /* 穿透组件作用域 */
  461. ::v-deep .custom-list {
  462. scrollbar-width: thin;
  463. scrollbar-color: #1890ff #f0f0f0;
  464. &::-webkit-scrollbar {
  465. width: 4px;
  466. height: 6px;
  467. }
  468. &::-webkit-scrollbar-thumb {
  469. background: #1890ff;
  470. border-radius: 4px;
  471. }
  472. &::-webkit-scrollbar-track {
  473. background: #f0f0f0;
  474. border-radius: 4px;
  475. }
  476. }
  477. ::v-deep .zxm-list-items {
  478. color: #1890ff;
  479. }
  480. ::v-deep .zxm-list-item:hover {
  481. text-decoration: underline;
  482. color: #1890ff !important;
  483. }
  484. .text-container {
  485. display: flex;
  486. align-items: center;
  487. width: 100%;
  488. overflow: hidden;
  489. }
  490. .text-ellipsis {
  491. flex: 1;
  492. }
  493. .edit-text {
  494. overflow: hidden;
  495. text-overflow: ellipsis;
  496. white-space: nowrap;
  497. min-width: 0;
  498. }
  499. .edit-icon {
  500. flex-shrink: 0;
  501. cursor: pointer;
  502. margin-left: auto;
  503. }
  504. .edit-input {
  505. font-size: 10px;
  506. }
  507. .trigger-button {
  508. position: fixed;
  509. bottom: 10px;
  510. right: 10px;
  511. z-index: 1000000;
  512. .icon {
  513. width: 60px;
  514. height: 60px;
  515. position: relative;
  516. background-image: url('/@/assets/images/vent/home/wakeBtn.png');
  517. background-position: center;
  518. background-size: 100% 100%;
  519. }
  520. }
  521. .dialog-overlay {
  522. width: 32%;
  523. height: 55%;
  524. z-index: 999;
  525. display: flex;
  526. position: fixed;
  527. right: 90px;
  528. bottom: 20px;
  529. box-shadow: 0 0 3px 3px #1074c1;
  530. background-color: #09172c;
  531. }
  532. /* 遮罩层淡入淡出 */
  533. .fade-enter-active,
  534. .fade-leave-active {
  535. transition: opacity 0.3s;
  536. }
  537. .fade-enter-from,
  538. .fade-leave-to {
  539. opacity: 0;
  540. }
  541. /* 弹窗缩放动画 */
  542. .scale-enter-active,
  543. .scale-leave-active {
  544. transition: all 0.3s ease;
  545. }
  546. .scale-enter-from {
  547. transform: scale(0.5) translate(-50%, -50%);
  548. opacity: 0;
  549. }
  550. .scale-leave-to {
  551. transform: scale(1.2) translate(-50%, -50%);
  552. opacity: 0;
  553. }
  554. .left-side {
  555. background: #0c2842;
  556. transition: width 0.5s ease; /* 平滑过渡动画 */
  557. width: 120px; /* 展开时宽度 */
  558. position: relative; /* 用于按钮定位 */
  559. }
  560. .left-side.collapsed {
  561. width: 40px; /* 折叠时宽度 */
  562. }
  563. .addBtn {
  564. height: 30px;
  565. position: absolute;
  566. background-size: 100% 100%;
  567. background-position: center;
  568. padding: 2px;
  569. right: 10px;
  570. top: 10px;
  571. left: 10px;
  572. align-items: center;
  573. border-radius: 3px;
  574. cursor: pointer;
  575. }
  576. .btn-text-bg {
  577. width: 14px;
  578. height: 14px;
  579. position: absolute;
  580. background-size: 100% 100%;
  581. right: 10px;
  582. top: 9px;
  583. left: 10px;
  584. bottom: 10px;
  585. }
  586. .btn-text {
  587. margin-left: 3px;
  588. font-size: 12px;
  589. color: #fff;
  590. white-space: nowrap;
  591. margin-left: 30px;
  592. line-height: 26px;
  593. }
  594. .historyBtn {
  595. width: 20px;
  596. height: 20px;
  597. position: absolute;
  598. background-size: 100% 100%;
  599. background-position: center;
  600. padding: 2px;
  601. right: 10px;
  602. top: 100px;
  603. }
  604. .historyBtn1 {
  605. width: 20px;
  606. height: 20px;
  607. position: absolute;
  608. background-size: 100% 100%;
  609. background-position: center;
  610. left: 3px;
  611. top: 80px;
  612. }
  613. .divider0 {
  614. border-bottom: 1px solid #1074c1;
  615. width: auto;
  616. margin: 0 10px;
  617. height: 13%;
  618. display: block;
  619. background: transparent;
  620. }
  621. .foldBtn {
  622. width: 20px;
  623. height: 20px;
  624. position: absolute;
  625. background-size: 100% 100%;
  626. background-position: center;
  627. padding: 2px;
  628. right: 10px;
  629. bottom: 10px;
  630. cursor: pointer;
  631. }
  632. .right-side {
  633. flex: 1; /* 占据剩余空间 */
  634. background: #09172c;
  635. }
  636. .input-content {
  637. display: flex;
  638. flex-direction: column;
  639. justify-content: flex-end; /* 内容底部对齐 */
  640. height: 100%;
  641. padding: 20px; /* 统一内边距 */
  642. }
  643. .ask-message {
  644. align-self: flex-end;
  645. float: right;
  646. max-width: 70%;
  647. padding: 10px;
  648. margin: 10px;
  649. border-radius: 5px;
  650. color: #fff;
  651. background: #0c2842;
  652. align-self: flex-end; /* 右侧对齐‌:ml-citation{ref="2" data="citationList"} */
  653. }
  654. .answer {
  655. display: flex;
  656. flex-direction: row;
  657. }
  658. .answerIcon {
  659. flex-shrink: 0;
  660. margin-top: 10px;
  661. width: 35px;
  662. height: 35px;
  663. background-image: url('/@/assets/images/vent/home/answerIcon.svg');
  664. background-size: 100% 100%;
  665. }
  666. .answer-message {
  667. float: left;
  668. padding: 10px;
  669. margin: 10px;
  670. border-radius: 5px;
  671. color: #fff;
  672. background: #0c2842;
  673. }
  674. /** 系统返回信息**/
  675. .system-message {
  676. display: flex;
  677. flex-direction: row;
  678. align-self: flex-start;
  679. width: 100%;
  680. padding: 12px;
  681. display: flex;
  682. }
  683. .answerIcon {
  684. margin-top: 10px;
  685. width: 35px;
  686. height: 35px;
  687. background-image: url('/@/assets/images/vent/home/answerIcon.svg');
  688. background-size: 100% 100%;
  689. }
  690. .answer-message {
  691. float: left;
  692. width: 100%;
  693. padding: 10px;
  694. margin: 10px;
  695. border-radius: 5px;
  696. color: #fff;
  697. background: #0c2842;
  698. }
  699. .dialog-area {
  700. flex: 1; /* 占据剩余空间 */
  701. gap: 50px; /* 消息块间隔统一控制 */
  702. overflow-y: auto; /* 垂直滚动条 */
  703. margin-bottom: 10px;
  704. }
  705. .loading-wrapper,
  706. .content-wrapper {
  707. min-height: 40px;
  708. }
  709. .message-item.user {
  710. margin-bottom: 50px;
  711. }
  712. .input-area {
  713. background-color: #043256 !important;
  714. display: flex;
  715. flex-direction: column;
  716. gap: 10px;
  717. }
  718. textarea {
  719. background-color: #043256 !important;
  720. width: 100%;
  721. height: 40px;
  722. border: none;
  723. resize: none;
  724. outline: none;
  725. overflow: hidden;
  726. padding: 10px; /* 统一内边距 */
  727. color: #fff;
  728. }
  729. .action-bar {
  730. display: flex;
  731. align-items: center;
  732. gap: 12px;
  733. }
  734. .upload-btn {
  735. display: flex;
  736. cursor: pointer;
  737. padding: 6px 12px;
  738. }
  739. .divider {
  740. color: #ccc;
  741. font-weight: 300;
  742. margin: 0 10px;
  743. }
  744. .send-file {
  745. width: 20px;
  746. height: 20px;
  747. background-image: url('/@/assets/images/vent/home/sendFile.svg');
  748. background-size: 100% 100%;
  749. border-radius: 4px;
  750. cursor: pointer;
  751. }
  752. .send-img {
  753. width: 20px;
  754. height: 20px;
  755. background-image: url('/@/assets/images/vent/home/sendImg.svg');
  756. background-size: 100% 100%;
  757. border-radius: 4px;
  758. cursor: pointer;
  759. }
  760. .send-btn {
  761. width: 20px;
  762. height: 20px;
  763. margin-left: auto;
  764. margin-right: 10px;
  765. background-color: #1074c1;
  766. background-image: url('/@/assets/images/vent/home/send.svg');
  767. background-position: center;
  768. background-size: 100% 100%;
  769. border-radius: 2px;
  770. cursor: pointer;
  771. }
  772. </style>