From c163da8e8aefba43419614c2bd57867c3b99591b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=95=E5=98=89=E6=82=A6?= Date: Mon, 19 May 2025 06:58:57 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20:sparkles:=20=E5=B8=83=E5=B1=80?= =?UTF-8?q?=EF=BC=8C=E6=A0=B7=E5=BC=8F=E7=BB=9F=E4=B8=80=EF=BC=8C=E9=9B=86?= =?UTF-8?q?=E6=88=90=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .commitlintrc.cjs | 145 +++++-- .editorconfig | 43 ++ .eslintrc-auto-import.json | 4 +- .prettierignore | 9 - .stylelintignore | 4 - .stylelintrc.cjs | 40 -- .vscode/extensions.json | 16 +- .vscode/settings.json | 8 +- LICENSE | 2 +- package.json | 8 +- src/App.vue | 2 +- src/assets/icons/svg/ctrl+k.svg | 1 + src/assets/images/logo.png | Bin 0 -> 15776 bytes src/components/IconSelect/index.vue | 33 +- src/components/Popover/index.vue | 407 ++++++++++++++++++ src/config/design.ts | 23 +- src/config/index.ts | 6 + src/hooks/useCollapseToggle.ts | 61 +++ src/hooks/useWindowWidthObserver.ts | 45 +- src/layouts/LayoutVertical/index.vue | 75 ++-- src/layouts/components/Aside/index.vue | 150 ++++++- .../components/Header/components/Avatar.vue | 121 ++++++ .../components/Header/components/Collapse.vue | 43 +- .../Header/components/CreateChat.vue | 15 + .../components/Header/components/LoginBtn.vue | 17 + .../Header/components/TitleEditing.vue | 54 +++ src/layouts/components/Header/index.vue | 64 ++- src/pages/chat/home/index.vue | 83 ++++ src/pages/chat/index.vue | 6 +- src/router/index.ts | 12 +- src/store/modules/design.ts | 33 +- src/styles/btn-style.scss | 35 ++ src/styles/index.scss | 1 + src/styles/var.scss | 9 + types/auto-imports.d.ts | 135 +++--- types/components.d.ts | 11 +- vite.config.ts | 8 + 37 files changed, 1429 insertions(+), 300 deletions(-) create mode 100644 .editorconfig delete mode 100644 .prettierignore delete mode 100644 .stylelintignore delete mode 100644 .stylelintrc.cjs create mode 100644 src/assets/icons/svg/ctrl+k.svg create mode 100644 src/assets/images/logo.png create mode 100644 src/components/Popover/index.vue create mode 100644 src/hooks/useCollapseToggle.ts create mode 100644 src/layouts/components/Header/components/Avatar.vue create mode 100644 src/layouts/components/Header/components/CreateChat.vue create mode 100644 src/layouts/components/Header/components/LoginBtn.vue create mode 100644 src/layouts/components/Header/components/TitleEditing.vue create mode 100644 src/pages/chat/home/index.vue create mode 100644 src/styles/btn-style.scss create mode 100644 src/styles/var.scss diff --git a/.commitlintrc.cjs b/.commitlintrc.cjs index 812e548..26d9f14 100644 --- a/.commitlintrc.cjs +++ b/.commitlintrc.cjs @@ -1,77 +1,132 @@ -// .commitlintrc.js +// commitlint.config.js +const fs = require('node:fs') +const path = require('node:path') +const { execSync } = require('node:child_process') + +const scopes = fs + .readdirSync(path.resolve(__dirname, 'src'), { withFileTypes: true }) + .filter(dirent => dirent.isDirectory()) + .map(dirent => dirent.name.replace(/s$/, '')) + +// precomputed scope +const scopeComplete = execSync('git status --porcelain || true') + .toString() + .trim() + .split('\n') + .find(r => ~r.indexOf('M src')) + ?.replace(/(\/)/g, '%%') + ?.match(/src%%((\w|-)*)/)?.[1] + ?.replace(/s$/, '') + /** @type {import('cz-git').UserConfig} */ module.exports = { + ignores: [commit => commit.includes('init')], extends: ['@commitlint/config-conventional'], rules: { - // @see: https://commitlint.js.org/#/reference-rules + 'body-leading-blank': [2, 'always'], + 'footer-leading-blank': [1, 'always'], + 'header-max-length': [2, 'always', 108], + 'subject-empty': [2, 'never'], + 'type-empty': [2, 'never'], + 'subject-case': [0], 'type-enum': [ 2, 'always', - ['build', 'chore', 'ci', 'docs', 'feat', 'fix', 'perf', 'refactor', 'revert', 'style', 'test'] - ] + [ + 'feat', + 'fix', + 'perf', + 'style', + 'docs', + 'test', + 'refactor', + 'build', + 'ci', + 'chore', + 'revert', + 'wip', + 'workflow', + 'types', + 'release', + ], + ], }, prompt: { - alias: { fd: 'docs: fix typos' }, + /** @use `yarn commit :f` */ + alias: { + f: 'docs: fix typos', + r: 'docs: update README', + s: 'style: update code format', + b: 'build: bump dependencies', + c: 'chore: update config', + }, + customScopesAlign: !scopeComplete ? 'top' : 'bottom', + defaultScope: scopeComplete, + scopes: [...scopes, 'mock'], + allowEmptyIssuePrefixs: true, + allowCustomIssuePrefixs: true, messages: { - type: "选择你要提交的类型 | Select the type of change that you're committing:", - scope: '选择一个提交范围(可选)| Denote the SCOPE of this change (optional):', - customScope: '请输入自定义的提交范围 | Denote the SCOPE of this change:', - subject: '填写简短精炼的变更描述 | Write a SHORT, IMPERATIVE tense description of the change:\n', - body: '填写更加详细的变更描述(可选)。使用 "|" 换行 | Provide a LONGER description of the change (optional). Use "|" to break new line:\n', - breaking: - '列举非兼容性重大的变更(可选)。使用 "|" 换行 | List any BREAKING CHANGES (optional). Use "|" to break new line:\n', - footerPrefixesSelect: - '选择关联issue前缀(可选)| Select the ISSUES type of changeList by this change (optional):', - customFooterPrefix: '输入自定义issue前缀 | Input ISSUES prefix:', - footer: '列举关联issue (可选) 例如: #31, #I3244 | List any ISSUES by this change. E.g.: #31, #34:\n', - confirmCommit: '是否提交或修改commit ? | Are you sure you want to proceed with the commit above?' + type: '选择你要提交的类型 :', + scope: '选择一个提交范围(可选):', + customScope: '请输入自定义的提交范围 :', + subject: '填写简短精炼的变更描述 :\n', + body: '填写更加详细的变更描述(可选)。使用 "|" 换行 :\n', + breaking: '列举非兼容性重大的变更(可选)。使用 "|" 换行 :\n', + footerPrefixsSelect: '选择关联issue前缀(可选):', + customFooterPrefixs: '输入自定义issue前缀 :', + footer: '列举关联issue (可选) 例如: #31, #I3244 :\n', + confirmCommit: '是否提交或修改commit ?', }, types: [ - { value: 'feat', name: 'feat: 新增功能 | A new feature' }, - { value: 'fix', name: 'fix: 修复缺陷 | A bug fix' }, + { value: 'feat', name: 'feat: ✨ 新增功能 | A new feature', emoji: ':sparkles:' }, + { value: 'fix', name: 'fix: 🐛 修复缺陷 | A bug fix', emoji: ':bug:' }, { value: 'docs', - name: 'docs: 文档更新 | Documentation only changes' + name: 'docs: 📝 文档更新 | Documentation only changes', + emoji: ':memo:', }, { value: 'style', - name: 'style: 代码格式 | Changes that do not affect the meaning of the code' + name: 'style: 💄 代码格式 | Changes that do not affect the meaning of the code', + emoji: ':lipstick:', }, { value: 'refactor', - name: 'refactor: 代码重构 | A code change that neither fixes a bug nor adds a feature' + name: 'refactor: ♻️ 代码重构 | A code change that neither fixes a bug nor adds a feature', + emoji: ':recycle:', }, { value: 'perf', - name: 'perf: 性能提升 | A code change that improves performance' + name: 'perf: ⚡️ 性能提升 | A code change that improves performance', + emoji: ':zap:', }, { value: 'test', - name: 'test: 测试相关 | Adding missing tests or correcting existing tests' + name: 'test: ✅ 测试相关 | Adding missing tests or correcting existing tests', + emoji: ':white_check_mark:', }, { value: 'build', - name: 'build: 构建相关 | Changes that affect the build system or external dependencies' + name: 'build: 📦️ 构建相关 | Changes that affect the build system or external dependencies', + emoji: ':package:', }, { value: 'ci', - name: 'ci: 持续集成 | Changes to our CI configuration files and scripts' + name: 'ci: 🎡 持续集成 | Changes to our CI configuration files and scripts', + emoji: ':ferris_wheel:', }, - { value: 'revert', name: 'revert: 回退代码 | Revert to a commit' }, + { value: 'revert', name: 'revert: 🔨 回退代码 | Revert to a commit', emoji: ':hammer:' }, { value: 'chore', - name: 'chore: 其他修改 | Other changes that do not modify src or test files' - } + name: 'chore: ⏪️ 其他修改 | Other changes that do not modify src or test files', + emoji: ':rewind:', + }, ], - useEmoji: false, + useEmoji: true, emojiAlign: 'center', - useAI: false, - aiNumber: 1, themeColorCode: '', - scopes: [], allowCustomScopes: true, allowEmptyScopes: true, - customScopesAlign: 'bottom', customScopesAlias: 'custom', emptyScopesAlias: 'empty', upperCaseSubject: false, @@ -80,21 +135,21 @@ module.exports = { breaklineNumber: 100, breaklineChar: '|', skipQuestions: [], - issuePrefixes: [ + issuePrefixs: [ // 如果使用 gitee 作为开发管理 { value: 'link', name: 'link: 链接 ISSUES 进行中' }, - { value: 'closed', name: 'closed: 标记 ISSUES 已完成' } + { value: 'closed', name: 'closed: 标记 ISSUES 已完成' }, ], - customIssuePrefixAlign: 'top', - emptyIssuePrefixAlias: 'skip', - customIssuePrefixAlias: 'custom', - allowCustomIssuePrefix: true, - allowEmptyIssuePrefix: true, + customIssuePrefixsAlign: 'top', + emptyIssuePrefixsAlias: 'skip', + customIssuePrefixsAlias: 'custom', confirmColorize: true, + maxHeaderLength: Number.POSITIVE_INFINITY, + maxSubjectLength: Number.POSITIVE_INFINITY, + minSubjectLength: 0, scopeOverrides: undefined, defaultBody: '', defaultIssues: '', - defaultScope: '', - defaultSubject: '' - } -}; + defaultSubject: '', + }, +} \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..661d01e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,43 @@ +# 官网是这么介绍 EditorConfig 的: +# EditorConfig帮助开发人员在不同的编辑器和IDE之间定义和维护一致的编码样式。 +# EditorConfig 项目由用于定义编码样式的文件格式和一组文本编辑器插件组成,这些插件使编辑器能够读取文件格式并遵循定义的样式。 +# EditorConfig 文件易于阅读,并且与版本控制系统配合使用。 +# 不同的开发人员,不同的编辑器,有不同的编码风格,而 EditorConfig 就是用来协同团队开发人员之间的代码的风格及样式规范化的一个工具, +# 而.editorconfig正是它的默认配置文件 + +#EditorConfig 的匹配规则是从上往下,即先定义的规则优先级比后定义的优先级要高。 + +# 告诉 EditorConfig 插件,这是根文件,不用继续往上查找 +root = true + +# 匹配全部文件 +[*] + +# 设置字符集 +charset=utf-8 + +# 结尾换行符,可选"lf"、"cr"、"crlf" +end_of_line=LF + +# 在文件结尾插入新行 +insert_final_newline=true + +# 缩进风格,可选"space"、"tab" +indent_style=space + +# 缩进的空格数 +indent_size=2 + +max_line_length = 100 + +# 匹配 yml 和 yaml、json 结尾的文件 +[*.{yml,yaml,json}] +indent_style = space +indent_size = 2 + +[*.md] +# 删除一行中的前后空格 +trim_trailing_whitespace = false + +[Makefile] +indent_style = tab diff --git a/.eslintrc-auto-import.json b/.eslintrc-auto-import.json index af1083b..ff73334 100644 --- a/.eslintrc-auto-import.json +++ b/.eslintrc-auto-import.json @@ -71,6 +71,8 @@ "watch": true, "watchEffect": true, "watchPostEffect": true, - "watchSyncEffect": true + "watchSyncEffect": true, + "ElMessage": true, + "ElMessageBox": true } } diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 477ae12..0000000 --- a/.prettierignore +++ /dev/null @@ -1,9 +0,0 @@ -/dist/* -.local -/node_modules/** - -**/*.svg -**/*.sh - -/public/* -stats.html diff --git a/.stylelintignore b/.stylelintignore deleted file mode 100644 index b7eb686..0000000 --- a/.stylelintignore +++ /dev/null @@ -1,4 +0,0 @@ -/dist/* -/public/* -public/* -stats.html diff --git a/.stylelintrc.cjs b/.stylelintrc.cjs deleted file mode 100644 index 4095486..0000000 --- a/.stylelintrc.cjs +++ /dev/null @@ -1,40 +0,0 @@ -// @see: https://stylelint.io - -module.exports = { - root: true, - // 继承某些已有的规则 - extends: [ - "stylelint-config-standard", // 配置 stylelint 拓展插件 - "stylelint-config-html/vue", // 配置 vue 中 template 样式格式化 - "stylelint-config-standard-scss", // 配置 stylelint scss 插件 - "stylelint-config-recommended-vue/scss", // 配置 vue 中 scss 样式格式化 - "stylelint-config-recess-order" // 配置 stylelint css 属性书写顺序插件, - ], - overrides: [ - // 扫描 .vue/html 文件中的 + diff --git a/src/assets/icons/svg/ctrl+k.svg b/src/assets/icons/svg/ctrl+k.svg new file mode 100644 index 0000000..a937dbb --- /dev/null +++ b/src/assets/icons/svg/ctrl+k.svg @@ -0,0 +1 @@ + diff --git a/src/assets/images/logo.png b/src/assets/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..0cf743987dc63bc33c4272968d170618fa137621 GIT binary patch literal 15776 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 literal 0 HcmV?d00001 diff --git a/src/components/IconSelect/index.vue b/src/components/IconSelect/index.vue index f3c9d35..007ba9d 100644 --- a/src/components/IconSelect/index.vue +++ b/src/components/IconSelect/index.vue @@ -1,8 +1,10 @@ @@ -51,16 +52,12 @@ function selectedIcon(name: string) { :label="classify.classifyName" >
-
+
- {{ - item - }} + + {{ item }} +
@@ -78,6 +75,7 @@ function selectedIcon(name: string) { display: grid; grid-template-columns: repeat(auto-fill, minmax(40px, 1fr)); } + .icon-item { cursor: pointer; padding: 0px 4px; @@ -87,13 +85,16 @@ function selectedIcon(name: string) { text-align: center; font-size: 18px; } + .icon-item:hover { box-shadow: 1px 1px 10px 0 #a1a1a1; } + .el-tab-pane { height: 200px; overflow: auto; } + .icon_name { display: none; } @@ -104,10 +105,13 @@ function selectedIcon(name: string) { .icon-body { padding: 10px; } + .icon_name { display: block; } + overflow: hidden; + .grid-container { margin-top: 12px; position: relative; @@ -115,7 +119,11 @@ function selectedIcon(name: string) { grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); border-left: 1px solid #eee; border-top: 1px solid #eee; + overflow-y: auto; + overflow-x: hidden; + height: 500px; } + .icon-item { padding: 16px 0; margin: 0 !important; @@ -136,14 +144,17 @@ function selectedIcon(name: string) { .disabled { pointer-events: none; } + .grid { border-top: 1px solid #eee; } } + .icons-container span { font-size: 12px !important; color: #99a9bf; } + .icons-container svg { font-size: 24px !important; color: #606266; diff --git a/src/components/Popover/index.vue b/src/components/Popover/index.vue new file mode 100644 index 0000000..d402c2b --- /dev/null +++ b/src/components/Popover/index.vue @@ -0,0 +1,407 @@ + + + + + diff --git a/src/config/design.ts b/src/config/design.ts index c81af2d..ea87a65 100644 --- a/src/config/design.ts +++ b/src/config/design.ts @@ -1,5 +1,12 @@ export type LayoutType = 'vertical'; +// 仿豆包折叠逻辑 +export type CollapseType = + | 'alwaysCollapsed' // 始终折叠 + | 'followSystem' // 跟随系统视口宽度 + | 'alwaysExpanded' // 始终打开 + | 'narrowExpandWideCollapse'; // 系统视口 宽小则张,宽大则收 + export interface DesignConfigState { // 系统主题 darkMode: 'light' | 'dark' | 'inverted'; @@ -13,12 +20,10 @@ export interface DesignConfigState { pageAnimateType: string; // 布局模式 (纵向:vertical | ... | 自己定义) layout: LayoutType; - // 是否折叠菜单-视口宽度自动决定 + // 折叠类型 + collapseType: CollapseType; + // 是否折叠菜单 isCollapse: boolean; - // 是否折叠菜单-用户意愿点击决定 - isCollapseManual: boolean; - // 最终是否折叠菜单,动态根据上述两种折叠条件决定 - isCollapseFinal: boolean; } export const themeColorList: string[] = [ @@ -56,12 +61,10 @@ const design: DesignConfigState = { pageAnimateType: 'zoom-fade', // 布局模式 (纵向:vertical | ... | 自己定义) layout: 'vertical', - // 是否折叠菜单-视口宽度自动决定 + // 折叠类型 + collapseType: 'followSystem', + // 是否折叠菜单 isCollapse: false, - // 是否折叠菜单-用户手动控制决定 - isCollapseManual: false, - // 最终是否折叠菜单,动态根据上述两种折叠条件决定 - isCollapseFinal: false, }; export default design; diff --git a/src/config/index.ts b/src/config/index.ts index db08e0b..e787f06 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -8,5 +8,11 @@ export const LOGIN_URL: string = '/login'; // 默认主题颜色 export const DEFAULT_THEME_COLOR: string = '#2992FF'; +// 折叠阈值 +export const COLLAPSE_THRESHOLD: number = 600; + +// 左侧菜单宽度 +export const SIDE_BAR_WIDTH: number = 280; + // 路由白名单地址[本地存在的路由 staticRouter.ts 中] export const ROUTER_WHITE_LIST: string[] = ['/500']; diff --git a/src/hooks/useCollapseToggle.ts b/src/hooks/useCollapseToggle.ts new file mode 100644 index 0000000..ff7636f --- /dev/null +++ b/src/hooks/useCollapseToggle.ts @@ -0,0 +1,61 @@ +import { COLLAPSE_THRESHOLD } from '@/config/index'; +import { useWindowWidthObserver } from '@/hooks/useWindowWidthObserver'; +import { useDesignStore } from '@/store/modules/design'; + +/** + * 侧边栏折叠切换逻辑组合式函数 方便多个页面调用 + * @param threshold 折叠阈值(可选,默认使用全局配置) + */ +export function useCollapseToggle(threshold: number = COLLAPSE_THRESHOLD) { + const designStore = useDesignStore(); + // 获取当前视口宽度是否大于阈值,但不做响应式处理,传入一个空函数执行 + const { isAboveThreshold } = useWindowWidthObserver(threshold, () => {}); + + /** 核心折叠切换方法 */ + const changeCollapse = () => { + // 切换最终折叠状态 + designStore.setCollapseFinal(!designStore.isCollapse); + + if (isAboveThreshold.value) { + // 宽屏逻辑 + if (designStore.isCollapse) { + designStore.setCollapseType('alwaysCollapsed'); + } + else { + designStore.setCollapseType( + designStore.collapseType === 'narrowExpandWideCollapse' + ? 'alwaysExpanded' + : 'followSystem', + ); + } + } + else { + // 窄屏逻辑 + if (designStore.isCollapse) { + designStore.setCollapseType('followSystem'); + } + else { + designStore.setCollapseType( + designStore.collapseType === 'alwaysCollapsed' + ? 'narrowExpandWideCollapse' + : 'alwaysExpanded', + ); + } + } + }; + + return { + changeCollapse, + }; +} + +// 使用示例与特性说明 +// +// diff --git a/src/hooks/useWindowWidthObserver.ts b/src/hooks/useWindowWidthObserver.ts index 028324e..77429ba 100644 --- a/src/hooks/useWindowWidthObserver.ts +++ b/src/hooks/useWindowWidthObserver.ts @@ -1,15 +1,17 @@ import type { MaybeRef } from 'vue'; import { onBeforeUnmount, ref, unref, watch } from 'vue'; +import { COLLAPSE_THRESHOLD, SIDE_BAR_WIDTH } from '@/config/index'; import { useDesignStore } from '@/store/modules/design'; /** + * 这里逻辑是研究豆包的折叠逻辑后,设计的折叠方法 * 基于ResizeObserver的窗口宽度监听hooks(高性能实时监控) * @param threshold 宽度阈值(默认600px,支持响应式) * @param onChange 自定义回调(传入则覆盖默认逻辑,参数:当前视口宽度是否超过阈值) * @returns {object} 包含卸载监听的方法及当前状态 */ export function useWindowWidthObserver( - threshold: MaybeRef = 600, + threshold: MaybeRef = COLLAPSE_THRESHOLD, onChange?: (isAboveThreshold: boolean) => void, ) { const designStore = useDesignStore(); @@ -20,15 +22,40 @@ export function useWindowWidthObserver( // 默认逻辑:修改全局折叠状态 const updateCollapseState = (isAbove: boolean) => { - if (!isAbove) { - // 小于阈值时 且为展开状态时候 - // 跟随用户当前的意愿 - designStore.setCollapseFinal(designStore.isCollapseManual); - // 如果是开,则执行展开动画表示用户意愿 + // 判断当前的折叠状态 + switch (designStore.collapseType) { + case 'alwaysCollapsed': + designStore.setCollapseFinal(true); + break; + case 'followSystem': + designStore.setCollapseFinal(!isAbove); + designStore.setCollapseFinal(!isAbove); + break; + case 'alwaysExpanded': + designStore.setCollapseFinal(false); + if (isAbove) { + // 大于的时候执行关闭动画 + console.log('执行关闭动画'); + } + else { + // 小于的时候执行打开动画 + console.log('小于的时候执行打开动画'); + } + break; + case 'narrowExpandWideCollapse': + designStore.setCollapseFinal(isAbove); + designStore.setCollapseFinal(isAbove); } - else if (!designStore.isCollapseFinal && isAbove) { - // 大于阈值时 且为收起状态时 - designStore.setCollapseFinal(true); + console.log('最终的折叠状态:', designStore.isCollapse); + + if (!designStore.isCollapse) { + document.documentElement.style.setProperty( + `--sidebar-left-container-default-width`, + `${SIDE_BAR_WIDTH}px`, + ); + } + else { + document.documentElement.style.setProperty(`--sidebar-left-container-default-width`, ``); } }; diff --git a/src/layouts/LayoutVertical/index.vue b/src/layouts/LayoutVertical/index.vue index 6e75556..f2c6287 100644 --- a/src/layouts/LayoutVertical/index.vue +++ b/src/layouts/LayoutVertical/index.vue @@ -4,22 +4,11 @@ import { useWindowWidthObserver } from '@/hooks/useWindowWidthObserver'; import Aside from '@/layouts/components/Aside/index.vue'; import Header from '@/layouts/components/Header/index.vue'; import Main from '@/layouts/components/Main/index.vue'; - import { useDesignStore } from '@/store/modules/design'; const designStore = useDesignStore(); -// 动态绑定左侧菜单animate动画 -const menuAnimate = computed(() => designStore.pageAnimateType); -const menuCollapseFinal = computed(() => designStore.isCollapseFinal); - -console.log('menuAnimate===>', menuAnimate); -console.log('menuCollapseFinal===>', menuCollapseFinal); - -watch([menuCollapseFinal, menuAnimate], (newValue, oldValue) => { - console.log('newValue', newValue); - console.log('oldValue', oldValue); -}); +console.log('每次加载全局的折叠状态', designStore.collapseType); /** 监听窗口大小变化,折叠侧边栏 */ useWindowWidthObserver(); @@ -27,40 +16,46 @@ useWindowWidthObserver(); diff --git a/src/layouts/components/Aside/index.vue b/src/layouts/components/Aside/index.vue index 198a822..c02be63 100644 --- a/src/layouts/components/Aside/index.vue +++ b/src/layouts/components/Aside/index.vue @@ -1,15 +1,155 @@ - - + + diff --git a/src/layouts/components/Header/components/Avatar.vue b/src/layouts/components/Header/components/Avatar.vue new file mode 100644 index 0000000..4606554 --- /dev/null +++ b/src/layouts/components/Header/components/Avatar.vue @@ -0,0 +1,121 @@ + + + + + + diff --git a/src/layouts/components/Header/components/Collapse.vue b/src/layouts/components/Header/components/Collapse.vue index 04a7c74..a17e373 100644 --- a/src/layouts/components/Header/components/Collapse.vue +++ b/src/layouts/components/Header/components/Collapse.vue @@ -1,34 +1,35 @@ + - + diff --git a/src/layouts/components/Header/components/CreateChat.vue b/src/layouts/components/Header/components/CreateChat.vue new file mode 100644 index 0000000..53798c6 --- /dev/null +++ b/src/layouts/components/Header/components/CreateChat.vue @@ -0,0 +1,15 @@ + + + + + + diff --git a/src/layouts/components/Header/components/LoginBtn.vue b/src/layouts/components/Header/components/LoginBtn.vue new file mode 100644 index 0000000..018f119 --- /dev/null +++ b/src/layouts/components/Header/components/LoginBtn.vue @@ -0,0 +1,17 @@ + + + + + + diff --git a/src/layouts/components/Header/components/TitleEditing.vue b/src/layouts/components/Header/components/TitleEditing.vue new file mode 100644 index 0000000..46bde23 --- /dev/null +++ b/src/layouts/components/Header/components/TitleEditing.vue @@ -0,0 +1,54 @@ + + + + + + diff --git a/src/layouts/components/Header/index.vue b/src/layouts/components/Header/index.vue index 412b50b..b67c670 100644 --- a/src/layouts/components/Header/index.vue +++ b/src/layouts/components/Header/index.vue @@ -1,10 +1,70 @@ - + diff --git a/src/pages/chat/home/index.vue b/src/pages/chat/home/index.vue new file mode 100644 index 0000000..c516cd3 --- /dev/null +++ b/src/pages/chat/home/index.vue @@ -0,0 +1,83 @@ + + + + + + diff --git a/src/pages/chat/index.vue b/src/pages/chat/index.vue index e513df1..925805c 100644 --- a/src/pages/chat/index.vue +++ b/src/pages/chat/index.vue @@ -3,10 +3,8 @@ import { BubbleList, Sender } from 'vue-element-plus-x'; import { useRoute, useRouter } from 'vue-router'; import { createSession } from '@/api'; import { send } from '@/api/chat'; -import IconSelect from '@/components/IconSelect/index.vue'; import { ModelEnum } from '@/constants/enums'; import { useUserStore } from '@/store'; - import { useChatStore } from '@/store/modules/chat'; const route = useRoute(); @@ -85,9 +83,7 @@ async function handleSend() {