Merge pull request #5 from element-plus-x/feat-20250617-coder_zr
Feat 20250617 coder zr
This commit is contained in:
commit
4212d01c46
33
.release-it.json
Normal file
33
.release-it.json
Normal file
@ -0,0 +1,33 @@
|
||||
{
|
||||
"plugins": {
|
||||
"@release-it/conventional-changelog": {
|
||||
"preset": {
|
||||
"name": "conventionalcommits",
|
||||
"types": [
|
||||
{ "type": "feat", "section": "✨ Features | 新功能" },
|
||||
{ "type": "fix", "section": "🐛 Bug Fixes | Bug 修复" },
|
||||
{ "type": "chore", "section": "🎫 Chores | 其他更新" },
|
||||
{ "type": "docs", "section": "📝 Documentation | 文档" },
|
||||
{ "type": "style", "section": "💄 Styles | 风格" },
|
||||
{ "type": "refactor", "section": "♻ Code Refactoring | 代码重构" },
|
||||
{ "type": "perf", "section": "⚡ Performance Improvements | 性能优化" },
|
||||
{ "type": "test", "section": "✅ Tests | 测试" },
|
||||
{ "type": "revert", "section": "⏪ Reverts | 回退" },
|
||||
{ "type": "build", "section": "👷 Build System | 构建" },
|
||||
{ "type": "ci", "section": "🔧 Continuous Integration | CI 配置" },
|
||||
{ "type": "config", "section": "🔨 CONFIG | 配置" }
|
||||
]
|
||||
},
|
||||
"infile": "CHANGELOG.md",
|
||||
"ignoreRecommendedBump": true,
|
||||
"strictSemVer": true
|
||||
}
|
||||
},
|
||||
"git": {
|
||||
"commitMessage": "chore: Release v${version}"
|
||||
},
|
||||
"github": {
|
||||
"release": true,
|
||||
"draft": false
|
||||
}
|
||||
}
|
||||
25
README.md
25
README.md
@ -77,6 +77,31 @@ pnpm lint:stylelint # 样式格式化
|
||||
pnpm cz # 规范提交(自动执行lint)
|
||||
```
|
||||
|
||||
开发模式配置远程服务器地址:
|
||||
|
||||
根目录下新建 `.env.development.local` 文件
|
||||
|
||||
```bash
|
||||
VITE_API_URL = xxxxxxxxxxxxxxxxxxxxx
|
||||
```
|
||||
|
||||
## 🪼 项目发版
|
||||
|
||||
项目使用 `release-it` 进行发版
|
||||
|
||||
默认更新次版本号,如果想每次更新修订号,可执行 pnpm release patch
|
||||
|
||||
```bash
|
||||
# 更新主版本号
|
||||
pnpm release major
|
||||
|
||||
# 更新次版本号
|
||||
pnpm release minor
|
||||
|
||||
# 更新修订号
|
||||
pnpm release patch
|
||||
```
|
||||
|
||||
## 🧸 即将推出 (含 ruoyi-ai 接口联调)
|
||||
- [x] 会话管理
|
||||
- [x] 发送消息
|
||||
|
||||
@ -24,6 +24,7 @@
|
||||
"preview": "vite preview",
|
||||
"prepare": "husky",
|
||||
"lint": "eslint .",
|
||||
"release": "release-it",
|
||||
"lint:stylelint": "stylelint --cache --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
|
||||
"fix": "eslint . --fix"
|
||||
},
|
||||
@ -37,6 +38,7 @@
|
||||
"@vueuse/integrations": "^13.3.0",
|
||||
"element-plus": "^2.9.11",
|
||||
"hook-fetch": "^1.1.3",
|
||||
"moment": "^2.30.1",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^3.0.3",
|
||||
"pinia-plugin-persistedstate": "^4.3.0",
|
||||
@ -51,6 +53,7 @@
|
||||
"@antfu/eslint-config": "^4.13.3",
|
||||
"@changesets/cli": "^2.29.4",
|
||||
"@commitlint/config-conventional": "^19.8.1",
|
||||
"@release-it/conventional-changelog": "^10.0.1",
|
||||
"@vitejs/plugin-vue": "^5.2.4",
|
||||
"@vue/tsconfig": "^0.7.0",
|
||||
"commitlint": "^19.8.1",
|
||||
@ -59,6 +62,7 @@
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^16.1.0",
|
||||
"prettier": "^3.5.3",
|
||||
"release-it": "^19.0.3",
|
||||
"sass-embedded": "^1.89.1",
|
||||
"stylelint": "^16.20.0",
|
||||
"stylelint-config-html": "^1.1.0",
|
||||
|
||||
1459
pnpm-lock.yaml
generated
1459
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,5 @@
|
||||
<script setup lang="ts"></script>
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<router-view />
|
||||
|
||||
@ -113,8 +113,8 @@ function onAfterLeave() {
|
||||
/* 动画样式(仅作用于弹框) */
|
||||
.dialog-zoom-enter-active,
|
||||
.dialog-zoom-leave-active {
|
||||
transition: all 0.3s ease-in-out;
|
||||
transform-origin: center;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
.dialog-zoom-enter-from,
|
||||
.dialog-zoom-leave-to {
|
||||
@ -141,8 +141,8 @@ function onAfterLeave() {
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
background-color: rgb(0 0 0 / 50%);
|
||||
backdrop-filter: blur(3px);
|
||||
opacity: 1;
|
||||
backdrop-filter: blur(3px);
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
.mask[hidden] {
|
||||
@ -189,9 +189,9 @@ function onAfterLeave() {
|
||||
height: 40px;
|
||||
padding: 4px;
|
||||
background: var(--login-dialog-logo-background);
|
||||
filter: drop-shadow(0 4px 4px rgb(0 0 0 / 10%));
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 12px 0 rgb(0 0 0 / 8%);
|
||||
filter: drop-shadow(0 4px 4px rgb(0 0 0 / 10%));
|
||||
}
|
||||
.left-section .logo-wrap .logo-text {
|
||||
font-size: 16px;
|
||||
|
||||
53
src/hooks/useCurrentInstance.ts
Normal file
53
src/hooks/useCurrentInstance.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import type { ComponentInternalInstance } from 'vue';
|
||||
import type { RouteLocationNormalizedLoaded, Router } from 'vue-router';
|
||||
import { getCurrentInstance } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
interface Utils {
|
||||
$is: {
|
||||
[key: string]: (...args: any[]) => boolean;
|
||||
};
|
||||
$dataHelpers: {
|
||||
[key: string]: (...args: any[]) => any;
|
||||
};
|
||||
$common: {
|
||||
[key: string]: (...args: any[]) => any;
|
||||
};
|
||||
}
|
||||
|
||||
interface UseCurrentInstanceReturn {
|
||||
currentInstance: ComponentInternalInstance;
|
||||
proxy: ComponentInternalInstance['proxy'] & Utils;
|
||||
router: Router;
|
||||
route: RouteLocationNormalizedLoaded;
|
||||
$is: Utils['$is'];
|
||||
$dataHelpers: Utils['$dataHelpers'];
|
||||
$common: Utils['$common'];
|
||||
}
|
||||
|
||||
export function useCurrentInstance(): UseCurrentInstanceReturn {
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
const currentInstance = getCurrentInstance();
|
||||
|
||||
if (!currentInstance) {
|
||||
throw new Error('useCurrentInstance必须在setup函数中调用💘');
|
||||
}
|
||||
|
||||
const { proxy } = currentInstance as ComponentInternalInstance & { proxy: Utils };
|
||||
|
||||
const $is = proxy.$is;
|
||||
const $dataHelpers = proxy.$dataHelpers;
|
||||
const $common = proxy.$common;
|
||||
|
||||
return {
|
||||
currentInstance,
|
||||
proxy,
|
||||
router,
|
||||
route,
|
||||
$is,
|
||||
$dataHelpers,
|
||||
$common,
|
||||
};
|
||||
}
|
||||
@ -329,12 +329,12 @@ function handleMenuCommand(command: string, item: ConversationItem<ChatSessionVo
|
||||
0 10px 20px 0 rgb(0 0 0 / 10%),
|
||||
0 0 1px 0 rgb(0 0 0 / 15%);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease 0.3s, transform 0.3s ease 0.3s;
|
||||
|
||||
// 指定样式过渡
|
||||
|
||||
// 向左偏移一个宽度
|
||||
transform: translateX(-100%);
|
||||
transition: opacity 0.3s ease 0.3s, transform 0.3s ease 0.3s;
|
||||
|
||||
/* 新增:未激活悬停时覆盖延迟 */
|
||||
&.no-delay {
|
||||
@ -358,10 +358,10 @@ function handleMenuCommand(command: string, item: ConversationItem<ChatSessionVo
|
||||
|
||||
// 直接在这里写悬停时的样式(与 aside-container-suspended 一致)
|
||||
opacity: 1;
|
||||
transition: opacity 0.3s ease 0s, transform 0.3s ease 0s;
|
||||
|
||||
// 过渡动画沿用原有设置
|
||||
transform: translateX(15px);
|
||||
transition: opacity 0.3s ease 0s, transform 0.3s ease 0s;
|
||||
|
||||
// 会话列表高度-悬停样式
|
||||
.conversations-wrap {
|
||||
|
||||
11
src/main.ts
11
src/main.ts
@ -1,9 +1,8 @@
|
||||
// 引入ElementPlus所有图标
|
||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { createApp } from 'vue';
|
||||
import ElementPlusX from 'vue-element-plus-x';
|
||||
import App from './App.vue';
|
||||
import { registerPlugins } from './plugins';
|
||||
import router from './routers';
|
||||
import store from './stores';
|
||||
import './styles/index.scss';
|
||||
@ -14,13 +13,13 @@ import 'virtual:svg-icons-register';
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
// 插件安装
|
||||
registerPlugins(app);
|
||||
|
||||
app.use(router);
|
||||
app.use(ElMessage);
|
||||
app.use(ElementPlusX);
|
||||
// 注册ElementPlus所有图标
|
||||
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||
app.component(key, component);
|
||||
}
|
||||
|
||||
app.use(store);
|
||||
|
||||
app.mount('#app');
|
||||
|
||||
19
src/plugins/index.ts
Normal file
19
src/plugins/index.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import type { App, Plugin } from 'vue';
|
||||
|
||||
interface PluginModule { default: Plugin }
|
||||
|
||||
const modules = import.meta.glob<PluginModule>('./modules/*.ts', { eager: true });
|
||||
|
||||
export function registerPlugins(app: App) {
|
||||
Object.values(modules).forEach((module) => {
|
||||
if (typeof module.default === 'object' && module.default && typeof module.default.install === 'function') {
|
||||
app.use(module.default);
|
||||
}
|
||||
else if (typeof module.default === 'function') {
|
||||
app.use(module.default);
|
||||
}
|
||||
else {
|
||||
console.warn('插件模块无效:', module);
|
||||
}
|
||||
});
|
||||
}
|
||||
13
src/plugins/modules/elementPlusIconsPlugin.ts
Normal file
13
src/plugins/modules/elementPlusIconsPlugin.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import type { App } from 'vue';
|
||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue';
|
||||
|
||||
const elementPlusIconsPlugin = {
|
||||
install(app: App) {
|
||||
// 注册所有ElementPlus图标
|
||||
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||
app.component(key, component);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default elementPlusIconsPlugin;
|
||||
0
src/plugins/modules/globalComponentsPlugin.ts
Normal file
0
src/plugins/modules/globalComponentsPlugin.ts
Normal file
26
src/plugins/modules/utilsPlugin.ts
Normal file
26
src/plugins/modules/utilsPlugin.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import type { App, InjectionKey } from 'vue';
|
||||
import * as common from '../../utils/helper/common';
|
||||
import * as dataHelpers from '../../utils/helper/dataHelpers';
|
||||
import * as is from '../../utils/helper/is';
|
||||
|
||||
export const COMMON_KEY: InjectionKey<typeof common> = Symbol('$common');
|
||||
export const IS_KEY: InjectionKey<typeof is> = Symbol('$is');
|
||||
export const DATA_HELPERS_KEY: InjectionKey<typeof dataHelpers> = Symbol('$dataHelpers');
|
||||
|
||||
const utilsPlugin = {
|
||||
install(app: App) {
|
||||
// common工具
|
||||
app.config.globalProperties.$common = common;
|
||||
app.provide(COMMON_KEY, common);
|
||||
|
||||
// 类型检测工具
|
||||
app.config.globalProperties.$is = is;
|
||||
app.provide(IS_KEY, is);
|
||||
|
||||
// 数据处理工具
|
||||
app.config.globalProperties.$dataHelpers = dataHelpers;
|
||||
app.provide(DATA_HELPERS_KEY, dataHelpers);
|
||||
},
|
||||
};
|
||||
|
||||
export default utilsPlugin;
|
||||
@ -1,15 +1,15 @@
|
||||
.el-form{
|
||||
.el-form-item{
|
||||
&:nth-last-child(1){
|
||||
margin-bottom: 0px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// el 弹框不知道为啥宽度会变大
|
||||
.el-popup-parent--hidden {
|
||||
overflow: hidden;
|
||||
width: 100% !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
@ -22,29 +22,73 @@
|
||||
|
||||
// rounded-tooltip 提示框样式
|
||||
.rounded-tooltip {
|
||||
border-radius: 10px !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: fit-content;
|
||||
border-radius: 10px !important;
|
||||
}
|
||||
|
||||
.rounded-tooltip-enter-from {
|
||||
transform: scale(0.9); /* 进入前:缩小隐藏 */
|
||||
opacity: 0;
|
||||
transform: scale(0.9); /* 进入前:缩小隐藏 */
|
||||
}
|
||||
.rounded-tooltip-enter-active,
|
||||
.rounded-tooltip-leave-active {
|
||||
transition: transform 0.3s, opacity 0.3s; /* 缓入动画 */
|
||||
}
|
||||
.rounded-tooltip-enter-to {
|
||||
transform: scale(1); /* 进入后:正常大小 */
|
||||
opacity: 1;
|
||||
transform: scale(1); /* 进入后:正常大小 */
|
||||
}
|
||||
.rounded-tooltip-leave-from {
|
||||
transform: scale(1); /* 离开前:正常大小 */
|
||||
opacity: 1;
|
||||
transform: scale(1); /* 离开前:正常大小 */
|
||||
}
|
||||
.rounded-tooltip-leave-to {
|
||||
transform: scale(0.9); /* 离开后:缩小隐藏 */
|
||||
opacity: 0;
|
||||
transform: scale(0.9); /* 离开后:缩小隐藏 */
|
||||
}
|
||||
|
||||
/* S Dialog 样式行为 */
|
||||
.dialog-fade-enter-active .el-dialog {
|
||||
animation: dialog-open 0.2s cubic-bezier(0.33, 0.89, 0.25, 1.02);
|
||||
}
|
||||
.dialog-fade-leave-active {
|
||||
animation: fade-out 0.2s linear;
|
||||
}
|
||||
.dialog-fade-leave-active .el-dialog {
|
||||
animation: dialog-close 0.2s cubic-bezier(0.78, 0.14, 0.15, 0.86);
|
||||
}
|
||||
|
||||
@keyframes dialog-open {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: scale(0.2);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes dialog-close {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: scale(0.2);
|
||||
}
|
||||
}
|
||||
|
||||
// 遮罩层动画
|
||||
@keyframes fade-out {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* E Dialog 样式行为 */
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
--login-dialog-section-padding: 0px;
|
||||
--login-dialog-border-radius: 24px;
|
||||
--login-dialog-mode-toggle-color: #409eff;
|
||||
--login-dialog-logo-background: #fff;
|
||||
--login-dialog-logo-background: #ffffff;
|
||||
--login-dialog-logo-text-color: #191919;
|
||||
|
||||
|
||||
|
||||
7
src/utils/helper/common.ts
Normal file
7
src/utils/helper/common.ts
Normal file
@ -0,0 +1,7 @@
|
||||
/**
|
||||
* 非处理数据的功能性工具汇总
|
||||
*
|
||||
*/
|
||||
export function test() {
|
||||
return '';
|
||||
}
|
||||
30
src/utils/helper/dataHelpers.ts
Normal file
30
src/utils/helper/dataHelpers.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import type { MomentInput } from 'moment';
|
||||
import moment from 'moment';
|
||||
|
||||
/**
|
||||
* 数据处理工具汇总
|
||||
* formatDate 日期格式化
|
||||
*/
|
||||
|
||||
/**
|
||||
* 日期格式化字符串形式
|
||||
* @param date 日期输入(支持时间戳、日期字符串、Date 对象或 Moment 对象)
|
||||
* @param format 模式,默认为 'YYYY-MM-DD HH:mm:ss'
|
||||
* @returns 格式化时间字符串,若输入无效则返回空字符串
|
||||
*/
|
||||
export function formatDate(
|
||||
date: MomentInput | null | undefined,
|
||||
format: string = 'YYYY-MM-DD HH:mm:ss',
|
||||
): string {
|
||||
if (!date) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const momentDate = moment(date);
|
||||
if (!momentDate.isValid()) {
|
||||
console.warn('Invalid date input:', date);
|
||||
return '';
|
||||
}
|
||||
|
||||
return momentDate.format(format);
|
||||
}
|
||||
59
src/utils/helper/is.ts
Normal file
59
src/utils/helper/is.ts
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 数据类型检测工具汇总
|
||||
* * * @function isArray: 检测给定的值是否为数组
|
||||
* * * @function isObject: 检测给定的值是否为对象(注意:在JavaScript中,数组和null也会被认为是对象)
|
||||
* * * @function isString: 检测给定的值是否为字符串
|
||||
* * * @function isNumber: 检测给定的值是否为数字
|
||||
* * * @function isFunction: 检测给定的值是否为函数(包括异步函数)
|
||||
* * * @function isAsyncFunction: 检测给定的值是否为异步函数
|
||||
* * * @function isRegExp: 检测给定的值是否为正则表达式对象
|
||||
* * * @function isDef: 检测给定的值是否已定义(即不是undefined)
|
||||
* * * @function isUnDef: 检测给定的值是否未定义(即为undefined)
|
||||
* * * @function isNull: 检测给定的值是否为null
|
||||
*/
|
||||
|
||||
type DataType = 'Array' | 'Object' | 'String' | 'Number' | 'Function' | 'AsyncFunction' | 'RegExp' | 'Undefined' | 'Null';
|
||||
|
||||
export function dataTypeCheck<T extends DataType>(value: unknown, type: T): value is T extends 'Array' ? unknown[] : T extends 'Object' ? object : T extends 'String' ? string : T extends 'Number' ? number : T extends 'Function' ? (...args: any[]) => any : T extends 'AsyncFunction' ? (...args: any[]) => Promise<any> : T extends 'RegExp' ? RegExp : T extends 'Undefined' ? undefined : T extends 'Null' ? null : never {
|
||||
return Object.prototype.toString.call(value) === `[object ${type}]`;
|
||||
}
|
||||
|
||||
export function isArray(value: unknown): value is unknown[] {
|
||||
return dataTypeCheck(value, 'Array');
|
||||
}
|
||||
|
||||
export function isObject(value: unknown): value is object {
|
||||
return dataTypeCheck(value, 'Object');
|
||||
}
|
||||
|
||||
export function isString(value: unknown): value is string {
|
||||
return dataTypeCheck(value, 'String');
|
||||
}
|
||||
|
||||
export function isNumber(value: unknown): value is number {
|
||||
return dataTypeCheck(value, 'Number');
|
||||
}
|
||||
|
||||
export function isFunction(value: unknown): value is (...args: any[]) => any {
|
||||
return dataTypeCheck(value, 'Function') || isAsyncFunction(value);
|
||||
}
|
||||
|
||||
export function isAsyncFunction(value: unknown): value is (...args: any[]) => Promise<any> {
|
||||
return dataTypeCheck(value, 'AsyncFunction');
|
||||
}
|
||||
|
||||
export function isRegExp(value: unknown): value is RegExp {
|
||||
return dataTypeCheck(value, 'RegExp');
|
||||
}
|
||||
|
||||
export function isDef<T>(value: T): value is NonNullable<T> {
|
||||
return !dataTypeCheck(value, 'Undefined');
|
||||
}
|
||||
|
||||
export function isUnDef(value: unknown): value is undefined {
|
||||
return !isDef(value);
|
||||
}
|
||||
|
||||
export function isNull(value: unknown): value is null {
|
||||
return dataTypeCheck(value, 'Null');
|
||||
}
|
||||
@ -13,6 +13,8 @@ export default defineConfig((cnf) => {
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
"~": path.resolve(__dirname, "./src/assets"),
|
||||
"GCnps": path.resolve(__dirname, "./src/components"),
|
||||
},
|
||||
},
|
||||
css: {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user