mirror of
https://github.com/soybeanjs/soybean-admin.git
synced 2025-09-20 18:46:39 +08:00
chore(projects): merge branch main into example
This commit is contained in:
commit
4a7e9c2669
38
CHANGELOG.md
38
CHANGELOG.md
@ -1,6 +1,44 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
|
||||||
|
## [v1.3.0](https://github.com/soybeanjs/soybean-admin/compare/v1.2.8...v1.3.0) (2024-07-22)
|
||||||
|
|
||||||
|
### 🚨 Breaking Changes
|
||||||
|
|
||||||
|
- **projects**: refactor global menu & support `reversed-horizontal-mix-menu`. close #365 - by @honghuangdc in https://github.com/soybeanjs/soybean-admin/issues/365 [<samp>(087e5)</samp>](https://github.com/soybeanjs/soybean-admin/commit/087e532)
|
||||||
|
|
||||||
|
### 🚀 Features
|
||||||
|
|
||||||
|
- **packages**:
|
||||||
|
- `@sa/scripts`: command `gitCommit` support chinese - by @mmdapl in https://github.com/soybeanjs/soybean-admin/issues/548 [<samp>(06971)</samp>](https://github.com/soybeanjs/soybean-admin/commit/06971f3)
|
||||||
|
- @sa/axios: replace CancelTokenSource by AbortController. close #530, close #532 - by @honghuangdc in https://github.com/soybeanjs/soybean-admin/issues/530 and https://github.com/soybeanjs/soybean-admin/issues/532 [<samp>(527fd)</samp>](https://github.com/soybeanjs/soybean-admin/commit/527fd79)
|
||||||
|
- @sa/scripts: add ignore pattern list for command `gitCommitVerify`. close #504 - by @honghuangdc in https://github.com/soybeanjs/soybean-admin/issues/504 [<samp>(958d0)</samp>](https://github.com/soybeanjs/soybean-admin/commit/958d0ba)
|
||||||
|
- **projects**:
|
||||||
|
- make branch `main` tiny & modify request retry times to 0 - by @Azir-11 [<samp>(793b1)</samp>](https://github.com/soybeanjs/soybean-admin/commit/793b16e)
|
||||||
|
|
||||||
|
### 🐞 Bug Fixes
|
||||||
|
|
||||||
|
- **hooks**: prevent program freezing when pagesize returns 0 - by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/545 [<samp>(f4eeb)</samp>](https://github.com/soybeanjs/soybean-admin/commit/f4eeb2e)
|
||||||
|
|
||||||
|
### 💅 Refactors
|
||||||
|
|
||||||
|
- **projects**:
|
||||||
|
- combine `theme tokens` and `theme settings`. close #379 - by @honghuangdc in https://github.com/soybeanjs/soybean-admin/issues/379 [<samp>(1d1b1)</samp>](https://github.com/soybeanjs/soybean-admin/commit/1d1b148)
|
||||||
|
- change css vars mount to root - by @honghuangdc [<samp>(00f41)</samp>](https://github.com/soybeanjs/soybean-admin/commit/00f41dd)
|
||||||
|
|
||||||
|
### 📖 Documentation
|
||||||
|
|
||||||
|
- **projects**: update CHANGELOG - by @honghuangdc [<samp>(a0b76)</samp>](https://github.com/soybeanjs/soybean-admin/commit/a0b76da)
|
||||||
|
|
||||||
|
### 🏡 Chore
|
||||||
|
|
||||||
|
- **deps**: update deps - by @honghuangdc [<samp>(f6bd6)</samp>](https://github.com/soybeanjs/soybean-admin/commit/f6bd6b8)
|
||||||
|
- **projects**: add script `czh` - by @honghuangdc [<samp>(02069)</samp>](https://github.com/soybeanjs/soybean-admin/commit/0206969)
|
||||||
|
|
||||||
|
### ❤️ Contributors
|
||||||
|
|
||||||
|
[](https://github.com/honghuangdc) [](https://github.com/mmdapl) [](https://github.com/Azir-11)
|
||||||
|
|
||||||
## [v1.2.8](https://github.com/soybeanjs/soybean-admin/compare/v1.2.7...v1.2.8) (2024-07-20)
|
## [v1.2.8](https://github.com/soybeanjs/soybean-admin/compare/v1.2.7...v1.2.8) (2024-07-20)
|
||||||
|
|
||||||
### 🐞 Bug Fixes
|
### 🐞 Bug Fixes
|
||||||
|
@ -1,6 +1,73 @@
|
|||||||
# 更新日志
|
# 更新日志
|
||||||
|
|
||||||
|
|
||||||
|
## [v1.3.0](https://github.com/soybeanjs/soybean-admin/compare/v1.2.8...v1.3.0) (2024-07-22)
|
||||||
|
|
||||||
|
### 🚨 破坏性变更
|
||||||
|
|
||||||
|
- **项目**: 重构全局菜单 & 支持 `reversed-horizontal-mix-menu`。关闭 #365 - 由 @honghuangdc 在 https://github.com/soybeanjs/soybean-admin/issues/365 提出 [<samp>(087e5)</samp>](https://github.com/soybeanjs/soybean-admin/commit/087e532)
|
||||||
|
|
||||||
|
### 🚀 功能
|
||||||
|
|
||||||
|
- **包**:
|
||||||
|
- `@sa/scripts`: 命令 `gitCommit` 支持中文 - 由 @mmdapl 在 https://github.com/soybeanjs/soybean-admin/issues/548 提出 [<samp>(06971)</samp>](https://github.com/soybeanjs/soybean-admin/commit/06971f3)
|
||||||
|
- @sa/axios: 用 AbortController 替换 CancelTokenSource。关闭 #530, 关闭 #532 - 由 @honghuangdc 在 https://github.com/soybeanjs/soybean-admin/issues/530 和 https://github.com/soybeanjs/soybean-admin/issues/532 提出 [<samp>(527fd)</samp>](https://github.com/soybeanjs/soybean-admin/commit/527fd79)
|
||||||
|
- @sa/scripts: 为命令 `gitCommitVerify` 添加忽略模式列表。关闭 #504 - 由 @honghuangdc 在 https://github.com/soybeanjs/soybean-admin/issues/504 提出 [<samp>(958d0)</samp>](https://github.com/soybeanjs/soybean-admin/commit/958d0ba)
|
||||||
|
- **项目**:
|
||||||
|
- 使分支 `main` 更精简 & 修改请求重试次数为 0 - 由 @Azir-11 提出 [<samp>(793b1)</samp>](https://github.com/soybeanjs/soybean-admin/commit/793b16e)
|
||||||
|
|
||||||
|
### 🐞 修复
|
||||||
|
|
||||||
|
- **钩子**: 当 pagesize 返回 0 时防止程序冻结 - 由 @Azir-11 在 https://github.com/soybeanjs/soybean-admin/issues/545 提出 [<samp>(f4eeb)</samp>](https://github.com/soybeanjs/soybean-admin/commit/f4eeb2e)
|
||||||
|
|
||||||
|
### 💅 重构
|
||||||
|
|
||||||
|
- **项目**:
|
||||||
|
- 合并 `theme tokens` 和 `theme settings`。关闭 #379 - 由 @honghuangdc 在 https://github.com/soybeanjs/soybean-admin/issues/379 提出 [<samp>(1d1b1)</samp>](https://github.com/soybeanjs/soybean-admin/commit/1d1b148)
|
||||||
|
- 将 css 变量挂载到 root - 由 @honghuangdc 提出 [<samp>(00f41)</samp>](https://github.com/soybeanjs/soybean-admin/commit/00f41dd)
|
||||||
|
|
||||||
|
### 📖 文档
|
||||||
|
|
||||||
|
- **项目**: 更新更新日志 - 由 @honghuangdc 提出 [<samp>(a0b76)</samp>](https://github.com/soybeanjs/soybean-admin/commit/a0b76da)
|
||||||
|
|
||||||
|
### 🏡 杂务
|
||||||
|
|
||||||
|
- **依赖**: 更新依赖 - 由 @honghuangdc 提出 [<samp>(f6bd6)</samp>](https://github.com/soybeanjs/soybean-admin/commit/f6bd6b8)
|
||||||
|
- **项目**: 添加脚本 `czh` - 由 @honghuangdc 提出 [<samp>(02069)</samp>](https://github.com/soybeanjs/soybean-admin/commit/0206969)
|
||||||
|
|
||||||
|
### ❤️ 贡献者
|
||||||
|
|
||||||
|
[](https://github.com/honghuangdc) [](https://github.com/mmdapl) [](https://github.com/Azir-11)
|
||||||
|
|
||||||
|
|
||||||
|
## [v1.2.8](https://github.com/soybeanjs/soybean-admin/compare/v1.2.7...v1.2.8) (2024-07-20)
|
||||||
|
|
||||||
|
### 🐞 修复
|
||||||
|
|
||||||
|
- **包**:
|
||||||
|
- @sa/hooks: 修复 useHookTable 的 searchParams。修复了 #552 - 由 @honghuangdc 在 https://github.com/soybeanjs/soybean-admin/issues/552 提出 [<samp>(96c10)</samp>](https://github.com/soybeanjs/soybean-admin/commit/96c1044)
|
||||||
|
- **类型**:
|
||||||
|
- 修复了引用类型错误 - 由 **dodu2014** 在 https://github.com/soybeanjs/soybean-admin/issues/551 提出 [<samp>(3e2a9)</samp>](https://github.com/soybeanjs/soybean-admin/commit/3e2a993)
|
||||||
|
- 修复 useHookTable 的数据类型 - 由 @honghuangdc 提出 [<samp>(276ea)</samp>](https://github.com/soybeanjs/soybean-admin/commit/276ea7f)
|
||||||
|
|
||||||
|
### 💅 重构
|
||||||
|
|
||||||
|
- **项目**: 用 `klona` 替换 `lodash-es` 的 `cloneDeep` - 由 @honghuangdc 提出 [<samp>(a9133)</samp>](https://github.com/soybeanjs/soybean-admin/commit/a91335d)
|
||||||
|
|
||||||
|
### 📖 文档
|
||||||
|
|
||||||
|
- **项目**: 更新更新日志 - 由 @honghuangdc 提出 [<samp>(58fc0)</samp>](https://github.com/soybeanjs/soybean-admin/commit/58fc096)
|
||||||
|
|
||||||
|
### 🏡 杂务
|
||||||
|
|
||||||
|
- **依赖**: 更新依赖 - 由 @honghuangdc 提出 [<samp>(cf019)</samp>](https://github.com/soybeanjs/soybean-admin/commit/cf0192a)
|
||||||
|
|
||||||
|
### ❤️ 贡献者
|
||||||
|
|
||||||
|
[](https://github.com/honghuangdc)
|
||||||
|
[dodu2014](mailto:dodu@live.cn)
|
||||||
|
|
||||||
|
|
||||||
## [v1.2.7](https://github.com/honghuangdc/soybean-admin/compare/v1.2.6...v1.2.7) (2024-07-12)
|
## [v1.2.7](https://github.com/honghuangdc/soybean-admin/compare/v1.2.6...v1.2.7) (2024-07-12)
|
||||||
|
|
||||||
### 🛠 优化
|
### 🛠 优化
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "soybean-admin",
|
"name": "soybean-admin",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "1.2.8",
|
"version": "1.3.0",
|
||||||
"description": "A fresh and elegant admin template, based on Vue3、Vite3、TypeScript、NaiveUI and UnoCSS. 一个基于Vue3、Vite3、TypeScript、NaiveUI and UnoCSS的清新优雅的中后台模版。",
|
"description": "A fresh and elegant admin template, based on Vue3、Vite3、TypeScript、NaiveUI and UnoCSS. 一个基于Vue3、Vite3、TypeScript、NaiveUI and UnoCSS的清新优雅的中后台模版。",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Soybean",
|
"name": "Soybean",
|
||||||
@ -35,6 +35,7 @@
|
|||||||
"build:test": "vite build --mode test",
|
"build:test": "vite build --mode test",
|
||||||
"cleanup": "sa cleanup",
|
"cleanup": "sa cleanup",
|
||||||
"commit": "sa git-commit",
|
"commit": "sa git-commit",
|
||||||
|
"czh": "sa git-commit -l=zh-cn",
|
||||||
"dev": "vite --mode test",
|
"dev": "vite --mode test",
|
||||||
"dev:prod": "vite --mode prod",
|
"dev:prod": "vite --mode prod",
|
||||||
"gen-route": "sa gen-route",
|
"gen-route": "sa gen-route",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@sa/axios",
|
"name": "@sa/axios",
|
||||||
"version": "1.2.8",
|
"version": "1.3.0",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts"
|
".": "./src/index.ts"
|
||||||
},
|
},
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import axios, { AxiosError } from 'axios';
|
import axios, { AxiosError } from 'axios';
|
||||||
import type { AxiosResponse, CancelTokenSource, CreateAxiosDefaults, InternalAxiosRequestConfig } from 'axios';
|
import type { AxiosResponse, CreateAxiosDefaults, InternalAxiosRequestConfig } from 'axios';
|
||||||
import axiosRetry from 'axios-retry';
|
import axiosRetry from 'axios-retry';
|
||||||
import { nanoid } from '@sa/utils';
|
import { nanoid } from '@sa/utils';
|
||||||
import { createAxiosConfig, createDefaultOptions, createRetryOptions } from './options';
|
import { createAxiosConfig, createDefaultOptions, createRetryOptions } from './options';
|
||||||
@ -22,7 +22,7 @@ function createCommonRequest<ResponseData = any>(
|
|||||||
const axiosConf = createAxiosConfig(axiosConfig);
|
const axiosConf = createAxiosConfig(axiosConfig);
|
||||||
const instance = axios.create(axiosConf);
|
const instance = axios.create(axiosConf);
|
||||||
|
|
||||||
const cancelTokenSourceMap = new Map<string, CancelTokenSource>();
|
const abortControllerMap = new Map<string, AbortController>();
|
||||||
|
|
||||||
// config axios retry
|
// config axios retry
|
||||||
const retryOptions = createRetryOptions(axiosConf);
|
const retryOptions = createRetryOptions(axiosConf);
|
||||||
@ -35,10 +35,12 @@ function createCommonRequest<ResponseData = any>(
|
|||||||
const requestId = nanoid();
|
const requestId = nanoid();
|
||||||
config.headers.set(REQUEST_ID_KEY, requestId);
|
config.headers.set(REQUEST_ID_KEY, requestId);
|
||||||
|
|
||||||
// config cancel token
|
// config abort controller
|
||||||
const cancelTokenSource = axios.CancelToken.source();
|
if (!config.signal) {
|
||||||
config.cancelToken = cancelTokenSource.token;
|
const abortController = new AbortController();
|
||||||
cancelTokenSourceMap.set(requestId, cancelTokenSource);
|
config.signal = abortController.signal;
|
||||||
|
abortControllerMap.set(requestId, abortController);
|
||||||
|
}
|
||||||
|
|
||||||
// handle config by hook
|
// handle config by hook
|
||||||
const handledConfig = opts.onRequest?.(config) || config;
|
const handledConfig = opts.onRequest?.(config) || config;
|
||||||
@ -79,18 +81,18 @@ function createCommonRequest<ResponseData = any>(
|
|||||||
);
|
);
|
||||||
|
|
||||||
function cancelRequest(requestId: string) {
|
function cancelRequest(requestId: string) {
|
||||||
const cancelTokenSource = cancelTokenSourceMap.get(requestId);
|
const abortController = abortControllerMap.get(requestId);
|
||||||
if (cancelTokenSource) {
|
if (abortController) {
|
||||||
cancelTokenSource.cancel();
|
abortController.abort();
|
||||||
cancelTokenSourceMap.delete(requestId);
|
abortControllerMap.delete(requestId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function cancelAllRequest() {
|
function cancelAllRequest() {
|
||||||
cancelTokenSourceMap.forEach(cancelTokenSource => {
|
abortControllerMap.forEach(abortController => {
|
||||||
cancelTokenSource.cancel();
|
abortController.abort();
|
||||||
});
|
});
|
||||||
cancelTokenSourceMap.clear();
|
abortControllerMap.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -20,7 +20,7 @@ export function createDefaultOptions<ResponseData = any>(options?: Partial<Reque
|
|||||||
|
|
||||||
export function createRetryOptions(config?: Partial<CreateAxiosDefaults>) {
|
export function createRetryOptions(config?: Partial<CreateAxiosDefaults>) {
|
||||||
const retryConfig: IAxiosRetryConfig = {
|
const retryConfig: IAxiosRetryConfig = {
|
||||||
retries: 3
|
retries: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.assign(retryConfig, config);
|
Object.assign(retryConfig, config);
|
||||||
|
@ -69,7 +69,19 @@ export type CustomAxiosRequestConfig<R extends ResponseType = 'json'> = Omit<Axi
|
|||||||
};
|
};
|
||||||
|
|
||||||
export interface RequestInstanceCommon<T> {
|
export interface RequestInstanceCommon<T> {
|
||||||
|
/**
|
||||||
|
* cancel the request by request id
|
||||||
|
*
|
||||||
|
* if the request provide abort controller sign from config, it will not collect in the abort controller map
|
||||||
|
*
|
||||||
|
* @param requestId
|
||||||
|
*/
|
||||||
cancelRequest: (requestId: string) => void;
|
cancelRequest: (requestId: string) => void;
|
||||||
|
/**
|
||||||
|
* cancel all request
|
||||||
|
*
|
||||||
|
* if the request provide abort controller sign from config, it will not collect in the abort controller map
|
||||||
|
*/
|
||||||
cancelAllRequest: () => void;
|
cancelAllRequest: () => void;
|
||||||
/** you can set custom state in the request instance */
|
/** you can set custom state in the request instance */
|
||||||
state: T;
|
state: T;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@sa/color",
|
"name": "@sa/color",
|
||||||
"version": "1.2.8",
|
"version": "1.3.0",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts"
|
".": "./src/index.ts"
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@sa/hooks",
|
"name": "@sa/hooks",
|
||||||
"version": "1.2.8",
|
"version": "1.3.0",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts"
|
".": "./src/index.ts"
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@sa/materials",
|
"name": "@sa/materials",
|
||||||
"version": "1.2.8",
|
"version": "1.3.0",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts"
|
".": "./src/index.ts"
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@sa/fetch",
|
"name": "@sa/fetch",
|
||||||
"version": "1.2.8",
|
"version": "1.3.0",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts"
|
".": "./src/index.ts"
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@sa/scripts",
|
"name": "@sa/scripts",
|
||||||
"version": "1.2.8",
|
"version": "1.3.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
"sa": "./bin.ts"
|
"sa": "./bin.ts"
|
||||||
},
|
},
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { readFileSync } from 'node:fs';
|
import { readFileSync } from 'node:fs';
|
||||||
import { prompt } from 'enquirer';
|
import { prompt } from 'enquirer';
|
||||||
import { bgRed, green, red, yellow } from 'kolorist';
|
|
||||||
import { execCommand } from '../shared';
|
import { execCommand } from '../shared';
|
||||||
import type { CliOption } from '../types';
|
import { locales } from '../locales';
|
||||||
|
import type { Lang } from '../locales';
|
||||||
|
|
||||||
interface PromptObject {
|
interface PromptObject {
|
||||||
types: string;
|
types: string;
|
||||||
@ -14,13 +14,11 @@ interface PromptObject {
|
|||||||
/**
|
/**
|
||||||
* Git commit with Conventional Commits standard
|
* Git commit with Conventional Commits standard
|
||||||
*
|
*
|
||||||
* @param gitCommitTypes
|
* @param lang
|
||||||
* @param gitCommitScopes
|
|
||||||
*/
|
*/
|
||||||
export async function gitCommit(
|
export async function gitCommit(lang: Lang = 'en-us') {
|
||||||
gitCommitTypes: CliOption['gitCommitTypes'],
|
const { gitCommitMessages, gitCommitTypes, gitCommitScopes } = locales[lang];
|
||||||
gitCommitScopes: CliOption['gitCommitScopes']
|
|
||||||
) {
|
|
||||||
const typesChoices = gitCommitTypes.map(([value, msg]) => {
|
const typesChoices = gitCommitTypes.map(([value, msg]) => {
|
||||||
const nameWithSuffix = `${value}:`;
|
const nameWithSuffix = `${value}:`;
|
||||||
|
|
||||||
@ -41,19 +39,19 @@ export async function gitCommit(
|
|||||||
{
|
{
|
||||||
name: 'types',
|
name: 'types',
|
||||||
type: 'select',
|
type: 'select',
|
||||||
message: 'Please select a type',
|
message: gitCommitMessages.types,
|
||||||
choices: typesChoices
|
choices: typesChoices
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'scopes',
|
name: 'scopes',
|
||||||
type: 'select',
|
type: 'select',
|
||||||
message: 'Please select a scope',
|
message: gitCommitMessages.scopes,
|
||||||
choices: scopesChoices
|
choices: scopesChoices
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'description',
|
name: 'description',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
message: `Please enter a description (add prefix ${yellow('!')} to indicate breaking change)`
|
message: gitCommitMessages.description
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -67,20 +65,20 @@ export async function gitCommit(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Git commit message verify */
|
/** Git commit message verify */
|
||||||
export async function gitCommitVerify() {
|
export async function gitCommitVerify(lang: Lang = 'en-us', ignores: RegExp[] = []) {
|
||||||
const gitPath = await execCommand('git', ['rev-parse', '--show-toplevel']);
|
const gitPath = await execCommand('git', ['rev-parse', '--show-toplevel']);
|
||||||
|
|
||||||
const gitMsgPath = path.join(gitPath, '.git', 'COMMIT_EDITMSG');
|
const gitMsgPath = path.join(gitPath, '.git', 'COMMIT_EDITMSG');
|
||||||
|
|
||||||
const commitMsg = readFileSync(gitMsgPath, 'utf8').trim();
|
const commitMsg = readFileSync(gitMsgPath, 'utf8').trim();
|
||||||
|
|
||||||
|
if (ignores.some(regExp => regExp.test(commitMsg))) return;
|
||||||
|
|
||||||
const REG_EXP = /(?<type>[a-z]+)(?:\((?<scope>.+)\))?(?<breaking>!)?: (?<description>.+)/i;
|
const REG_EXP = /(?<type>[a-z]+)(?:\((?<scope>.+)\))?(?<breaking>!)?: (?<description>.+)/i;
|
||||||
|
|
||||||
if (!REG_EXP.test(commitMsg)) {
|
if (!REG_EXP.test(commitMsg)) {
|
||||||
throw new Error(
|
const errorMsg = locales[lang].gitCommitVerify;
|
||||||
`${bgRed(' ERROR ')} ${red('git commit message must match the Conventional Commits standard!')}\n\n${green(
|
|
||||||
'Recommended to use the command `pnpm commit` to generate Conventional Commits compliant commit information.\nGet more info about Conventional Commits, follow this link: https://conventionalcommits.org'
|
throw new Error(errorMsg);
|
||||||
)}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,34 +12,18 @@ const defaultOptions: CliOption = {
|
|||||||
'**/node_modules',
|
'**/node_modules',
|
||||||
'!node_modules/**'
|
'!node_modules/**'
|
||||||
],
|
],
|
||||||
gitCommitTypes: [
|
|
||||||
['feat', 'A new feature'],
|
|
||||||
['fix', 'A bug fix'],
|
|
||||||
['docs', 'Documentation only changes'],
|
|
||||||
['style', 'Changes that do not affect the meaning of the code'],
|
|
||||||
['refactor', 'A code change that neither fixes a bug nor adds a feature'],
|
|
||||||
['perf', 'A code change that improves performance'],
|
|
||||||
['optimize', 'A code change that optimizes code quality'],
|
|
||||||
['test', 'Adding missing tests or correcting existing tests'],
|
|
||||||
['build', 'Changes that affect the build system or external dependencies'],
|
|
||||||
['ci', 'Changes to our CI configuration files and scripts'],
|
|
||||||
['chore', "Other changes that don't modify src or test files"],
|
|
||||||
['revert', 'Reverts a previous commit']
|
|
||||||
],
|
|
||||||
gitCommitScopes: [
|
|
||||||
['projects', 'project'],
|
|
||||||
['packages', 'packages'],
|
|
||||||
['components', 'components'],
|
|
||||||
['hooks', 'hook functions'],
|
|
||||||
['utils', 'utils functions'],
|
|
||||||
['types', 'TS declaration'],
|
|
||||||
['styles', 'style'],
|
|
||||||
['deps', 'project dependencies'],
|
|
||||||
['release', 'release project'],
|
|
||||||
['other', 'other changes']
|
|
||||||
],
|
|
||||||
ncuCommandArgs: ['--deep', '-u'],
|
ncuCommandArgs: ['--deep', '-u'],
|
||||||
changelogOptions: {}
|
changelogOptions: {},
|
||||||
|
gitCommitVerifyIgnores: [
|
||||||
|
/^((Merge pull request)|(Merge (.*?) into (.*?)|(Merge branch (.*?)))(?:\r?\n)*$)/m,
|
||||||
|
/^(Merge tag (.*?))(?:\r?\n)*$/m,
|
||||||
|
/^(R|r)evert (.*)/,
|
||||||
|
/^(amend|fixup|squash)!/,
|
||||||
|
/^(Merged (.*?)(in|into) (.*)|Merged PR (.*): (.*))/,
|
||||||
|
/^Merge remote-tracking branch(\s*)(.*)/,
|
||||||
|
/^Automatic merge(.*)/,
|
||||||
|
/^Auto-merged (.*?) into (.*)/
|
||||||
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function loadCliOptions(overrides?: Partial<CliOption>, cwd = process.cwd()) {
|
export async function loadCliOptions(overrides?: Partial<CliOption>, cwd = process.cwd()) {
|
||||||
|
@ -3,6 +3,7 @@ import { blue, lightGreen } from 'kolorist';
|
|||||||
import { version } from '../package.json';
|
import { version } from '../package.json';
|
||||||
import { cleanup, genChangelog, generateRoute, gitCommit, gitCommitVerify, release, updatePkg } from './commands';
|
import { cleanup, genChangelog, generateRoute, gitCommit, gitCommitVerify, release, updatePkg } from './commands';
|
||||||
import { loadCliOptions } from './config';
|
import { loadCliOptions } from './config';
|
||||||
|
import type { Lang } from './locales';
|
||||||
|
|
||||||
type Command = 'cleanup' | 'update-pkg' | 'git-commit' | 'git-commit-verify' | 'changelog' | 'release' | 'gen-route';
|
type Command = 'cleanup' | 'update-pkg' | 'git-commit' | 'git-commit-verify' | 'changelog' | 'release' | 'gen-route';
|
||||||
|
|
||||||
@ -18,13 +19,19 @@ interface CommandArg {
|
|||||||
/** Generate changelog by total tags */
|
/** Generate changelog by total tags */
|
||||||
total?: boolean;
|
total?: boolean;
|
||||||
/**
|
/**
|
||||||
* The glob pattern of dirs to cleanup
|
* The glob pattern of dirs to clean up
|
||||||
*
|
*
|
||||||
* If not set, it will use the default value
|
* If not set, it will use the default value
|
||||||
*
|
*
|
||||||
* Multiple values use "," to separate them
|
* Multiple values use "," to separate them
|
||||||
*/
|
*/
|
||||||
cleanupDir?: string;
|
cleanupDir?: string;
|
||||||
|
/**
|
||||||
|
* display lang of cli
|
||||||
|
*
|
||||||
|
* @default 'en-us'
|
||||||
|
*/
|
||||||
|
lang?: Lang;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setupCli() {
|
export async function setupCli() {
|
||||||
@ -44,6 +51,7 @@ export async function setupCli() {
|
|||||||
'-c, --cleanupDir <dir>',
|
'-c, --cleanupDir <dir>',
|
||||||
'The glob pattern of dirs to cleanup, If not set, it will use the default value, Multiple values use "," to separate them'
|
'The glob pattern of dirs to cleanup, If not set, it will use the default value, Multiple values use "," to separate them'
|
||||||
)
|
)
|
||||||
|
.option('-l, --lang <lang>', 'display lang of cli', { default: 'en-us', type: [String] })
|
||||||
.help();
|
.help();
|
||||||
|
|
||||||
const commands: CommandWithAction<CommandArg> = {
|
const commands: CommandWithAction<CommandArg> = {
|
||||||
@ -61,14 +69,14 @@ export async function setupCli() {
|
|||||||
},
|
},
|
||||||
'git-commit': {
|
'git-commit': {
|
||||||
desc: 'git commit, generate commit message which match Conventional Commits standard',
|
desc: 'git commit, generate commit message which match Conventional Commits standard',
|
||||||
action: async () => {
|
action: async args => {
|
||||||
await gitCommit(cliOptions.gitCommitTypes, cliOptions.gitCommitScopes);
|
await gitCommit(args?.lang);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'git-commit-verify': {
|
'git-commit-verify': {
|
||||||
desc: 'verify git commit message, make sure it match Conventional Commits standard',
|
desc: 'verify git commit message, make sure it match Conventional Commits standard',
|
||||||
action: async () => {
|
action: async args => {
|
||||||
await gitCommitVerify();
|
await gitCommitVerify(args?.lang, cliOptions.gitCommitVerifyIgnores);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
changelog: {
|
changelog: {
|
||||||
|
78
packages/scripts/src/locales/index.ts
Normal file
78
packages/scripts/src/locales/index.ts
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import { bgRed, green, red, yellow } from 'kolorist';
|
||||||
|
|
||||||
|
export type Lang = 'zh-cn' | 'en-us';
|
||||||
|
|
||||||
|
export const locales = {
|
||||||
|
'zh-cn': {
|
||||||
|
gitCommitMessages: {
|
||||||
|
types: '请选择提交类型',
|
||||||
|
scopes: '请选择提交范围',
|
||||||
|
description: `请输入描述信息(${yellow('!')}开头表示破坏性改动`
|
||||||
|
},
|
||||||
|
gitCommitTypes: [
|
||||||
|
['feat', '新功能'],
|
||||||
|
['fix', '修复Bug'],
|
||||||
|
['docs', '只更新文档'],
|
||||||
|
['style', '修改代码风格,不影响代码含义的变更'],
|
||||||
|
['refactor', '代码重构,既不修复 bug 也不添加功能的代码变更'],
|
||||||
|
['perf', '可提高性能的代码更改'],
|
||||||
|
['optimize', '优化代码质量的代码更改'],
|
||||||
|
['test', '添加缺失的测试或更正现有测'],
|
||||||
|
['build', '影响构建系统或外部依赖项的更改'],
|
||||||
|
['ci', '对 CI 配置文件和脚本的更改'],
|
||||||
|
['chore', '没有修改src或测试文件的其他变更'],
|
||||||
|
['revert', '还原先前的提交']
|
||||||
|
] as [string, string][],
|
||||||
|
gitCommitScopes: [
|
||||||
|
['projects', '项目'],
|
||||||
|
['packages', '包'],
|
||||||
|
['components', '组件'],
|
||||||
|
['hooks', '钩子函数'],
|
||||||
|
['utils', '工具函数'],
|
||||||
|
['types', 'TS类型声明'],
|
||||||
|
['styles', '代码风格'],
|
||||||
|
['deps', '项目依赖'],
|
||||||
|
['release', '发布项目新版本'],
|
||||||
|
['other', '其他的变更']
|
||||||
|
] as [string, string][],
|
||||||
|
gitCommitVerify: `${bgRed(' 错误 ')} ${red('git 提交信息必须符合 Conventional Commits 标准!')}\n\n${green(
|
||||||
|
'推荐使用命令 `pnpm commit` 生成符合 Conventional Commits 标准的提交信息。\n获取有关 Conventional Commits 的更多信息,请访问此链接: https://conventionalcommits.org'
|
||||||
|
)}`
|
||||||
|
},
|
||||||
|
'en-us': {
|
||||||
|
gitCommitMessages: {
|
||||||
|
types: 'Please select a type',
|
||||||
|
scopes: 'Please select a scope',
|
||||||
|
description: `Please enter a description (add prefix ${yellow('!')} to indicate breaking change)`
|
||||||
|
},
|
||||||
|
gitCommitTypes: [
|
||||||
|
['feat', 'A new feature'],
|
||||||
|
['fix', 'A bug fix'],
|
||||||
|
['docs', 'Documentation only changes'],
|
||||||
|
['style', 'Changes that do not affect the meaning of the code'],
|
||||||
|
['refactor', 'A code change that neither fixes a bug nor adds a feature'],
|
||||||
|
['perf', 'A code change that improves performance'],
|
||||||
|
['optimize', 'A code change that optimizes code quality'],
|
||||||
|
['test', 'Adding missing tests or correcting existing tests'],
|
||||||
|
['build', 'Changes that affect the build system or external dependencies'],
|
||||||
|
['ci', 'Changes to our CI configuration files and scripts'],
|
||||||
|
['chore', "Other changes that don't modify src or test files"],
|
||||||
|
['revert', 'Reverts a previous commit']
|
||||||
|
] as [string, string][],
|
||||||
|
gitCommitScopes: [
|
||||||
|
['projects', 'project'],
|
||||||
|
['packages', 'packages'],
|
||||||
|
['components', 'components'],
|
||||||
|
['hooks', 'hook functions'],
|
||||||
|
['utils', 'utils functions'],
|
||||||
|
['types', 'TS declaration'],
|
||||||
|
['styles', 'style'],
|
||||||
|
['deps', 'project dependencies'],
|
||||||
|
['release', 'release project'],
|
||||||
|
['other', 'other changes']
|
||||||
|
] as [string, string][],
|
||||||
|
gitCommitVerify: `${bgRed(' ERROR ')} ${red('git commit message must match the Conventional Commits standard!')}\n\n${green(
|
||||||
|
'Recommended to use the command `pnpm commit` to generate Conventional Commits compliant commit information.\nGet more info about Conventional Commits, follow this link: https://conventionalcommits.org'
|
||||||
|
)}`
|
||||||
|
}
|
||||||
|
} satisfies Record<Lang, Record<string, unknown>>;
|
@ -14,10 +14,6 @@ export interface CliOption {
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
cleanupDirs: string[];
|
cleanupDirs: string[];
|
||||||
/** Git commit types */
|
|
||||||
gitCommitTypes: [string, string][];
|
|
||||||
/** Git commit scopes */
|
|
||||||
gitCommitScopes: [string, string][];
|
|
||||||
/**
|
/**
|
||||||
* Npm-check-updates command args
|
* Npm-check-updates command args
|
||||||
*
|
*
|
||||||
@ -30,4 +26,6 @@ export interface CliOption {
|
|||||||
* @link https://github.com/soybeanjs/changelog
|
* @link https://github.com/soybeanjs/changelog
|
||||||
*/
|
*/
|
||||||
changelogOptions: Partial<ChangelogOption>;
|
changelogOptions: Partial<ChangelogOption>;
|
||||||
|
/** The ignore pattern list of git commit verify */
|
||||||
|
gitCommitVerifyIgnores: RegExp[];
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@sa/uno-preset",
|
"name": "@sa/uno-preset",
|
||||||
"version": "1.2.8",
|
"version": "1.3.0",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts"
|
".": "./src/index.ts"
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@sa/utils",
|
"name": "@sa/utils",
|
||||||
"version": "1.2.8",
|
"version": "1.3.0",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts"
|
".": "./src/index.ts"
|
||||||
},
|
},
|
||||||
|
@ -9,7 +9,7 @@ defineProps<Props>();
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="bg-container text-base_text transition-300" :class="{ 'bg-inverted text-#1f1f1f': inverted }">
|
<div class="bg-container text-base-text transition-300" :class="{ 'bg-inverted text-#1f1f1f': inverted }">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
import { transformRecordToOption } from '@/utils/common';
|
import { transformRecordToOption } from '@/utils/common';
|
||||||
|
|
||||||
|
export const GLOBAL_HEADER_MENU_ID = '__GLOBAL_HEADER_MENU__';
|
||||||
|
|
||||||
|
export const GLOBAL_SIDER_MENU_ID = '__GLOBAL_SIDER_MENU__';
|
||||||
|
|
||||||
export const themeSchemaRecord: Record<UnionKey.ThemeScheme, App.I18n.I18nKey> = {
|
export const themeSchemaRecord: Record<UnionKey.ThemeScheme, App.I18n.I18nKey> = {
|
||||||
light: 'theme.themeSchema.light',
|
light: 'theme.themeSchema.light',
|
||||||
dark: 'theme.themeSchema.dark',
|
dark: 'theme.themeSchema.dark',
|
||||||
|
@ -30,17 +30,30 @@ export function useRouterPush(inSetup = true) {
|
|||||||
name: key
|
name: key
|
||||||
};
|
};
|
||||||
|
|
||||||
if (query) {
|
if (Object.keys(query || {}).length) {
|
||||||
routeLocation.query = query;
|
routeLocation.query = query;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params) {
|
if (Object.keys(params || {}).length) {
|
||||||
routeLocation.params = params;
|
routeLocation.params = params;
|
||||||
}
|
}
|
||||||
|
|
||||||
return routerPush(routeLocation);
|
return routerPush(routeLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function routerPushByKeyWithMetaQuery(key: RouteKey) {
|
||||||
|
const allRoutes = router.getRoutes();
|
||||||
|
const meta = allRoutes.find(item => item.name === key)?.meta || null;
|
||||||
|
|
||||||
|
const query: Record<string, string> = {};
|
||||||
|
|
||||||
|
meta?.query?.forEach(item => {
|
||||||
|
query[item.key] = item.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
return routerPushByKey(key, { query });
|
||||||
|
}
|
||||||
|
|
||||||
async function toHome() {
|
async function toHome() {
|
||||||
return routerPushByKey('root');
|
return routerPushByKey('root');
|
||||||
}
|
}
|
||||||
@ -95,6 +108,7 @@ export function useRouterPush(inSetup = true) {
|
|||||||
routerPush,
|
routerPush,
|
||||||
routerBack,
|
routerBack,
|
||||||
routerPushByKey,
|
routerPushByKey,
|
||||||
|
routerPushByKeyWithMetaQuery,
|
||||||
toLogin,
|
toLogin,
|
||||||
toggleLoginModule,
|
toggleLoginModule,
|
||||||
redirectFromLogin
|
redirectFromLogin
|
||||||
|
@ -40,17 +40,20 @@ export function useTable<A extends NaiveUI.TableApiFn>(config: NaiveUI.NaiveTabl
|
|||||||
transformer: res => {
|
transformer: res => {
|
||||||
const { records = [], current = 1, size = 10, total = 0 } = res.data || {};
|
const { records = [], current = 1, size = 10, total = 0 } = res.data || {};
|
||||||
|
|
||||||
|
// Ensure that the size is greater than 0, If it is less than 0, it will cause paging calculation errors.
|
||||||
|
const pageSize = size <= 0 ? 10 : size;
|
||||||
|
|
||||||
const recordsWithIndex = records.map((item, index) => {
|
const recordsWithIndex = records.map((item, index) => {
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
index: (current - 1) * size + index + 1
|
index: (current - 1) * pageSize + index + 1
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data: recordsWithIndex,
|
data: recordsWithIndex,
|
||||||
pageNum: current,
|
pageNum: current,
|
||||||
pageSize: size,
|
pageSize,
|
||||||
total
|
total
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed, defineAsyncComponent } from 'vue';
|
||||||
import { AdminLayout, LAYOUT_SCROLL_EL_ID } from '@sa/materials';
|
import { AdminLayout, LAYOUT_SCROLL_EL_ID } from '@sa/materials';
|
||||||
import type { LayoutMode } from '@sa/materials';
|
import type { LayoutMode } from '@sa/materials';
|
||||||
import { useAppStore } from '@/store/modules/app';
|
import { useAppStore } from '@/store/modules/app';
|
||||||
@ -18,7 +18,9 @@ defineOptions({
|
|||||||
|
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const themeStore = useThemeStore();
|
const themeStore = useThemeStore();
|
||||||
const { menus } = setupMixMenuContext();
|
const { childLevelMenus, isActiveFirstLevelMenuHasChildren } = setupMixMenuContext();
|
||||||
|
|
||||||
|
const GlobalMenu = defineAsyncComponent(() => import('../modules/global-menu/index.vue'));
|
||||||
|
|
||||||
const layoutMode = computed(() => {
|
const layoutMode = computed(() => {
|
||||||
const vertical: LayoutMode = 'vertical';
|
const vertical: LayoutMode = 'vertical';
|
||||||
@ -26,7 +28,10 @@ const layoutMode = computed(() => {
|
|||||||
return themeStore.layout.mode.includes(vertical) ? vertical : horizontal;
|
return themeStore.layout.mode.includes(vertical) ? vertical : horizontal;
|
||||||
});
|
});
|
||||||
|
|
||||||
const headerPropsConfig: Record<UnionKey.ThemeLayoutMode, App.Global.HeaderProps> = {
|
const headerProps = computed(() => {
|
||||||
|
const { mode, reverseHorizontalMix } = themeStore.layout;
|
||||||
|
|
||||||
|
const headerPropsConfig: Record<UnionKey.ThemeLayoutMode, App.Global.HeaderProps> = {
|
||||||
vertical: {
|
vertical: {
|
||||||
showLogo: false,
|
showLogo: false,
|
||||||
showMenu: false,
|
showMenu: false,
|
||||||
@ -45,11 +50,12 @@ const headerPropsConfig: Record<UnionKey.ThemeLayoutMode, App.Global.HeaderProps
|
|||||||
'horizontal-mix': {
|
'horizontal-mix': {
|
||||||
showLogo: true,
|
showLogo: true,
|
||||||
showMenu: true,
|
showMenu: true,
|
||||||
showMenuToggler: false
|
showMenuToggler: reverseHorizontalMix && isActiveFirstLevelMenuHasChildren.value
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const headerProps = computed(() => headerPropsConfig[themeStore.layout.mode]);
|
return headerPropsConfig[mode];
|
||||||
|
});
|
||||||
|
|
||||||
const siderVisible = computed(() => themeStore.layout.mode !== 'horizontal');
|
const siderVisible = computed(() => themeStore.layout.mode !== 'horizontal');
|
||||||
|
|
||||||
@ -62,11 +68,16 @@ const siderWidth = computed(() => getSiderWidth());
|
|||||||
const siderCollapsedWidth = computed(() => getSiderCollapsedWidth());
|
const siderCollapsedWidth = computed(() => getSiderCollapsedWidth());
|
||||||
|
|
||||||
function getSiderWidth() {
|
function getSiderWidth() {
|
||||||
|
const { reverseHorizontalMix } = themeStore.layout;
|
||||||
const { width, mixWidth, mixChildMenuWidth } = themeStore.sider;
|
const { width, mixWidth, mixChildMenuWidth } = themeStore.sider;
|
||||||
|
|
||||||
|
if (isHorizontalMix.value && reverseHorizontalMix) {
|
||||||
|
return isActiveFirstLevelMenuHasChildren.value ? width : 0;
|
||||||
|
}
|
||||||
|
|
||||||
let w = isVerticalMix.value || isHorizontalMix.value ? mixWidth : width;
|
let w = isVerticalMix.value || isHorizontalMix.value ? mixWidth : width;
|
||||||
|
|
||||||
if (isVerticalMix.value && appStore.mixSiderFixed && menus.value.length) {
|
if (isVerticalMix.value && appStore.mixSiderFixed && childLevelMenus.value.length) {
|
||||||
w += mixChildMenuWidth;
|
w += mixChildMenuWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,7 +89,7 @@ function getSiderCollapsedWidth() {
|
|||||||
|
|
||||||
let w = isVerticalMix.value || isHorizontalMix.value ? mixCollapsedWidth : collapsedWidth;
|
let w = isVerticalMix.value || isHorizontalMix.value ? mixCollapsedWidth : collapsedWidth;
|
||||||
|
|
||||||
if (isVerticalMix.value && appStore.mixSiderFixed && menus.value.length) {
|
if (isVerticalMix.value && appStore.mixSiderFixed && childLevelMenus.value.length) {
|
||||||
w += mixChildMenuWidth;
|
w += mixChildMenuWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,6 +127,7 @@ function getSiderCollapsedWidth() {
|
|||||||
<template #sider>
|
<template #sider>
|
||||||
<GlobalSider />
|
<GlobalSider />
|
||||||
</template>
|
</template>
|
||||||
|
<GlobalMenu />
|
||||||
<GlobalContent />
|
<GlobalContent />
|
||||||
<ThemeDrawer />
|
<ThemeDrawer />
|
||||||
<template #footer>
|
<template #footer>
|
||||||
|
@ -26,10 +26,30 @@ function useMixMenu() {
|
|||||||
setActiveFirstLevelMenuKey(firstLevelRouteName);
|
setActiveFirstLevelMenuKey(firstLevelRouteName);
|
||||||
}
|
}
|
||||||
|
|
||||||
const menus = computed(
|
const allMenus = computed<App.Global.Menu[]>(() => routeStore.menus);
|
||||||
|
|
||||||
|
const firstLevelMenus = computed<App.Global.Menu[]>(() =>
|
||||||
|
routeStore.menus.map(menu => {
|
||||||
|
const { children: _, ...rest } = menu;
|
||||||
|
|
||||||
|
return rest;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const childLevelMenus = computed<App.Global.Menu[]>(
|
||||||
() => routeStore.menus.find(menu => menu.key === activeFirstLevelMenuKey.value)?.children || []
|
() => routeStore.menus.find(menu => menu.key === activeFirstLevelMenuKey.value)?.children || []
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isActiveFirstLevelMenuHasChildren = computed(() => {
|
||||||
|
if (!activeFirstLevelMenuKey.value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const findItem = allMenus.value.find(item => item.key === activeFirstLevelMenuKey.value);
|
||||||
|
|
||||||
|
return Boolean(findItem?.children?.length);
|
||||||
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => route.name,
|
() => route.name,
|
||||||
() => {
|
() => {
|
||||||
@ -39,9 +59,12 @@ function useMixMenu() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
allMenus,
|
||||||
|
firstLevelMenus,
|
||||||
|
childLevelMenus,
|
||||||
|
isActiveFirstLevelMenuHasChildren,
|
||||||
activeFirstLevelMenuKey,
|
activeFirstLevelMenuKey,
|
||||||
setActiveFirstLevelMenuKey,
|
setActiveFirstLevelMenuKey,
|
||||||
getActiveFirstLevelMenuKey,
|
getActiveFirstLevelMenuKey
|
||||||
menus
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -68,6 +68,7 @@ function handleDropdown(key: DropdownKey) {
|
|||||||
if (key === 'logout') {
|
if (key === 'logout') {
|
||||||
logout();
|
logout();
|
||||||
} else {
|
} else {
|
||||||
|
// If your other options are jumps from other routes, they will be directly supported here
|
||||||
routerPushByKey(key);
|
routerPushByKey(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
|
||||||
import { useFullscreen } from '@vueuse/core';
|
import { useFullscreen } from '@vueuse/core';
|
||||||
import { useAppStore } from '@/store/modules/app';
|
import { useAppStore } from '@/store/modules/app';
|
||||||
import { useThemeStore } from '@/store/modules/theme';
|
import { useThemeStore } from '@/store/modules/theme';
|
||||||
import { useRouteStore } from '@/store/modules/route';
|
import { GLOBAL_HEADER_MENU_ID } from '@/constants/app';
|
||||||
import HorizontalMenu from '../global-menu/base-menu.vue';
|
|
||||||
import GlobalLogo from '../global-logo/index.vue';
|
import GlobalLogo from '../global-logo/index.vue';
|
||||||
import GlobalBreadcrumb from '../global-breadcrumb/index.vue';
|
import GlobalBreadcrumb from '../global-breadcrumb/index.vue';
|
||||||
import GlobalSearch from '../global-search/index.vue';
|
import GlobalSearch from '../global-search/index.vue';
|
||||||
import { useMixMenuContext } from '../../context';
|
|
||||||
import ThemeButton from './components/theme-button.vue';
|
import ThemeButton from './components/theme-button.vue';
|
||||||
import UserAvatar from './components/user-avatar.vue';
|
import UserAvatar from './components/user-avatar.vue';
|
||||||
|
|
||||||
@ -29,29 +26,15 @@ defineProps<Props>();
|
|||||||
|
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const themeStore = useThemeStore();
|
const themeStore = useThemeStore();
|
||||||
const routeStore = useRouteStore();
|
|
||||||
const { isFullscreen, toggle } = useFullscreen();
|
const { isFullscreen, toggle } = useFullscreen();
|
||||||
const { menus } = useMixMenuContext();
|
|
||||||
|
|
||||||
const headerMenus = computed(() => {
|
|
||||||
if (themeStore.layout.mode === 'horizontal') {
|
|
||||||
return routeStore.menus;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (themeStore.layout.mode === 'horizontal-mix') {
|
|
||||||
return menus.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DarkModeContainer class="h-full flex-y-center px-12px shadow-header">
|
<DarkModeContainer class="h-full flex-y-center px-12px shadow-header">
|
||||||
<GlobalLogo v-if="showLogo" class="h-full" :style="{ width: themeStore.sider.width + 'px' }" />
|
<GlobalLogo v-if="showLogo" class="h-full" :style="{ width: themeStore.sider.width + 'px' }" />
|
||||||
<HorizontalMenu v-if="showMenu" mode="horizontal" :menus="headerMenus" class="px-12px" />
|
|
||||||
<div v-else class="h-full flex-y-center flex-1-hidden">
|
|
||||||
<MenuToggler v-if="showMenuToggler" :collapsed="appStore.siderCollapse" @click="appStore.toggleSiderCollapse" />
|
<MenuToggler v-if="showMenuToggler" :collapsed="appStore.siderCollapse" @click="appStore.toggleSiderCollapse" />
|
||||||
|
<div v-if="showMenu" :id="GLOBAL_HEADER_MENU_ID" class="h-full flex-y-center flex-1-hidden"></div>
|
||||||
|
<div v-else class="h-full flex-y-center flex-1-hidden">
|
||||||
<GlobalBreadcrumb v-if="!appStore.isMobile" class="ml-12px" />
|
<GlobalBreadcrumb v-if="!appStore.isMobile" class="ml-12px" />
|
||||||
</div>
|
</div>
|
||||||
<div class="h-full flex-y-center justify-end">
|
<div class="h-full flex-y-center justify-end">
|
||||||
|
@ -1,96 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { computed, ref, watch } from 'vue';
|
|
||||||
import { useRoute } from 'vue-router';
|
|
||||||
import type { MentionOption, MenuProps } from 'naive-ui';
|
|
||||||
import { SimpleScrollbar } from '@sa/materials';
|
|
||||||
import type { RouteKey } from '@elegant-router/types';
|
|
||||||
import { useAppStore } from '@/store/modules/app';
|
|
||||||
import { useThemeStore } from '@/store/modules/theme';
|
|
||||||
import { useRouteStore } from '@/store/modules/route';
|
|
||||||
import { useRouterPush } from '@/hooks/common/router';
|
|
||||||
|
|
||||||
defineOptions({
|
|
||||||
name: 'BaseMenu'
|
|
||||||
});
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
darkTheme?: boolean;
|
|
||||||
mode?: MenuProps['mode'];
|
|
||||||
menus: App.Global.Menu[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
|
||||||
mode: 'vertical'
|
|
||||||
});
|
|
||||||
|
|
||||||
const route = useRoute();
|
|
||||||
const appStore = useAppStore();
|
|
||||||
const themeStore = useThemeStore();
|
|
||||||
const routeStore = useRouteStore();
|
|
||||||
const { routerPushByKey } = useRouterPush();
|
|
||||||
|
|
||||||
const naiveMenus = computed(() => props.menus as unknown as MentionOption[]);
|
|
||||||
|
|
||||||
const isHorizontal = computed(() => props.mode === 'horizontal');
|
|
||||||
|
|
||||||
const siderCollapse = computed(() => themeStore.layout.mode === 'vertical' && appStore.siderCollapse);
|
|
||||||
|
|
||||||
const headerHeight = computed(() => `${themeStore.header.height}px`);
|
|
||||||
|
|
||||||
const selectedKey = computed(() => {
|
|
||||||
const { hideInMenu, activeMenu } = route.meta;
|
|
||||||
const name = route.name as string;
|
|
||||||
|
|
||||||
const routeName = (hideInMenu ? activeMenu : name) || name;
|
|
||||||
|
|
||||||
return routeName;
|
|
||||||
});
|
|
||||||
|
|
||||||
const expandedKeys = ref<string[]>([]);
|
|
||||||
|
|
||||||
function updateExpandedKeys() {
|
|
||||||
if (isHorizontal.value || siderCollapse.value || !selectedKey.value) {
|
|
||||||
expandedKeys.value = [];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
expandedKeys.value = routeStore.getSelectedMenuKeyPath(selectedKey.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleClickMenu(key: RouteKey) {
|
|
||||||
const query = routeStore.getRouteQueryOfMetaByKey(key);
|
|
||||||
|
|
||||||
routerPushByKey(key, { query });
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => route.name,
|
|
||||||
() => {
|
|
||||||
updateExpandedKeys();
|
|
||||||
},
|
|
||||||
{ immediate: true }
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<SimpleScrollbar>
|
|
||||||
<NMenu
|
|
||||||
v-model:expanded-keys="expandedKeys"
|
|
||||||
:mode="mode"
|
|
||||||
:value="selectedKey"
|
|
||||||
:collapsed="siderCollapse"
|
|
||||||
:collapsed-width="themeStore.sider.collapsedWidth"
|
|
||||||
:collapsed-icon-size="22"
|
|
||||||
:options="naiveMenus"
|
|
||||||
:inverted="darkTheme"
|
|
||||||
:indent="18"
|
|
||||||
responsive
|
|
||||||
@update:value="handleClickMenu"
|
|
||||||
/>
|
|
||||||
</SimpleScrollbar>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
:deep(.n-menu--horizontal) {
|
|
||||||
--n-item-height: v-bind(headerHeight) !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -3,31 +3,29 @@ import { computed } from 'vue';
|
|||||||
import { createReusableTemplate } from '@vueuse/core';
|
import { createReusableTemplate } from '@vueuse/core';
|
||||||
import { SimpleScrollbar } from '@sa/materials';
|
import { SimpleScrollbar } from '@sa/materials';
|
||||||
import { transformColorWithOpacity } from '@sa/color';
|
import { transformColorWithOpacity } from '@sa/color';
|
||||||
import { useAppStore } from '@/store/modules/app';
|
|
||||||
import { useRouteStore } from '@/store/modules/route';
|
|
||||||
import { useThemeStore } from '@/store/modules/theme';
|
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'FirstLevelMenu'
|
name: 'FirstLevelMenu'
|
||||||
});
|
});
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
menus: App.Global.Menu[];
|
||||||
activeMenuKey?: string;
|
activeMenuKey?: string;
|
||||||
inverted?: boolean;
|
inverted?: boolean;
|
||||||
|
siderCollapse?: boolean;
|
||||||
|
darkMode?: boolean;
|
||||||
|
themeColor: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
defineProps<Props>();
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
interface Emits {
|
interface Emits {
|
||||||
(e: 'select', menu: App.Global.Menu): boolean;
|
(e: 'select', menu: App.Global.Menu): boolean;
|
||||||
|
(e: 'toggleSiderCollapse'): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const emit = defineEmits<Emits>();
|
const emit = defineEmits<Emits>();
|
||||||
|
|
||||||
const appStore = useAppStore();
|
|
||||||
const themeStore = useThemeStore();
|
|
||||||
const routeStore = useRouteStore();
|
|
||||||
|
|
||||||
interface MixMenuItemProps {
|
interface MixMenuItemProps {
|
||||||
/** Menu item label */
|
/** Menu item label */
|
||||||
label: App.Global.Menu['label'];
|
label: App.Global.Menu['label'];
|
||||||
@ -36,12 +34,12 @@ interface MixMenuItemProps {
|
|||||||
/** Active menu item */
|
/** Active menu item */
|
||||||
active: boolean;
|
active: boolean;
|
||||||
/** Mini size */
|
/** Mini size */
|
||||||
isMini: boolean;
|
isMini?: boolean;
|
||||||
}
|
}
|
||||||
const [DefineMixMenuItem, MixMenuItem] = createReusableTemplate<MixMenuItemProps>();
|
const [DefineMixMenuItem, MixMenuItem] = createReusableTemplate<MixMenuItemProps>();
|
||||||
|
|
||||||
const selectedBgColor = computed(() => {
|
const selectedBgColor = computed(() => {
|
||||||
const { darkMode, themeColor } = themeStore;
|
const { darkMode, themeColor } = props;
|
||||||
|
|
||||||
const light = transformColorWithOpacity(themeColor, 0.1, '#ffffff');
|
const light = transformColorWithOpacity(themeColor, 0.1, '#ffffff');
|
||||||
const dark = transformColorWithOpacity(themeColor, 0.3, '#000000');
|
const dark = transformColorWithOpacity(themeColor, 0.3, '#000000');
|
||||||
@ -52,6 +50,10 @@ const selectedBgColor = computed(() => {
|
|||||||
function handleClickMixMenu(menu: App.Global.Menu) {
|
function handleClickMixMenu(menu: App.Global.Menu) {
|
||||||
emit('select', menu);
|
emit('select', menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleSiderCollapse() {
|
||||||
|
emit('toggleSiderCollapse');
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -80,21 +82,21 @@ function handleClickMixMenu(menu: App.Global.Menu) {
|
|||||||
<slot></slot>
|
<slot></slot>
|
||||||
<SimpleScrollbar>
|
<SimpleScrollbar>
|
||||||
<MixMenuItem
|
<MixMenuItem
|
||||||
v-for="menu in routeStore.menus"
|
v-for="menu in menus"
|
||||||
:key="menu.key"
|
:key="menu.key"
|
||||||
:label="menu.label"
|
:label="menu.label"
|
||||||
:icon="menu.icon"
|
:icon="menu.icon"
|
||||||
:active="menu.key === activeMenuKey"
|
:active="menu.key === activeMenuKey"
|
||||||
:is-mini="appStore.siderCollapse"
|
:is-mini="siderCollapse"
|
||||||
@click="handleClickMixMenu(menu)"
|
@click="handleClickMixMenu(menu)"
|
||||||
/>
|
/>
|
||||||
</SimpleScrollbar>
|
</SimpleScrollbar>
|
||||||
<MenuToggler
|
<MenuToggler
|
||||||
arrow-icon
|
arrow-icon
|
||||||
:collapsed="appStore.siderCollapse"
|
:collapsed="siderCollapse"
|
||||||
:z-index="99"
|
:z-index="99"
|
||||||
:class="{ 'text-white:88 !hover:text-white': inverted }"
|
:class="{ 'text-white:88 !hover:text-white': inverted }"
|
||||||
@click="appStore.toggleSiderCollapse"
|
@click="toggleSiderCollapse"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
@ -1,28 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { useRouterPush } from '@/hooks/common/router';
|
|
||||||
import { useMixMenuContext } from '../../context';
|
|
||||||
import FirstLevelMenu from './first-level-menu.vue';
|
|
||||||
|
|
||||||
defineOptions({
|
|
||||||
name: 'HorizontalMixMenu'
|
|
||||||
});
|
|
||||||
|
|
||||||
const { activeFirstLevelMenuKey, setActiveFirstLevelMenuKey } = useMixMenuContext();
|
|
||||||
const { routerPushByKey } = useRouterPush();
|
|
||||||
|
|
||||||
function handleSelectMixMenu(menu: App.Global.Menu) {
|
|
||||||
setActiveFirstLevelMenuKey(menu.key);
|
|
||||||
|
|
||||||
if (!menu.children?.length) {
|
|
||||||
routerPushByKey(menu.routeKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<FirstLevelMenu :active-menu-key="activeFirstLevelMenuKey" @select="handleSelectMixMenu">
|
|
||||||
<slot></slot>
|
|
||||||
</FirstLevelMenu>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
33
src/layouts/modules/global-menu/index.vue
Normal file
33
src/layouts/modules/global-menu/index.vue
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import type { Component } from 'vue';
|
||||||
|
import { useThemeStore } from '@/store/modules/theme';
|
||||||
|
import VerticalMenu from './modules/vertical-menu.vue';
|
||||||
|
import VerticalMixMenu from './modules/vertical-mix-menu.vue';
|
||||||
|
import HorizontalMenu from './modules/horizontal-menu.vue';
|
||||||
|
import HorizontalMixMenu from './modules/horizontal-mix-menu.vue';
|
||||||
|
import ReversedHorizontalMixMenu from './modules/reversed-horizontal-mix-menu.vue';
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'GlobalMenu'
|
||||||
|
});
|
||||||
|
|
||||||
|
const themeStore = useThemeStore();
|
||||||
|
|
||||||
|
const activeMenu = computed(() => {
|
||||||
|
const menuMap: Record<UnionKey.ThemeLayoutMode, Component> = {
|
||||||
|
vertical: VerticalMenu,
|
||||||
|
'vertical-mix': VerticalMixMenu,
|
||||||
|
horizontal: HorizontalMenu,
|
||||||
|
'horizontal-mix': themeStore.layout.reverseHorizontalMix ? ReversedHorizontalMixMenu : HorizontalMixMenu
|
||||||
|
};
|
||||||
|
|
||||||
|
return menuMap[themeStore.layout.mode];
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<component :is="activeMenu" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
39
src/layouts/modules/global-menu/modules/horizontal-menu.vue
Normal file
39
src/layouts/modules/global-menu/modules/horizontal-menu.vue
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import { GLOBAL_HEADER_MENU_ID } from '@/constants/app';
|
||||||
|
import { useRouteStore } from '@/store/modules/route';
|
||||||
|
import { useRouterPush } from '@/hooks/common/router';
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'HorizontalMenu'
|
||||||
|
});
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const routeStore = useRouteStore();
|
||||||
|
const { routerPushByKeyWithMetaQuery } = useRouterPush();
|
||||||
|
|
||||||
|
const selectedKey = computed(() => {
|
||||||
|
const { hideInMenu, activeMenu } = route.meta;
|
||||||
|
const name = route.name as string;
|
||||||
|
|
||||||
|
const routeName = (hideInMenu ? activeMenu : name) || name;
|
||||||
|
|
||||||
|
return routeName;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Teleport :to="`#${GLOBAL_HEADER_MENU_ID}`">
|
||||||
|
<NMenu
|
||||||
|
mode="horizontal"
|
||||||
|
:value="selectedKey"
|
||||||
|
:options="routeStore.menus"
|
||||||
|
:indent="18"
|
||||||
|
responsive
|
||||||
|
@update:value="routerPushByKeyWithMetaQuery"
|
||||||
|
/>
|
||||||
|
</Teleport>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
@ -0,0 +1,68 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import { GLOBAL_HEADER_MENU_ID, GLOBAL_SIDER_MENU_ID } from '@/constants/app';
|
||||||
|
import { useAppStore } from '@/store/modules/app';
|
||||||
|
import { useThemeStore } from '@/store/modules/theme';
|
||||||
|
import { useRouterPush } from '@/hooks/common/router';
|
||||||
|
import FirstLevelMenu from '../components/first-level-menu.vue';
|
||||||
|
import { useMixMenuContext } from '../../../context';
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'HorizontalMixMenu'
|
||||||
|
});
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const appStore = useAppStore();
|
||||||
|
const themeStore = useThemeStore();
|
||||||
|
const { allMenus, childLevelMenus, activeFirstLevelMenuKey, setActiveFirstLevelMenuKey } = useMixMenuContext();
|
||||||
|
const { routerPushByKeyWithMetaQuery } = useRouterPush();
|
||||||
|
|
||||||
|
const inverted = computed(() => !themeStore.darkMode && themeStore.sider.inverted);
|
||||||
|
|
||||||
|
const selectedKey = computed(() => {
|
||||||
|
const { hideInMenu, activeMenu } = route.meta;
|
||||||
|
const name = route.name as string;
|
||||||
|
|
||||||
|
const routeName = (hideInMenu ? activeMenu : name) || name;
|
||||||
|
|
||||||
|
return routeName;
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleSelectMixMenu(menu: App.Global.Menu) {
|
||||||
|
setActiveFirstLevelMenuKey(menu.key);
|
||||||
|
|
||||||
|
if (!menu.children?.length) {
|
||||||
|
routerPushByKeyWithMetaQuery(menu.routeKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Teleport :to="`#${GLOBAL_HEADER_MENU_ID}`">
|
||||||
|
<NMenu
|
||||||
|
mode="horizontal"
|
||||||
|
:value="selectedKey"
|
||||||
|
:options="childLevelMenus"
|
||||||
|
:indent="18"
|
||||||
|
responsive
|
||||||
|
@update:value="routerPushByKeyWithMetaQuery"
|
||||||
|
/>
|
||||||
|
</Teleport>
|
||||||
|
<Teleport :to="`#${GLOBAL_SIDER_MENU_ID}`">
|
||||||
|
<FirstLevelMenu
|
||||||
|
:menus="allMenus"
|
||||||
|
:active-menu-key="activeFirstLevelMenuKey"
|
||||||
|
:inverted="inverted"
|
||||||
|
:sider-collapse="appStore.siderCollapse"
|
||||||
|
:dark-mode="themeStore.darkMode"
|
||||||
|
:theme-color="themeStore.themeColor"
|
||||||
|
@select="handleSelectMixMenu"
|
||||||
|
@toggle-sider-collapse="appStore.toggleSiderCollapse"
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
</FirstLevelMenu>
|
||||||
|
</Teleport>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
@ -0,0 +1,97 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref, watch } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import type { RouteKey } from '@elegant-router/types';
|
||||||
|
import { SimpleScrollbar } from '@sa/materials';
|
||||||
|
import { GLOBAL_HEADER_MENU_ID, GLOBAL_SIDER_MENU_ID } from '@/constants/app';
|
||||||
|
import { useAppStore } from '@/store/modules/app';
|
||||||
|
import { useThemeStore } from '@/store/modules/theme';
|
||||||
|
import { useRouteStore } from '@/store/modules/route';
|
||||||
|
import { useRouterPush } from '@/hooks/common/router';
|
||||||
|
import { useMixMenuContext } from '../../../context';
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'ReversedHorizontalMixMenu'
|
||||||
|
});
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const appStore = useAppStore();
|
||||||
|
const themeStore = useThemeStore();
|
||||||
|
const routeStore = useRouteStore();
|
||||||
|
const {
|
||||||
|
firstLevelMenus,
|
||||||
|
childLevelMenus,
|
||||||
|
activeFirstLevelMenuKey,
|
||||||
|
setActiveFirstLevelMenuKey,
|
||||||
|
isActiveFirstLevelMenuHasChildren
|
||||||
|
} = useMixMenuContext();
|
||||||
|
const { routerPushByKeyWithMetaQuery } = useRouterPush();
|
||||||
|
|
||||||
|
const inverted = computed(() => !themeStore.darkMode && themeStore.sider.inverted);
|
||||||
|
|
||||||
|
const selectedKey = computed(() => {
|
||||||
|
const { hideInMenu, activeMenu } = route.meta;
|
||||||
|
const name = route.name as string;
|
||||||
|
|
||||||
|
const routeName = (hideInMenu ? activeMenu : name) || name;
|
||||||
|
|
||||||
|
return routeName;
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleSelectMixMenu(key: RouteKey) {
|
||||||
|
setActiveFirstLevelMenuKey(key);
|
||||||
|
|
||||||
|
if (!isActiveFirstLevelMenuHasChildren.value) {
|
||||||
|
routerPushByKeyWithMetaQuery(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const expandedKeys = ref<string[]>([]);
|
||||||
|
|
||||||
|
function updateExpandedKeys() {
|
||||||
|
if (appStore.siderCollapse || !selectedKey.value) {
|
||||||
|
expandedKeys.value = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
expandedKeys.value = routeStore.getSelectedMenuKeyPath(selectedKey.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => route.name,
|
||||||
|
() => {
|
||||||
|
updateExpandedKeys();
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Teleport :to="`#${GLOBAL_HEADER_MENU_ID}`">
|
||||||
|
<NMenu
|
||||||
|
mode="horizontal"
|
||||||
|
:value="activeFirstLevelMenuKey"
|
||||||
|
:options="firstLevelMenus"
|
||||||
|
:indent="18"
|
||||||
|
responsive
|
||||||
|
@update:value="handleSelectMixMenu"
|
||||||
|
/>
|
||||||
|
</Teleport>
|
||||||
|
<Teleport :to="`#${GLOBAL_SIDER_MENU_ID}`">
|
||||||
|
<SimpleScrollbar>
|
||||||
|
<NMenu
|
||||||
|
v-model:expanded-keys="expandedKeys"
|
||||||
|
mode="vertical"
|
||||||
|
:value="selectedKey"
|
||||||
|
:collapsed="appStore.siderCollapse"
|
||||||
|
:collapsed-width="themeStore.sider.collapsedWidth"
|
||||||
|
:collapsed-icon-size="22"
|
||||||
|
:options="childLevelMenus"
|
||||||
|
:inverted="inverted"
|
||||||
|
:indent="18"
|
||||||
|
@update:value="routerPushByKeyWithMetaQuery"
|
||||||
|
/>
|
||||||
|
</SimpleScrollbar>
|
||||||
|
</Teleport>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
70
src/layouts/modules/global-menu/modules/vertical-menu.vue
Normal file
70
src/layouts/modules/global-menu/modules/vertical-menu.vue
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref, watch } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import { SimpleScrollbar } from '@sa/materials';
|
||||||
|
import { useAppStore } from '@/store/modules/app';
|
||||||
|
import { useThemeStore } from '@/store/modules/theme';
|
||||||
|
import { useRouteStore } from '@/store/modules/route';
|
||||||
|
import { useRouterPush } from '@/hooks/common/router';
|
||||||
|
import { GLOBAL_SIDER_MENU_ID } from '@/constants/app';
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'VerticalMenu'
|
||||||
|
});
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const appStore = useAppStore();
|
||||||
|
const themeStore = useThemeStore();
|
||||||
|
const routeStore = useRouteStore();
|
||||||
|
const { routerPushByKeyWithMetaQuery } = useRouterPush();
|
||||||
|
|
||||||
|
const inverted = computed(() => !themeStore.darkMode && themeStore.sider.inverted);
|
||||||
|
|
||||||
|
const selectedKey = computed(() => {
|
||||||
|
const { hideInMenu, activeMenu } = route.meta;
|
||||||
|
const name = route.name as string;
|
||||||
|
|
||||||
|
const routeName = (hideInMenu ? activeMenu : name) || name;
|
||||||
|
|
||||||
|
return routeName;
|
||||||
|
});
|
||||||
|
|
||||||
|
const expandedKeys = ref<string[]>([]);
|
||||||
|
|
||||||
|
function updateExpandedKeys() {
|
||||||
|
if (appStore.siderCollapse || !selectedKey.value) {
|
||||||
|
expandedKeys.value = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
expandedKeys.value = routeStore.getSelectedMenuKeyPath(selectedKey.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => route.name,
|
||||||
|
() => {
|
||||||
|
updateExpandedKeys();
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Teleport :to="`#${GLOBAL_SIDER_MENU_ID}`">
|
||||||
|
<SimpleScrollbar>
|
||||||
|
<NMenu
|
||||||
|
v-model:expanded-keys="expandedKeys"
|
||||||
|
mode="vertical"
|
||||||
|
:value="selectedKey"
|
||||||
|
:collapsed="appStore.siderCollapse"
|
||||||
|
:collapsed-width="themeStore.sider.collapsedWidth"
|
||||||
|
:collapsed-icon-size="22"
|
||||||
|
:options="routeStore.menus"
|
||||||
|
:inverted="inverted"
|
||||||
|
:indent="18"
|
||||||
|
@update:value="routerPushByKeyWithMetaQuery"
|
||||||
|
/>
|
||||||
|
</SimpleScrollbar>
|
||||||
|
</Teleport>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
135
src/layouts/modules/global-menu/modules/vertical-mix-menu.vue
Normal file
135
src/layouts/modules/global-menu/modules/vertical-mix-menu.vue
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref, watch } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import { SimpleScrollbar } from '@sa/materials';
|
||||||
|
import { useBoolean } from '@sa/hooks';
|
||||||
|
import { useAppStore } from '@/store/modules/app';
|
||||||
|
import { useThemeStore } from '@/store/modules/theme';
|
||||||
|
import { useRouteStore } from '@/store/modules/route';
|
||||||
|
import { useRouterPush } from '@/hooks/common/router';
|
||||||
|
import { $t } from '@/locales';
|
||||||
|
import { GLOBAL_SIDER_MENU_ID } from '@/constants/app';
|
||||||
|
import { useMixMenuContext } from '../../../context';
|
||||||
|
import FirstLevelMenu from '../components/first-level-menu.vue';
|
||||||
|
import GlobalLogo from '../../global-logo/index.vue';
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'VerticalMenuMix'
|
||||||
|
});
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const appStore = useAppStore();
|
||||||
|
const themeStore = useThemeStore();
|
||||||
|
const routeStore = useRouteStore();
|
||||||
|
const { routerPushByKeyWithMetaQuery } = useRouterPush();
|
||||||
|
const { bool: drawerVisible, setBool: setDrawerVisible } = useBoolean();
|
||||||
|
const {
|
||||||
|
allMenus,
|
||||||
|
childLevelMenus,
|
||||||
|
activeFirstLevelMenuKey,
|
||||||
|
setActiveFirstLevelMenuKey,
|
||||||
|
getActiveFirstLevelMenuKey
|
||||||
|
//
|
||||||
|
} = useMixMenuContext();
|
||||||
|
|
||||||
|
const inverted = computed(() => !themeStore.darkMode && themeStore.sider.inverted);
|
||||||
|
|
||||||
|
const hasChildMenus = computed(() => childLevelMenus.value.length > 0);
|
||||||
|
|
||||||
|
const showDrawer = computed(() => hasChildMenus.value && (drawerVisible.value || appStore.mixSiderFixed));
|
||||||
|
|
||||||
|
function handleSelectMixMenu(menu: App.Global.Menu) {
|
||||||
|
setActiveFirstLevelMenuKey(menu.key);
|
||||||
|
|
||||||
|
if (menu.children?.length) {
|
||||||
|
setDrawerVisible(true);
|
||||||
|
} else {
|
||||||
|
routerPushByKeyWithMetaQuery(menu.routeKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleResetActiveMenu() {
|
||||||
|
getActiveFirstLevelMenuKey();
|
||||||
|
setDrawerVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedKey = computed(() => {
|
||||||
|
const { hideInMenu, activeMenu } = route.meta;
|
||||||
|
const name = route.name as string;
|
||||||
|
|
||||||
|
const routeName = (hideInMenu ? activeMenu : name) || name;
|
||||||
|
|
||||||
|
return routeName;
|
||||||
|
});
|
||||||
|
|
||||||
|
const expandedKeys = ref<string[]>([]);
|
||||||
|
|
||||||
|
function updateExpandedKeys() {
|
||||||
|
if (appStore.siderCollapse || !selectedKey.value) {
|
||||||
|
expandedKeys.value = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
expandedKeys.value = routeStore.getSelectedMenuKeyPath(selectedKey.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => route.name,
|
||||||
|
() => {
|
||||||
|
updateExpandedKeys();
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Teleport :to="`#${GLOBAL_SIDER_MENU_ID}`">
|
||||||
|
<div class="h-full flex" @mouseleave="handleResetActiveMenu">
|
||||||
|
<FirstLevelMenu
|
||||||
|
:menus="allMenus"
|
||||||
|
:active-menu-key="activeFirstLevelMenuKey"
|
||||||
|
:inverted="inverted"
|
||||||
|
:sider-collapse="appStore.siderCollapse"
|
||||||
|
:dark-mode="themeStore.darkMode"
|
||||||
|
:theme-color="themeStore.themeColor"
|
||||||
|
@select="handleSelectMixMenu"
|
||||||
|
@toggle-sider-collapse="appStore.toggleSiderCollapse"
|
||||||
|
>
|
||||||
|
<GlobalLogo :show-title="false" :style="{ height: themeStore.header.height + 'px' }" />
|
||||||
|
</FirstLevelMenu>
|
||||||
|
<div
|
||||||
|
class="relative h-full transition-width-300"
|
||||||
|
:style="{ width: appStore.mixSiderFixed && hasChildMenus ? themeStore.sider.mixChildMenuWidth + 'px' : '0px' }"
|
||||||
|
>
|
||||||
|
<DarkModeContainer
|
||||||
|
class="absolute-lt h-full flex-col-stretch nowrap-hidden shadow-sm transition-all-300"
|
||||||
|
:inverted="inverted"
|
||||||
|
:style="{ width: showDrawer ? themeStore.sider.mixChildMenuWidth + 'px' : '0px' }"
|
||||||
|
>
|
||||||
|
<header class="flex-y-center justify-between px-12px" :style="{ height: themeStore.header.height + 'px' }">
|
||||||
|
<h2 class="text-16px text-primary font-bold">{{ $t('system.title') }}</h2>
|
||||||
|
<PinToggler
|
||||||
|
:pin="appStore.mixSiderFixed"
|
||||||
|
:class="{ 'text-white:88 !hover:text-white': inverted }"
|
||||||
|
@click="appStore.toggleMixSiderFixed"
|
||||||
|
/>
|
||||||
|
</header>
|
||||||
|
<SimpleScrollbar>
|
||||||
|
<NMenu
|
||||||
|
v-model:expanded-keys="expandedKeys"
|
||||||
|
mode="vertical"
|
||||||
|
:options="childLevelMenus"
|
||||||
|
:collapsed="appStore.siderCollapse"
|
||||||
|
:collapsed-width="themeStore.sider.collapsedWidth"
|
||||||
|
:collapsed-icon-size="22"
|
||||||
|
:inverted="inverted"
|
||||||
|
:indent="18"
|
||||||
|
@update:value="routerPushByKeyWithMetaQuery"
|
||||||
|
/>
|
||||||
|
</SimpleScrollbar>
|
||||||
|
</DarkModeContainer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Teleport>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
@ -1,72 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { computed } from 'vue';
|
|
||||||
import { useBoolean } from '@sa/hooks';
|
|
||||||
import { useAppStore } from '@/store/modules/app';
|
|
||||||
import { useThemeStore } from '@/store/modules/theme';
|
|
||||||
import { useRouterPush } from '@/hooks/common/router';
|
|
||||||
import { $t } from '@/locales';
|
|
||||||
import { useMixMenuContext } from '../../context';
|
|
||||||
import FirstLevelMenu from './first-level-menu.vue';
|
|
||||||
import BaseMenu from './base-menu.vue';
|
|
||||||
|
|
||||||
defineOptions({
|
|
||||||
name: 'VerticalMixMenu'
|
|
||||||
});
|
|
||||||
|
|
||||||
const appStore = useAppStore();
|
|
||||||
const themeStore = useThemeStore();
|
|
||||||
const { routerPushByKey } = useRouterPush();
|
|
||||||
const { bool: drawerVisible, setBool: setDrawerVisible } = useBoolean();
|
|
||||||
const { menus, activeFirstLevelMenuKey, setActiveFirstLevelMenuKey, getActiveFirstLevelMenuKey } = useMixMenuContext();
|
|
||||||
|
|
||||||
const siderInverted = computed(() => !themeStore.darkMode && themeStore.sider.inverted);
|
|
||||||
|
|
||||||
const hasMenus = computed(() => menus.value.length > 0);
|
|
||||||
|
|
||||||
const showDrawer = computed(() => hasMenus.value && (drawerVisible.value || appStore.mixSiderFixed));
|
|
||||||
|
|
||||||
function handleSelectMixMenu(menu: App.Global.Menu) {
|
|
||||||
setActiveFirstLevelMenuKey(menu.key);
|
|
||||||
|
|
||||||
if (menu.children?.length) {
|
|
||||||
setDrawerVisible(true);
|
|
||||||
} else {
|
|
||||||
routerPushByKey(menu.routeKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleResetActiveMenu() {
|
|
||||||
getActiveFirstLevelMenuKey();
|
|
||||||
setDrawerVisible(false);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="h-full flex" @mouseleave="handleResetActiveMenu">
|
|
||||||
<FirstLevelMenu :active-menu-key="activeFirstLevelMenuKey" :inverted="siderInverted" @select="handleSelectMixMenu">
|
|
||||||
<slot></slot>
|
|
||||||
</FirstLevelMenu>
|
|
||||||
<div
|
|
||||||
class="relative h-full transition-width-300"
|
|
||||||
:style="{ width: appStore.mixSiderFixed && hasMenus ? themeStore.sider.mixChildMenuWidth + 'px' : '0px' }"
|
|
||||||
>
|
|
||||||
<DarkModeContainer
|
|
||||||
class="absolute-lt h-full flex-col-stretch nowrap-hidden shadow-sm transition-all-300"
|
|
||||||
:inverted="siderInverted"
|
|
||||||
:style="{ width: showDrawer ? themeStore.sider.mixChildMenuWidth + 'px' : '0px' }"
|
|
||||||
>
|
|
||||||
<header class="flex-y-center justify-between px-12px" :style="{ height: themeStore.header.height + 'px' }">
|
|
||||||
<h2 class="text-16px text-primary font-bold">{{ $t('system.title') }}</h2>
|
|
||||||
<PinToggler
|
|
||||||
:pin="appStore.mixSiderFixed"
|
|
||||||
:class="{ 'text-white:88 !hover:text-white': siderInverted }"
|
|
||||||
@click="appStore.toggleMixSiderFixed"
|
|
||||||
/>
|
|
||||||
</header>
|
|
||||||
<BaseMenu :dark-theme="siderInverted" :menus="menus" />
|
|
||||||
</DarkModeContainer>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
@ -2,11 +2,8 @@
|
|||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useAppStore } from '@/store/modules/app';
|
import { useAppStore } from '@/store/modules/app';
|
||||||
import { useThemeStore } from '@/store/modules/theme';
|
import { useThemeStore } from '@/store/modules/theme';
|
||||||
import { useRouteStore } from '@/store/modules/route';
|
import { GLOBAL_SIDER_MENU_ID } from '@/constants/app';
|
||||||
import GlobalLogo from '../global-logo/index.vue';
|
import GlobalLogo from '../global-logo/index.vue';
|
||||||
import VerticalMenu from '../global-menu/base-menu.vue';
|
|
||||||
import VerticalMixMenu from '../global-menu/vertical-mix-menu.vue';
|
|
||||||
import HorizontalMixMenu from '../global-menu/horizontal-mix-menu.vue';
|
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'GlobalSider'
|
name: 'GlobalSider'
|
||||||
@ -14,12 +11,12 @@ defineOptions({
|
|||||||
|
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const themeStore = useThemeStore();
|
const themeStore = useThemeStore();
|
||||||
const routeStore = useRouteStore();
|
|
||||||
|
|
||||||
const isVerticalMix = computed(() => themeStore.layout.mode === 'vertical-mix');
|
const isVerticalMix = computed(() => themeStore.layout.mode === 'vertical-mix');
|
||||||
const isHorizontalMix = computed(() => themeStore.layout.mode === 'horizontal-mix');
|
const isHorizontalMix = computed(() => themeStore.layout.mode === 'horizontal-mix');
|
||||||
const darkMenu = computed(() => !themeStore.darkMode && !isHorizontalMix.value && themeStore.sider.inverted);
|
const darkMenu = computed(() => !themeStore.darkMode && !isHorizontalMix.value && themeStore.sider.inverted);
|
||||||
const showLogo = computed(() => !isVerticalMix.value && !isHorizontalMix.value);
|
const showLogo = computed(() => !isVerticalMix.value && !isHorizontalMix.value);
|
||||||
|
const menuWrapperClass = computed(() => (showLogo.value ? 'flex-1-hidden' : 'h-full'));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -29,11 +26,7 @@ const showLogo = computed(() => !isVerticalMix.value && !isHorizontalMix.value);
|
|||||||
:show-title="!appStore.siderCollapse"
|
:show-title="!appStore.siderCollapse"
|
||||||
:style="{ height: themeStore.header.height + 'px' }"
|
:style="{ height: themeStore.header.height + 'px' }"
|
||||||
/>
|
/>
|
||||||
<VerticalMixMenu v-if="isVerticalMix">
|
<div :id="GLOBAL_SIDER_MENU_ID" :class="menuWrapperClass"></div>
|
||||||
<GlobalLogo :show-title="false" :style="{ height: themeStore.header.height + 'px' }" />
|
|
||||||
</VerticalMixMenu>
|
|
||||||
<HorizontalMixMenu v-else-if="isHorizontalMix" />
|
|
||||||
<VerticalMenu v-else :dark-theme="darkMenu" :menus="routeStore.menus" />
|
|
||||||
</DarkModeContainer>
|
</DarkModeContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ defineProps<Props>();
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-full flex-y-center justify-between">
|
<div class="w-full flex-y-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<span class="pr-8px text-base_text">{{ label }}</span>
|
<span class="pr-8px text-base-text">{{ label }}</span>
|
||||||
<slot name="suffix"></slot>
|
<slot name="suffix"></slot>
|
||||||
</div>
|
</div>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
|
@ -3,6 +3,7 @@ import { useAppStore } from '@/store/modules/app';
|
|||||||
import { useThemeStore } from '@/store/modules/theme';
|
import { useThemeStore } from '@/store/modules/theme';
|
||||||
import { $t } from '@/locales';
|
import { $t } from '@/locales';
|
||||||
import LayoutModeCard from '../components/layout-mode-card.vue';
|
import LayoutModeCard from '../components/layout-mode-card.vue';
|
||||||
|
import SettingItem from '../components/setting-item.vue';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'LayoutMode'
|
name: 'LayoutMode'
|
||||||
@ -10,6 +11,10 @@ defineOptions({
|
|||||||
|
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const themeStore = useThemeStore();
|
const themeStore = useThemeStore();
|
||||||
|
|
||||||
|
function handleReverseHorizontalMixChange(value: boolean) {
|
||||||
|
themeStore.setLayoutReverseHorizontalMix(value);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -44,6 +49,13 @@ const themeStore = useThemeStore();
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</LayoutModeCard>
|
</LayoutModeCard>
|
||||||
|
<SettingItem
|
||||||
|
v-if="themeStore.layout.mode === 'horizontal-mix'"
|
||||||
|
:label="$t('theme.layoutMode.reverseHorizontalMix')"
|
||||||
|
class="mt-16px"
|
||||||
|
>
|
||||||
|
<NSwitch :value="themeStore.layout.reverseHorizontalMix" @update:value="handleReverseHorizontalMixChange" />
|
||||||
|
</SettingItem>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@ -23,6 +23,7 @@ const local: App.I18n.Schema = {
|
|||||||
deleteSuccess: 'Delete Success',
|
deleteSuccess: 'Delete Success',
|
||||||
confirmDelete: 'Are you sure you want to delete?',
|
confirmDelete: 'Are you sure you want to delete?',
|
||||||
edit: 'Edit',
|
edit: 'Edit',
|
||||||
|
warning: 'Warning',
|
||||||
error: 'Error',
|
error: 'Error',
|
||||||
index: 'Index',
|
index: 'Index',
|
||||||
keywordSearch: 'Please enter keyword',
|
keywordSearch: 'Please enter keyword',
|
||||||
@ -69,7 +70,8 @@ const local: App.I18n.Schema = {
|
|||||||
vertical: 'Vertical Menu Mode',
|
vertical: 'Vertical Menu Mode',
|
||||||
horizontal: 'Horizontal Menu Mode',
|
horizontal: 'Horizontal Menu Mode',
|
||||||
'vertical-mix': 'Vertical Mix Menu Mode',
|
'vertical-mix': 'Vertical Mix Menu Mode',
|
||||||
'horizontal-mix': 'Horizontal Mix menu Mode'
|
'horizontal-mix': 'Horizontal Mix menu Mode',
|
||||||
|
reverseHorizontalMix: 'Reverse first level menus and child level menus position'
|
||||||
},
|
},
|
||||||
recommendColor: 'Apply Recommended Color Algorithm',
|
recommendColor: 'Apply Recommended Color Algorithm',
|
||||||
recommendColorDesc: 'The recommended color algorithm refers to',
|
recommendColorDesc: 'The recommended color algorithm refers to',
|
||||||
@ -260,6 +262,8 @@ const local: App.I18n.Schema = {
|
|||||||
devDep: 'Development Dependency'
|
devDep: 'Development Dependency'
|
||||||
},
|
},
|
||||||
home: {
|
home: {
|
||||||
|
branchDesc:
|
||||||
|
'For the convenience of everyone in developing and updating the merge, we have streamlined the code of the main branch, only retaining the homepage menu, and the rest of the content has been moved to the example branch for maintenance. The preview address displays the content of the example branch.',
|
||||||
greeting: 'Good morning, {userName}, today is another day full of vitality!',
|
greeting: 'Good morning, {userName}, today is another day full of vitality!',
|
||||||
weatherDesc: 'Today is cloudy to clear, 20℃ - 25℃!',
|
weatherDesc: 'Today is cloudy to clear, 20℃ - 25℃!',
|
||||||
projectCount: 'Project Count',
|
projectCount: 'Project Count',
|
||||||
|
@ -23,6 +23,7 @@ const local: App.I18n.Schema = {
|
|||||||
deleteSuccess: '删除成功',
|
deleteSuccess: '删除成功',
|
||||||
confirmDelete: '确认删除吗?',
|
confirmDelete: '确认删除吗?',
|
||||||
edit: '编辑',
|
edit: '编辑',
|
||||||
|
warning: '警告',
|
||||||
error: '错误',
|
error: '错误',
|
||||||
index: '序号',
|
index: '序号',
|
||||||
keywordSearch: '请输入关键词搜索',
|
keywordSearch: '请输入关键词搜索',
|
||||||
@ -69,7 +70,8 @@ const local: App.I18n.Schema = {
|
|||||||
vertical: '左侧菜单模式',
|
vertical: '左侧菜单模式',
|
||||||
'vertical-mix': '左侧菜单混合模式',
|
'vertical-mix': '左侧菜单混合模式',
|
||||||
horizontal: '顶部菜单模式',
|
horizontal: '顶部菜单模式',
|
||||||
'horizontal-mix': '顶部菜单混合模式'
|
'horizontal-mix': '顶部菜单混合模式',
|
||||||
|
reverseHorizontalMix: '一级菜单与子级菜单位置反转'
|
||||||
},
|
},
|
||||||
recommendColor: '应用推荐算法的颜色',
|
recommendColor: '应用推荐算法的颜色',
|
||||||
recommendColorDesc: '推荐颜色的算法参照',
|
recommendColorDesc: '推荐颜色的算法参照',
|
||||||
@ -260,6 +262,8 @@ const local: App.I18n.Schema = {
|
|||||||
devDep: '开发依赖'
|
devDep: '开发依赖'
|
||||||
},
|
},
|
||||||
home: {
|
home: {
|
||||||
|
branchDesc:
|
||||||
|
'为了方便大家开发和更新合并,我们对main分支的代码进行了精简,只保留了首页菜单,其余内容已移至example分支进行维护。预览地址显示的内容即为example分支的内容。',
|
||||||
greeting: '早安,{userName}, 今天又是充满活力的一天!',
|
greeting: '早安,{userName}, 今天又是充满活力的一天!',
|
||||||
weatherDesc: '今日多云转晴,20℃ - 25℃!',
|
weatherDesc: '今日多云转晴,20℃ - 25℃!',
|
||||||
projectCount: '项目数',
|
projectCount: '项目数',
|
||||||
|
@ -354,34 +354,6 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
|
|||||||
return getSelectedMenuKeyPathByKey(selectedKey, menus.value);
|
return getSelectedMenuKeyPathByKey(selectedKey, menus.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get route meta by key
|
|
||||||
*
|
|
||||||
* @param key Route key
|
|
||||||
*/
|
|
||||||
function getRouteMetaByKey(key: string) {
|
|
||||||
const allRoutes = router.getRoutes();
|
|
||||||
|
|
||||||
return allRoutes.find(route => route.name === key)?.meta || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get route query of meta by key
|
|
||||||
*
|
|
||||||
* @param key
|
|
||||||
*/
|
|
||||||
function getRouteQueryOfMetaByKey(key: string) {
|
|
||||||
const meta = getRouteMetaByKey(key);
|
|
||||||
|
|
||||||
const query: Record<string, string> = {};
|
|
||||||
|
|
||||||
meta?.query?.forEach(item => {
|
|
||||||
query[item.key] = item.value;
|
|
||||||
});
|
|
||||||
|
|
||||||
return query;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
resetStore,
|
resetStore,
|
||||||
routeHome,
|
routeHome,
|
||||||
@ -398,7 +370,6 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
|
|||||||
isInitAuthRoute,
|
isInitAuthRoute,
|
||||||
setIsInitAuthRoute,
|
setIsInitAuthRoute,
|
||||||
getIsAuthRouteExist,
|
getIsAuthRouteExist,
|
||||||
getSelectedMenuKeyPath,
|
getSelectedMenuKeyPath
|
||||||
getRouteQueryOfMetaByKey
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -6,7 +6,7 @@ import { getPaletteColorByNumber } from '@sa/color';
|
|||||||
import { SetupStoreId } from '@/enum';
|
import { SetupStoreId } from '@/enum';
|
||||||
import { localStg } from '@/utils/storage';
|
import { localStg } from '@/utils/storage';
|
||||||
import {
|
import {
|
||||||
addThemeVarsToHtml,
|
addThemeVarsToGlobal,
|
||||||
createThemeToken,
|
createThemeToken,
|
||||||
getNaiveTheme,
|
getNaiveTheme,
|
||||||
initThemeSettings,
|
initThemeSettings,
|
||||||
@ -123,10 +123,22 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
|
|||||||
settings.value.layout.mode = mode;
|
settings.value.layout.mode = mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Setup theme vars to html */
|
/** Setup theme vars to global */
|
||||||
function setupThemeVarsToHtml() {
|
function setupThemeVarsToGlobal() {
|
||||||
const { themeTokens, darkThemeTokens } = createThemeToken(themeColors.value, settings.value.recommendColor);
|
const { themeTokens, darkThemeTokens } = createThemeToken(
|
||||||
addThemeVarsToHtml(themeTokens, darkThemeTokens);
|
themeColors.value,
|
||||||
|
settings.value.tokens,
|
||||||
|
settings.value.recommendColor
|
||||||
|
);
|
||||||
|
addThemeVarsToGlobal(themeTokens, darkThemeTokens);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Set layout reverse horizontal mix
|
||||||
|
*
|
||||||
|
* @param reverse Reverse horizontal mix
|
||||||
|
*/
|
||||||
|
function setLayoutReverseHorizontalMix(reverse: boolean) {
|
||||||
|
settings.value.layout.reverseHorizontalMix = reverse;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Cache theme settings */
|
/** Cache theme settings */
|
||||||
@ -166,7 +178,7 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
|
|||||||
watch(
|
watch(
|
||||||
themeColors,
|
themeColors,
|
||||||
val => {
|
val => {
|
||||||
setupThemeVarsToHtml();
|
setupThemeVarsToGlobal();
|
||||||
localStg.set('themeColor', val.primary);
|
localStg.set('themeColor', val.primary);
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
@ -189,6 +201,7 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
|
|||||||
setThemeScheme,
|
setThemeScheme,
|
||||||
toggleThemeScheme,
|
toggleThemeScheme,
|
||||||
updateThemeColors,
|
updateThemeColors,
|
||||||
setThemeLayout
|
setThemeLayout,
|
||||||
|
setLayoutReverseHorizontalMix
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -30,39 +30,40 @@ export function initThemeSettings() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create theme token
|
* create theme token css vars value by theme settings
|
||||||
*
|
*
|
||||||
* @param colors Theme colors
|
* @param colors Theme colors
|
||||||
|
* @param tokens Theme setting tokens
|
||||||
* @param [recommended=false] Use recommended color. Default is `false`
|
* @param [recommended=false] Use recommended color. Default is `false`
|
||||||
*/
|
*/
|
||||||
export function createThemeToken(colors: App.Theme.ThemeColor, recommended = false) {
|
export function createThemeToken(
|
||||||
|
colors: App.Theme.ThemeColor,
|
||||||
|
tokens?: App.Theme.ThemeSetting['tokens'],
|
||||||
|
recommended = false
|
||||||
|
) {
|
||||||
const paletteColors = createThemePaletteColors(colors, recommended);
|
const paletteColors = createThemePaletteColors(colors, recommended);
|
||||||
|
|
||||||
const themeTokens: App.Theme.ThemeToken = {
|
const { light, dark } = tokens || themeSettings.tokens;
|
||||||
|
|
||||||
|
const themeTokens: App.Theme.ThemeTokenCSSVars = {
|
||||||
colors: {
|
colors: {
|
||||||
...paletteColors,
|
...paletteColors,
|
||||||
nprogress: paletteColors.primary,
|
nprogress: paletteColors.primary,
|
||||||
container: 'rgb(255, 255, 255)',
|
...light.colors
|
||||||
layout: 'rgb(247, 250, 252)',
|
|
||||||
inverted: 'rgb(0, 20, 40)',
|
|
||||||
base_text: 'rgb(31, 31, 31)'
|
|
||||||
},
|
},
|
||||||
boxShadow: {
|
boxShadow: {
|
||||||
header: '0 1px 2px rgb(0, 21, 41, 0.08)',
|
...light.boxShadow
|
||||||
sider: '2px 0 8px 0 rgb(29, 35, 41, 0.05)',
|
|
||||||
tab: '0 1px 2px rgb(0, 21, 41, 0.08)'
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const darkThemeTokens: App.Theme.ThemeToken = {
|
const darkThemeTokens: App.Theme.ThemeTokenCSSVars = {
|
||||||
colors: {
|
colors: {
|
||||||
...themeTokens.colors,
|
...themeTokens.colors,
|
||||||
container: 'rgb(28, 28, 28)',
|
...dark?.colors
|
||||||
layout: 'rgb(18, 18, 18)',
|
|
||||||
base_text: 'rgb(224, 224, 224)'
|
|
||||||
},
|
},
|
||||||
boxShadow: {
|
boxShadow: {
|
||||||
...themeTokens.boxShadow
|
...themeTokens.boxShadow,
|
||||||
|
...dark?.boxShadow
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -132,16 +133,16 @@ function getCssVarByTokens(tokens: App.Theme.BaseToken) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add theme vars to html
|
* Add theme vars to global
|
||||||
*
|
*
|
||||||
* @param tokens
|
* @param tokens
|
||||||
*/
|
*/
|
||||||
export function addThemeVarsToHtml(tokens: App.Theme.BaseToken, darkTokens: App.Theme.BaseToken) {
|
export function addThemeVarsToGlobal(tokens: App.Theme.BaseToken, darkTokens: App.Theme.BaseToken) {
|
||||||
const cssVarStr = getCssVarByTokens(tokens);
|
const cssVarStr = getCssVarByTokens(tokens);
|
||||||
const darkCssVarStr = getCssVarByTokens(darkTokens);
|
const darkCssVarStr = getCssVarByTokens(darkTokens);
|
||||||
|
|
||||||
const css = `
|
const css = `
|
||||||
html {
|
:root {
|
||||||
${cssVarStr}
|
${cssVarStr}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -13,7 +13,8 @@ export const themeSettings: App.Theme.ThemeSetting = {
|
|||||||
isInfoFollowPrimary: true,
|
isInfoFollowPrimary: true,
|
||||||
layout: {
|
layout: {
|
||||||
mode: 'vertical',
|
mode: 'vertical',
|
||||||
scrollMode: 'content'
|
scrollMode: 'content',
|
||||||
|
reverseHorizontalMix: false
|
||||||
},
|
},
|
||||||
page: {
|
page: {
|
||||||
animate: true,
|
animate: true,
|
||||||
@ -46,6 +47,28 @@ export const themeSettings: App.Theme.ThemeSetting = {
|
|||||||
fixed: false,
|
fixed: false,
|
||||||
height: 48,
|
height: 48,
|
||||||
right: true
|
right: true
|
||||||
|
},
|
||||||
|
tokens: {
|
||||||
|
light: {
|
||||||
|
colors: {
|
||||||
|
container: 'rgb(255, 255, 255)',
|
||||||
|
layout: 'rgb(247, 250, 252)',
|
||||||
|
inverted: 'rgb(0, 20, 40)',
|
||||||
|
'base-text': 'rgb(31, 31, 31)'
|
||||||
|
},
|
||||||
|
boxShadow: {
|
||||||
|
header: '0 1px 2px rgb(0, 21, 41, 0.08)',
|
||||||
|
sider: '2px 0 8px 0 rgb(29, 35, 41, 0.05)',
|
||||||
|
tab: '0 1px 2px rgb(0, 21, 41, 0.08)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dark: {
|
||||||
|
colors: {
|
||||||
|
container: 'rgb(28, 28, 28)',
|
||||||
|
layout: 'rgb(18, 18, 18)',
|
||||||
|
'base-text': 'rgb(224, 224, 224)'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -18,14 +18,14 @@ function createColorPaletteVars() {
|
|||||||
const colorPaletteVars = createColorPaletteVars();
|
const colorPaletteVars = createColorPaletteVars();
|
||||||
|
|
||||||
/** Theme vars */
|
/** Theme vars */
|
||||||
export const themeVars: App.Theme.ThemeToken = {
|
export const themeVars: App.Theme.ThemeTokenCSSVars = {
|
||||||
colors: {
|
colors: {
|
||||||
...colorPaletteVars,
|
...colorPaletteVars,
|
||||||
nprogress: 'rgb(var(--nprogress-color))',
|
nprogress: 'rgb(var(--nprogress-color))',
|
||||||
container: 'rgb(var(--container-bg-color))',
|
container: 'rgb(var(--container-bg-color))',
|
||||||
layout: 'rgb(var(--layout-bg-color))',
|
layout: 'rgb(var(--layout-bg-color))',
|
||||||
inverted: 'rgb(var(--inverted-bg-color))',
|
inverted: 'rgb(var(--inverted-bg-color))',
|
||||||
base_text: 'rgb(var(--base-text-color))'
|
'base-text': 'rgb(var(--base-text-color))'
|
||||||
},
|
},
|
||||||
boxShadow: {
|
boxShadow: {
|
||||||
header: 'var(--header-box-shadow)',
|
header: 'var(--header-box-shadow)',
|
||||||
|
58
src/typings/app.d.ts
vendored
58
src/typings/app.d.ts
vendored
@ -4,16 +4,6 @@ declare namespace App {
|
|||||||
namespace Theme {
|
namespace Theme {
|
||||||
type ColorPaletteNumber = import('@sa/color').ColorPaletteNumber;
|
type ColorPaletteNumber = import('@sa/color').ColorPaletteNumber;
|
||||||
|
|
||||||
/** Theme token */
|
|
||||||
type ThemeToken = {
|
|
||||||
colors: ThemeTokenColor;
|
|
||||||
boxShadow: {
|
|
||||||
header: string;
|
|
||||||
sider: string;
|
|
||||||
tab: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Theme setting */
|
/** Theme setting */
|
||||||
interface ThemeSetting {
|
interface ThemeSetting {
|
||||||
/** Theme scheme */
|
/** Theme scheme */
|
||||||
@ -34,6 +24,12 @@ declare namespace App {
|
|||||||
mode: UnionKey.ThemeLayoutMode;
|
mode: UnionKey.ThemeLayoutMode;
|
||||||
/** Scroll mode */
|
/** Scroll mode */
|
||||||
scrollMode: UnionKey.ThemeScrollMode;
|
scrollMode: UnionKey.ThemeScrollMode;
|
||||||
|
/**
|
||||||
|
* Whether to reverse the horizontal mix
|
||||||
|
*
|
||||||
|
* if true, the vertical child level menus in left and horizontal first level menus in top
|
||||||
|
*/
|
||||||
|
reverseHorizontalMix?: boolean;
|
||||||
};
|
};
|
||||||
/** Page */
|
/** Page */
|
||||||
page: {
|
page: {
|
||||||
@ -97,6 +93,13 @@ declare namespace App {
|
|||||||
/** Whether float the footer to the right when the layout is 'horizontal-mix' */
|
/** Whether float the footer to the right when the layout is 'horizontal-mix' */
|
||||||
right: boolean;
|
right: boolean;
|
||||||
};
|
};
|
||||||
|
/** define some theme settings tokens, will transform to css variables */
|
||||||
|
tokens: {
|
||||||
|
light: ThemeSettingToken;
|
||||||
|
dark?: {
|
||||||
|
[K in keyof ThemeSettingToken]?: Partial<ThemeSettingToken[K]>;
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OtherColor {
|
interface OtherColor {
|
||||||
@ -118,14 +121,33 @@ declare namespace App {
|
|||||||
|
|
||||||
type BaseToken = Record<string, Record<string, string>>;
|
type BaseToken = Record<string, Record<string, string>>;
|
||||||
|
|
||||||
interface ThemeTokenColor extends ThemePaletteColor {
|
interface ThemeSettingTokenColor {
|
||||||
nprogress: string;
|
/** the progress bar color, if not set, will use the primary color */
|
||||||
|
nprogress?: string;
|
||||||
container: string;
|
container: string;
|
||||||
layout: string;
|
layout: string;
|
||||||
inverted: string;
|
inverted: string;
|
||||||
base_text: string;
|
'base-text': string;
|
||||||
[key: string]: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ThemeSettingTokenBoxShadow {
|
||||||
|
header: string;
|
||||||
|
sider: string;
|
||||||
|
tab: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ThemeSettingToken {
|
||||||
|
colors: ThemeSettingTokenColor;
|
||||||
|
boxShadow: ThemeSettingTokenBoxShadow;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ThemeTokenColor = ThemePaletteColor & ThemeSettingTokenColor;
|
||||||
|
|
||||||
|
/** Theme token CSS variables */
|
||||||
|
type ThemeTokenCSSVars = {
|
||||||
|
colors: ThemeTokenColor & { [key: string]: string };
|
||||||
|
boxShadow: ThemeSettingTokenBoxShadow & { [key: string]: string };
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Global namespace */
|
/** Global namespace */
|
||||||
@ -148,7 +170,7 @@ declare namespace App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** The global menu */
|
/** The global menu */
|
||||||
interface Menu {
|
type Menu = {
|
||||||
/**
|
/**
|
||||||
* The menu key
|
* The menu key
|
||||||
*
|
*
|
||||||
@ -167,7 +189,7 @@ declare namespace App {
|
|||||||
icon?: () => VNode;
|
icon?: () => VNode;
|
||||||
/** The menu children */
|
/** The menu children */
|
||||||
children?: Menu[];
|
children?: Menu[];
|
||||||
}
|
};
|
||||||
|
|
||||||
type Breadcrumb = Omit<Menu, 'children'> & {
|
type Breadcrumb = Omit<Menu, 'children'> & {
|
||||||
options?: Breadcrumb[];
|
options?: Breadcrumb[];
|
||||||
@ -273,6 +295,7 @@ declare namespace App {
|
|||||||
deleteSuccess: string;
|
deleteSuccess: string;
|
||||||
confirmDelete: string;
|
confirmDelete: string;
|
||||||
edit: string;
|
edit: string;
|
||||||
|
warning: string;
|
||||||
error: string;
|
error: string;
|
||||||
index: string;
|
index: string;
|
||||||
keywordSearch: string;
|
keywordSearch: string;
|
||||||
@ -309,7 +332,7 @@ declare namespace App {
|
|||||||
theme: {
|
theme: {
|
||||||
themeSchema: { title: string } & Record<UnionKey.ThemeScheme, string>;
|
themeSchema: { title: string } & Record<UnionKey.ThemeScheme, string>;
|
||||||
grayscale: string;
|
grayscale: string;
|
||||||
layoutMode: { title: string } & Record<UnionKey.ThemeLayoutMode, string>;
|
layoutMode: { title: string; reverseHorizontalMix: string } & Record<UnionKey.ThemeLayoutMode, string>;
|
||||||
recommendColor: string;
|
recommendColor: string;
|
||||||
recommendColorDesc: string;
|
recommendColorDesc: string;
|
||||||
themeColor: {
|
themeColor: {
|
||||||
@ -420,6 +443,7 @@ declare namespace App {
|
|||||||
devDep: string;
|
devDep: string;
|
||||||
};
|
};
|
||||||
home: {
|
home: {
|
||||||
|
branchDesc: string;
|
||||||
greeting: string;
|
greeting: string;
|
||||||
weatherDesc: string;
|
weatherDesc: string;
|
||||||
projectCount: string;
|
projectCount: string;
|
||||||
|
1
src/typings/components.d.ts
vendored
1
src/typings/components.d.ts
vendored
@ -41,6 +41,7 @@ declare module 'vue' {
|
|||||||
LangSwitch: typeof import('./../components/common/lang-switch.vue')['default']
|
LangSwitch: typeof import('./../components/common/lang-switch.vue')['default']
|
||||||
LookForward: typeof import('./../components/custom/look-forward.vue')['default']
|
LookForward: typeof import('./../components/custom/look-forward.vue')['default']
|
||||||
MenuToggler: typeof import('./../components/common/menu-toggler.vue')['default']
|
MenuToggler: typeof import('./../components/common/menu-toggler.vue')['default']
|
||||||
|
NAlert: typeof import('naive-ui')['NAlert']
|
||||||
NBreadcrumb: typeof import('naive-ui')['NBreadcrumb']
|
NBreadcrumb: typeof import('naive-ui')['NBreadcrumb']
|
||||||
NBreadcrumbItem: typeof import('naive-ui')['NBreadcrumbItem']
|
NBreadcrumbItem: typeof import('naive-ui')['NBreadcrumbItem']
|
||||||
NButton: typeof import('naive-ui')['NButton']
|
NButton: typeof import('naive-ui')['NButton']
|
||||||
|
2
src/typings/union-key.d.ts
vendored
2
src/typings/union-key.d.ts
vendored
@ -20,7 +20,7 @@ declare namespace UnionKey {
|
|||||||
* - vertical: the vertical menu in left
|
* - vertical: the vertical menu in left
|
||||||
* - horizontal: the horizontal menu in top
|
* - horizontal: the horizontal menu in top
|
||||||
* - vertical-mix: two vertical mixed menus in left
|
* - vertical-mix: two vertical mixed menus in left
|
||||||
* - horizontal-mix: the vertical menu in left and horizontal menu in top
|
* - horizontal-mix: the vertical first level menus in left and horizontal child level menus in top
|
||||||
*/
|
*/
|
||||||
type ThemeLayoutMode = 'vertical' | 'horizontal' | 'vertical-mix' | 'horizontal-mix';
|
type ThemeLayoutMode = 'vertical' | 'horizontal' | 'vertical-mix' | 'horizontal-mix';
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user