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 0000000..0cf7439
Binary files /dev/null and b/src/assets/images/logo.png differ
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.title }}
+
+
+
+
+
+
+
+
+
+
+
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() {
-
-
-
+
{{ item.content }}
diff --git a/src/router/index.ts b/src/router/index.ts
index a926985..ee82085 100644
--- a/src/router/index.ts
+++ b/src/router/index.ts
@@ -1,10 +1,11 @@
import type { RouteRecordRaw } from 'vue-router';
-import { createRouter, createWebHashHistory } from 'vue-router';
+import { createRouter, createWebHistory } from 'vue-router';
import { jwtGuard } from './permissions';
const routes: Readonly[] = [
{
path: '/',
+ redirect: '/chat',
component: () => import('@/layouts/index.vue'),
children: [
{
@@ -14,9 +15,14 @@ const routes: Readonly[] = [
},
{
path: ':id',
- name: 'chat',
+ name: '/chat',
component: () => import('@/pages/chat/index.vue'),
},
+ {
+ path: '/chat/home',
+ name: 'chatHome',
+ component: () => import('@/pages/chat/home/index.vue'),
+ },
],
},
{
@@ -27,7 +33,7 @@ const routes: Readonly[] = [
] as const;
const router = createRouter({
- history: createWebHashHistory(),
+ history: createWebHistory(),
routes,
});
diff --git a/src/store/modules/design.ts b/src/store/modules/design.ts
index ab03231..44b318a 100644
--- a/src/store/modules/design.ts
+++ b/src/store/modules/design.ts
@@ -1,4 +1,4 @@
-import type { LayoutType } from '@/config/design';
+import type { CollapseType, LayoutType } from '@/config/design';
import { defineStore } from 'pinia';
import designSetting from '@/config/design';
@@ -9,9 +9,8 @@ const {
isPageAnimate,
pageAnimateType: rePageAnimateType,
layout: reLayout,
- isCollapse: reIsCollapse,
- isCollapseManual: reIsCollapseManual,
- isCollapseFinal: reIsCollapseFinal,
+ collapseType: reCollapseType,
+ isCollapse: reisCollapse,
} = designSetting;
export const useDesignStore = defineStore(
@@ -35,25 +34,17 @@ export const useDesignStore = defineStore(
// layout.value = layoutType;
// };
- // 更据视口宽度和阈值-是否展开左侧菜单
- const isCollapse = ref(reIsCollapse);
- const setCollapse = (collapse: boolean) => {
- isCollapse.value = collapse;
- };
-
- // 是否手动点击展开左侧菜单
- const isCollapseManual = ref(reIsCollapseManual);
- const setCollapseManual = (collapseManual: boolean): boolean => {
- isCollapseManual.value = collapseManual;
- return collapseManual;
+ // 折叠状态
+ const collapseType = ref(reCollapseType);
+ const setCollapseType = (type: CollapseType) => {
+ collapseType.value = type;
};
// 最终是否展开左侧菜单
- const isCollapseFinal = ref(reIsCollapseFinal);
+ const isCollapse = ref(reisCollapse);
const setCollapseFinal = (collapseFinal: boolean) => {
- console.log('最终的折叠状态', collapseFinal);
- isCollapseFinal.value = collapseFinal;
+ isCollapse.value = collapseFinal;
};
return {
@@ -65,11 +56,9 @@ export const useDesignStore = defineStore(
pageAnimateType,
setPageAnimateType,
layout,
+ collapseType,
+ setCollapseType,
isCollapse,
- setCollapse,
- isCollapseManual,
- setCollapseManual,
- isCollapseFinal,
setCollapseFinal,
};
},
diff --git a/src/styles/btn-style.scss b/src/styles/btn-style.scss
new file mode 100644
index 0000000..71c6fd1
--- /dev/null
+++ b/src/styles/btn-style.scss
@@ -0,0 +1,35 @@
+// 公共的图表按钮样式
+.btn-icon-btn {
+ align-items: center;
+ justify-content: center;
+ display: inline-block;
+ border: 1px solid transparent;
+ border-radius: 30%;
+ padding: 4px;
+ cursor: pointer;
+ transition: all 0.3s ease;
+
+ // 鼠标移入
+ &:hover {
+ background-color: rgb(0, 0, 0, .05);
+
+ .svg-icon {
+ color: rgb(0, 0, 0, .6);
+ }
+ }
+
+ // 鼠标按下
+ &:active {
+ background-color: rgb(0, 0, 0, .08);
+
+ .svg-icon {
+ color: rgb(0, 0, 0, .7);
+ }
+ }
+
+ // 图标
+ .svg-icon {
+ user-select: none;
+ color: rgb(0, 0, 0, .5);
+ }
+}
diff --git a/src/styles/index.scss b/src/styles/index.scss
index cfdba80..63035df 100644
--- a/src/styles/index.scss
+++ b/src/styles/index.scss
@@ -1,3 +1,4 @@
+@use './btn-style.scss';
@use 'reset-css';
@use './element-plus.scss';
diff --git a/src/styles/var.scss b/src/styles/var.scss
new file mode 100644
index 0000000..0a93a70
--- /dev/null
+++ b/src/styles/var.scss
@@ -0,0 +1,9 @@
+:root {
+ /* 头部高度 */
+ --header-container-default-heigth: 56px;
+
+ /* 侧边栏背景色 */
+ --sidebar-background-color: #f3f4f6;
+
+
+}
diff --git a/types/auto-imports.d.ts b/types/auto-imports.d.ts
index 4d6ea84..1db43ec 100644
--- a/types/auto-imports.d.ts
+++ b/types/auto-imports.d.ts
@@ -6,66 +6,85 @@
// biome-ignore lint: disable
export {}
declare global {
- const EffectScope: typeof import('vue')['EffectScope']
- const computed: typeof import('vue')['computed']
- const createApp: typeof import('vue')['createApp']
- const customRef: typeof import('vue')['customRef']
- const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
- const defineComponent: typeof import('vue')['defineComponent']
- const effectScope: typeof import('vue')['effectScope']
- const getCurrentInstance: typeof import('vue')['getCurrentInstance']
- const getCurrentScope: typeof import('vue')['getCurrentScope']
- const h: typeof import('vue')['h']
- const inject: typeof import('vue')['inject']
- const isProxy: typeof import('vue')['isProxy']
- const isReactive: typeof import('vue')['isReactive']
- const isReadonly: typeof import('vue')['isReadonly']
- const isRef: typeof import('vue')['isRef']
- const markRaw: typeof import('vue')['markRaw']
- const nextTick: typeof import('vue')['nextTick']
- const onActivated: typeof import('vue')['onActivated']
- const onBeforeMount: typeof import('vue')['onBeforeMount']
- const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
- const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
- const onDeactivated: typeof import('vue')['onDeactivated']
- const onErrorCaptured: typeof import('vue')['onErrorCaptured']
- const onMounted: typeof import('vue')['onMounted']
- const onRenderTracked: typeof import('vue')['onRenderTracked']
- const onRenderTriggered: typeof import('vue')['onRenderTriggered']
- const onScopeDispose: typeof import('vue')['onScopeDispose']
- const onServerPrefetch: typeof import('vue')['onServerPrefetch']
- const onUnmounted: typeof import('vue')['onUnmounted']
- const onUpdated: typeof import('vue')['onUpdated']
- const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
- const provide: typeof import('vue')['provide']
- const reactive: typeof import('vue')['reactive']
- const readonly: typeof import('vue')['readonly']
- const ref: typeof import('vue')['ref']
- const resolveComponent: typeof import('vue')['resolveComponent']
- const shallowReactive: typeof import('vue')['shallowReactive']
- const shallowReadonly: typeof import('vue')['shallowReadonly']
- const shallowRef: typeof import('vue')['shallowRef']
- const toRaw: typeof import('vue')['toRaw']
- const toRef: typeof import('vue')['toRef']
- const toRefs: typeof import('vue')['toRefs']
- const toValue: typeof import('vue')['toValue']
- const triggerRef: typeof import('vue')['triggerRef']
- const unref: typeof import('vue')['unref']
- const useAttrs: typeof import('vue')['useAttrs']
- const useCssModule: typeof import('vue')['useCssModule']
- const useCssVars: typeof import('vue')['useCssVars']
- const useId: typeof import('vue')['useId']
- const useModel: typeof import('vue')['useModel']
- const useSlots: typeof import('vue')['useSlots']
- const useTemplateRef: typeof import('vue')['useTemplateRef']
- const watch: typeof import('vue')['watch']
- const watchEffect: typeof import('vue')['watchEffect']
- const watchPostEffect: typeof import('vue')['watchPostEffect']
- const watchSyncEffect: typeof import('vue')['watchSyncEffect']
+ const EffectScope: (typeof import("vue"))["EffectScope"];
+ const ElMessage: (typeof import("element-plus/es"))["ElMessage"];
+ const ElMessageBox: (typeof import("element-plus/es"))["ElMessageBox"];
+ const computed: (typeof import("vue"))["computed"];
+ const createApp: (typeof import("vue"))["createApp"];
+ const customRef: (typeof import("vue"))["customRef"];
+ const defineAsyncComponent: (typeof import("vue"))["defineAsyncComponent"];
+ const defineComponent: (typeof import("vue"))["defineComponent"];
+ const effectScope: (typeof import("vue"))["effectScope"];
+ const getCurrentInstance: (typeof import("vue"))["getCurrentInstance"];
+ const getCurrentScope: (typeof import("vue"))["getCurrentScope"];
+ const h: (typeof import("vue"))["h"];
+ const inject: (typeof import("vue"))["inject"];
+ const isProxy: (typeof import("vue"))["isProxy"];
+ const isReactive: (typeof import("vue"))["isReactive"];
+ const isReadonly: (typeof import("vue"))["isReadonly"];
+ const isRef: (typeof import("vue"))["isRef"];
+ const markRaw: (typeof import("vue"))["markRaw"];
+ const nextTick: (typeof import("vue"))["nextTick"];
+ const onActivated: (typeof import("vue"))["onActivated"];
+ const onBeforeMount: (typeof import("vue"))["onBeforeMount"];
+ const onBeforeUnmount: (typeof import("vue"))["onBeforeUnmount"];
+ const onBeforeUpdate: (typeof import("vue"))["onBeforeUpdate"];
+ const onDeactivated: (typeof import("vue"))["onDeactivated"];
+ const onErrorCaptured: (typeof import("vue"))["onErrorCaptured"];
+ const onMounted: (typeof import("vue"))["onMounted"];
+ const onRenderTracked: (typeof import("vue"))["onRenderTracked"];
+ const onRenderTriggered: (typeof import("vue"))["onRenderTriggered"];
+ const onScopeDispose: (typeof import("vue"))["onScopeDispose"];
+ const onServerPrefetch: (typeof import("vue"))["onServerPrefetch"];
+ const onUnmounted: (typeof import("vue"))["onUnmounted"];
+ const onUpdated: (typeof import("vue"))["onUpdated"];
+ const onWatcherCleanup: (typeof import("vue"))["onWatcherCleanup"];
+ const provide: (typeof import("vue"))["provide"];
+ const reactive: (typeof import("vue"))["reactive"];
+ const readonly: (typeof import("vue"))["readonly"];
+ const ref: (typeof import("vue"))["ref"];
+ const resolveComponent: (typeof import("vue"))["resolveComponent"];
+ const shallowReactive: (typeof import("vue"))["shallowReactive"];
+ const shallowReadonly: (typeof import("vue"))["shallowReadonly"];
+ const shallowRef: (typeof import("vue"))["shallowRef"];
+ const toRaw: (typeof import("vue"))["toRaw"];
+ const toRef: (typeof import("vue"))["toRef"];
+ const toRefs: (typeof import("vue"))["toRefs"];
+ const toValue: (typeof import("vue"))["toValue"];
+ const triggerRef: (typeof import("vue"))["triggerRef"];
+ const unref: (typeof import("vue"))["unref"];
+ const useAttrs: (typeof import("vue"))["useAttrs"];
+ const useCssModule: (typeof import("vue"))["useCssModule"];
+ const useCssVars: (typeof import("vue"))["useCssVars"];
+ const useId: (typeof import("vue"))["useId"];
+ const useModel: (typeof import("vue"))["useModel"];
+ const useSlots: (typeof import("vue"))["useSlots"];
+ const useTemplateRef: (typeof import("vue"))["useTemplateRef"];
+ const watch: (typeof import("vue"))["watch"];
+ const watchEffect: (typeof import("vue"))["watchEffect"];
+ const watchPostEffect: (typeof import("vue"))["watchPostEffect"];
+ const watchSyncEffect: (typeof import("vue"))["watchSyncEffect"];
}
// for type re-export
declare global {
// @ts-ignore
- export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
- import('vue')
+ export type {
+ Component,
+ Slot,
+ Slots,
+ ComponentPublicInstance,
+ ComputedRef,
+ DirectiveBinding,
+ ExtractDefaultPropTypes,
+ ExtractPropTypes,
+ ExtractPublicPropTypes,
+ InjectionKey,
+ PropType,
+ Ref,
+ MaybeRef,
+ MaybeRefOrGetter,
+ VNode,
+ WritableComputedRef,
+ } from "vue";
+ import("vue");
}
diff --git a/types/components.d.ts b/types/components.d.ts
index f0b4b5f..c62200b 100644
--- a/types/components.d.ts
+++ b/types/components.d.ts
@@ -3,21 +3,20 @@
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
// biome-ignore lint: disable
-export {}
+export {};
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
- ElAside: typeof import('element-plus/es')['ElAside']
+ ElAvatar: typeof import('element-plus/es')['ElAvatar']
ElContainer: typeof import('element-plus/es')['ElContainer']
+ ElEmpty: typeof import('element-plus/es')['ElEmpty']
ElHeader: typeof import('element-plus/es')['ElHeader']
ElIcon: typeof import('element-plus/es')['ElIcon']
- ElInput: typeof import('element-plus/es')['ElInput']
+ ElImage: typeof import('element-plus/es')['ElImage']
ElMain: typeof import('element-plus/es')['ElMain']
- ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
- ElTabPane: typeof import('element-plus/es')['ElTabPane']
- ElTabs: typeof import('element-plus/es')['ElTabs']
IconSelect: typeof import('./../src/components/IconSelect/index.vue')['default']
+ Popover: typeof import('./../src/components/Popover/index.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SvgIcon: typeof import('./../src/components/SvgIcon/index.vue')['default']
diff --git a/vite.config.ts b/vite.config.ts
index 2d06669..5a9b6a5 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -36,5 +36,13 @@ export default defineConfig(({ mode, command }) => {
"@": path.resolve(__dirname, "./src"),
},
},
+ css: {
+ // css全局变量使用,@/styles/variable.scss文件
+ preprocessorOptions: {
+ scss: {
+ additionalData: '@use "@/styles/var.scss" as *;',
+ },
+ },
+ },
};
});