Compare commits

...

65 Commits

Author SHA1 Message Date
vben
642d48870c chore: release 2.8.0 2021-11-03 00:28:43 +08:00
vben
3b63e41b68 chore: move to pnpm 2021-11-03 00:28:32 +08:00
madlight-du
d6cdff97b6 fix: direct import for antdv subcomponents supported(8b00112) (#1322)
* fix: direct import for antdv subcomponents supported

修复commit<8b00112>使用直接过滤部分antdv子组件的方式,导致build后刷新页面子组件样式会丢失的问题。

* Update styleImport.ts

* Update styleImport.ts
2021-11-01 08:00:01 +08:00
Jobin
97fe8e2058 feat(ApiCascader): add asynchronous cascader component (#1321) 2021-11-01 07:59:15 +08:00
zuihou
5c491a4258 fix(Menu): 左侧菜单混合模式 在动态更改路由时,左侧显示的菜单没有实时更新 2021-10-30 01:38:28 +08:00
zuihou
5225345496 fix: 全局日期格式化缺少秒(大多数场景日期格式都需要携带秒!) 2021-10-29 20:09:43 +08:00
vben
a248e20013 chore: fix type 2021-10-26 01:19:51 +08:00
vben
ed40b333f3 chore: Allow git message to be uppercase 2021-10-26 01:11:56 +08:00
vben
b7813b9ba8 perf: typo 2021-10-26 01:06:35 +08:00
vben
dbcc40f5ea chore: remove pretty 2021-10-26 01:00:49 +08:00
vben
acea184320 chore: format code 2021-10-25 23:49:03 +08:00
vben
0f44291c5c chore: update deps 2021-10-25 23:45:58 +08:00
Jungzl
83bce6c1d4 chore: support for pnpm (#1310) 2021-10-25 23:33:24 +08:00
scil
85a68d5fc3 fix: check if searchText is null (#1301) 2021-10-25 23:33:11 +08:00
pq
a0165d1eee perf(useRuleFormItem): more accurate return type (#1290) 2021-10-20 09:13:04 +08:00
Joyboo
8447331197 chore: table size放到settings (#1294) 2021-10-20 09:12:27 +08:00
vben
787bc462e7 chore: update deps 2021-10-20 01:11:29 +08:00
zuihou
89414f173e feat(Form): 新增 ApiRadioGroup 组件 2021-10-16 21:25:57 +08:00
tangyh
34781d42e8 feat(Table): 支持设置默认的排序值 2021-10-14 22:15:19 +08:00
无木
456a661488 fix(table): deleteTableDataRecord not work 2021-10-14 14:59:12 +08:00
miofly
5902886798 fix(type): 修复几个 ts 报错,和文件引用位置 (#1283)
* fix(type): 删除多余的类型定义

* fix(login): 删除登录时的 toRaw 包裹参数

* fix(type): 修复几个 ts 报错,和文件引用位置
2021-10-12 18:30:32 +08:00
miofly
7a1b6e74ab fix(type): typo (#1281)
* fix(type): 删除多余的类型定义

* fix(login): 删除登录时的 toRaw 包裹参数
2021-10-12 11:49:50 +08:00
zmtlwzy
b653412260 fixed: (update pageTitle when change local) (#1278)
If AppLocalePicker reload prop is false, Should be updated when changing the language
2021-10-12 11:49:05 +08:00
nsk
a530ec867b fix(Loading): add theme prop, The repair background prop does not tak… (#1271)
* fix(Loading): add theme prop, The repair background prop does not take effect

* fix(AppLogo): fix title line height
2021-10-12 09:12:10 +08:00
Lowell
d6f65d476e fix(Table): 解决设置了分页的情况下,调整表格分页条数后,如果翻页,分页条数会重置的问题。 (#1270) 2021-10-12 09:11:50 +08:00
Haceral
052eff91c4 perf: Improve the dynamic routing and automatically close the Tab function (#1264)
* 增加动态路由最大打开Tab数控制

* 增加动态路由打开数控制Router参数

* feat(Tab): 新增动态路由打开数限制Demo

* fix(multipleTab.ts): 将原来的打开数限制从固定的 5 修改为读取配置

Co-authored-by: Haceral <18274416193@163.com>
2021-10-12 09:11:25 +08:00
无木
e1cbe23e96 fix(api-tree-select): fetch not triggered when params changed
修复ApiTreeSelect的params变化未能触发重新请求API数据的问题
2021-10-11 07:48:41 +08:00
JinMao
f3f56288af fix: build error 2021-10-06 12:01:13 +08:00
Lowell
473e56e4e0 form的label属性实际上可以使用VNode (#1252) 2021-10-05 22:55:02 +08:00
Haceral
eac2fb4aaa feat: 动态路由 Tab打开数量控制,超出限制自动关闭起始Tab (#1256)
* 增加动态路由最大打开Tab数控制

* 增加动态路由打开数控制Router参数
2021-10-05 22:54:49 +08:00
钱忠旺
7d40773b5b style(buildConf.ts): 增加函数接口和修改默认的 (#1259)
增加CreateConfigParams接口,并修改createConfig参数默认值

Co-authored-by: QIANZW <1870271825@gqq.com>
2021-10-05 22:53:53 +08:00
erniu
935d4fc12d feat(Upload): 兼容ant-design-vue的upload属性 (#1247)
1. 兼容`name`属性,用于自定义发到后台的文件参数名; 2. 兼容`filename`属性

Co-authored-by: erniu <joe.cheng237@gmail.com>
2021-10-05 22:53:21 +08:00
vben
2849743a4d chore: update deps 2021-10-05 22:52:34 +08:00
vben
437211107f chore: update deps 2021-09-28 00:56:47 +08:00
1sm
2f2526c564 fix(modal): 取消全屏功能后关闭图标颜色异常 (#1240)
Co-authored-by: liushiman <smliu@gk-estor.com>
2021-09-28 00:22:23 +08:00
Joyboo
754d1986e9 fix: 修复ApiSelect属性被覆盖的问题 (#1226) 2021-09-28 00:22:04 +08:00
Leon Guan
034e39ef06 fix: 当前路由所在菜单路径是嵌套并且隐藏时无法正常高亮根菜单节点 (#1201)
修复当路由所在菜单路径是嵌套并且隐藏时无法正常高亮根菜单节点的问题。

Fixed #1080
2021-09-28 00:21:53 +08:00
CXM
bfdbccfece fix: Dev/fix modal event (#1241)
* fix(type): fix ant-design-vue  ->

* fix: fix editor BasicModal type event check error
2021-09-28 00:21:16 +08:00
vben
246c5f795b chore: update deps 2021-09-24 15:52:21 +08:00
zuihou
902d38dc2f Merge remote-tracking branch 'origin/main' 2021-09-23 12:32:26 +08:00
zuihou
1abf7fdf5f refactor(route): 动态路由 component 属性支持以 / 开头或者以 .vue 和 .tsx 结尾 2021-09-23 12:28:21 +08:00
无木
96ce18c073 revert(form): revert formItem style 2021-09-23 12:27:25 +08:00
无木
f8102446d0 fix(form): fixed formitem style
修复FormItem样式问题

fixed: #1231
2021-09-23 09:33:29 +08:00
zuihou
07b17b9798 perf: 优化代码 2021-09-23 00:10:03 +08:00
zuihou
452b2599f4 fix(route): 获取 LAYOUT 或 IFRAME 忽略大小写。 并且在通过component找不到页面时打印警告,并返回404页面 2021-09-23 00:05:58 +08:00
zuihou
7028e0031d fix(Breadcrumb): 隐藏面包屑应交给 hideBreadcrumb 参数,而非hideMenu 参数 2021-09-22 23:46:42 +08:00
无木
6e0c70f415 fix(import-excel): support datetime raw data import
importExcel支持导入原始日期时间数据

fixed: #1215
2021-09-21 23:23:32 +08:00
无木
01b667facf fix(form): fixed custom item style
修复自定义FormItem组件的内容宽度可能超出范围的问题

fixed: #1225
2021-09-20 20:48:50 +08:00
无木
a82a69d00d fix(dropdown): add missing function
添加Dropdown组件缺失的函数
2021-09-20 19:06:26 +08:00
无木
8b00112d5a fix: direct import for antdv subcomponents supported
修复直接import部分antdv子组件时会在build过程中报错的问题
2021-09-19 11:14:19 +08:00
无木
853bde9275 fix: fixed prop mode of CodeEditor 2021-09-18 20:18:41 +08:00
无木
5af452754b fix: hmr error fixed
修复部分组件可能会造成热更新异常的问题
2021-09-18 14:58:22 +08:00
无木
9c5f11a54b fix: remove warning in building action
移除build过程中的警告
2021-09-18 12:32:15 +08:00
无木
6c08dae921 fix: build error for CodeEditor 2021-09-18 12:29:54 +08:00
无木
02eabe1f9f chore: update deps 2021-09-18 12:08:18 +08:00
无木
2875a97b70 fix(table): clickToRowSelect support disabled checkbox
修复`clickToRowSelect`会无视行选择框disabled状态的问题
2021-09-17 15:44:39 +08:00
无木
067753d490 feat(env): VITE_PROXY support single quote
env文件中的VITE_PROXY配置支持单引号

close: #1204
2021-09-17 09:40:22 +08:00
无木
7b6d5e44a9 fix(table): reload in useTable not support await
修复useTable返回的reload方法不支持使用await等待加载完毕的问题
2021-09-16 16:29:26 +08:00
无木
e85649bde2 fix(tabs): close tab without navigation
修复多标签在某些情况下关闭页签不会跳转路由的问题

fixed: #1131
2021-09-16 00:09:18 +08:00
无木
cecdfbaf29 fix(tabs): dropdown items enabled unnecessary
修复标签头的右键菜单某些项目不恰当地处于可用状态

fixed: #1207
2021-09-15 23:15:01 +08:00
无木
84c7d516df fix(table): prop inset take no effect
修复BasicTabled的inset属性不起作用的问题

fixed: #1209
2021-09-15 23:04:36 +08:00
无木
15ea0d2f53 fix(tree): node slot take no effect
修复BasicTree节点插槽不起作用的问题
2021-09-15 19:50:26 +08:00
无木
ba2c1a3bf5 fix(table): editable cell can not submit
修复可编辑单元格某些情况下无法提交的问题
2021-09-15 12:51:21 +08:00
无木
2346a90c08 fix(modal): maskClosable and Esc take no effect
修复BasicModal按Esc和点击遮罩无法关闭的问题

fixed: #1203
2021-09-15 10:56:14 +08:00
无木
6544f84bc2 feat(demo): add token expired example
添加token超时例子
2021-09-14 22:11:21 +08:00
141 changed files with 13782 additions and 12972 deletions

View File

@@ -6,5 +6,3 @@
# Format and submit code according to lintstagedrc.js configuration
npm run lint:lint-staged
npm run lint:pretty

13
.vscode/settings.json vendored
View File

@@ -6,15 +6,8 @@
//===========================================
//============= Editor ======================
//===========================================
"explorer.openEditors.visible": 0,
"editor.tabSize": 2,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"diffEditor.ignoreTrimWhitespace": false,
//===========================================
//============= Other =======================
//===========================================
"breadcrumbs.enabled": true,
"open-in-browser.default": "chrome",
//===========================================
//============= files =======================
//===========================================
@@ -69,15 +62,9 @@
},
"stylelint.enable": true,
"stylelint.packageManager": "yarn",
"liveServer.settings.donotShowInfoMsg": true,
"telemetry.enableCrashReporter": false,
"workbench.settings.enableNaturalLanguageSearch": false,
"path-intellisense.mappings": {
"/@/": "${workspaceRoot}/src"
},
"prettier.requireConfig": true,
"typescript.updateImportsOnFileMove.enabled": "always",
"workbench.sideBar.location": "left",
"[javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},

View File

@@ -1,3 +1,37 @@
## 2.8.0(2021-11.03)
### Upgrade Instructions
- Package manager changed from `yarn` to `pnpm`
- Delete `node_modules` and `yarn.lock`, install `pnpm` globally
- Execute `pnpm install`
### ✨ Features
- **Others**
- The `VITE_PROXY` configuration in the `.env` file supports single quotes
- Remove warnings during build
### 🐛 Bug Fixes
- **BasicTable**
- Fix the issue that editable cells cannot be submitted in some cases
- Fix the problem that the `inset` attribute does not work
- Fix the problem that the performance of `useTable` and `reload` method `await` of `BasicTable` instance are inconsistent
- Fix the issue that `clickToRowSelect` would ignore the disabled state of the row selection box
- Fix the problem that the page of `BasicTable` will be reset in some cases
- Modify the `deleteTableDataRecord` method
- **BasicModal**
- Fixed the problem that `Modal` could not be closed even when clicking on the mask and pressing the `Esc` key
- Fixed the issue that clicking the close button and the blank area next to the maximize button would also cause `Modal` to close
- **BasicTree** Fix the problem that the node slot does not work
- **CodeEditor** Fix the problem that may cause `Build` failure
- **BasicForm** Fix the problem that the content width of the custom FormItem component may be out of range
- **ApiTreeSelect** Fix the problem that the change of `params` failed to trigger the re-request of api data
- **Others** -Fixed an issue where multiple tabs would not jump to routing when closing tabs in some cases
- Fix the issue that some components may cause abnormal hot update
- Fix the problem that some sub-components of `antdv` will be reported in the build process when directly `import` part of the `antdv`, such as: TabPane, RadioGroup
## 2.7.2(2021-09-14)
### ✨ Features

View File

@@ -1,3 +1,38 @@
## 2.8.0(2021-11.03)
### 升级说明
- 包管理器由`yarn`改为 `pnpm`
- 删除`node_modules``yarn.lock`,全局安装`pnpm`
- 执行`pnpm install`
### ✨ Features
- **其它**
- `.env`文件中的`VITE_PROXY`配置支持单引号
- 移除 build 过程中的警告
### 🐛 Bug Fixes
- **BasicTable**
- 修复可编辑单元格某些情况下无法提交的问题
- 修复`inset`属性不起作用的问题
- 修复`useTable``BasicTable`实例的`reload`方法`await`表现不一致的问题
- 修复`clickToRowSelect`会无视行选择框 disabled 状态的问题
- 修复`BasicTable`在某些情况下,分页会被重置的问题
- 修改 `deleteTableDataRecord` 方法
- **BasicModal**
- 修复点击遮罩、按下`Esc`键都不能关闭`Modal`的问题
- 修复点击关闭按钮、最大化按钮旁边的空白区域也会导致`Modal`关闭的问题
- **BasicTree** 修复节点插槽不起作用的问题
- **CodeEditor** 修复可能会造成的`Build`失败的问题
- **BasicForm** 修复自定义 FormItem 组件的内容宽度可能超出范围的问题
- **ApiTreeSelect** 修复`params`变化未能触发重新请求 api 数据的问题
- **其它**
- 修复多标签在某些情况下关闭页签不会跳转路由的问题
- 修复部分组件可能会造成热更新异常的问题
- 修复直接`import`部分`antdv`子组件时会在 build 过程中报错的问题TabPane、RadioGroup
## 2.7.2(2021-09-14)
### ✨ Features

View File

@@ -5,18 +5,19 @@ import { GLOB_CONFIG_FILE_NAME, OUTPUT_DIR } from '../constant';
import fs, { writeFileSync } from 'fs-extra';
import chalk from 'chalk';
import { getRootPath, getEnvConfig } from '../utils';
import { getEnvConfig, getRootPath } from '../utils';
import { getConfigFileName } from '../getConfigFileName';
import pkg from '../../package.json';
function createConfig(
{
configName,
config,
configFileName = GLOB_CONFIG_FILE_NAME,
}: { configName: string; config: any; configFileName?: string } = { configName: '', config: {} },
) {
interface CreateConfigParams {
configName: string;
config: any;
configFileName?: string;
}
function createConfig(params: CreateConfigParams) {
const { configName, config, configFileName } = params;
try {
const windowConf = `window.${configName}`;
// Ensure that the variable will not be modified
@@ -40,5 +41,5 @@ function createConfig(
export function runBuildConfig() {
const config = getEnvConfig();
const configFileName = getConfigFileName(config);
createConfig({ config, configName: configFileName });
createConfig({ config, configName: configFileName, configFileName: GLOB_CONFIG_FILE_NAME });
}

View File

@@ -28,9 +28,9 @@ export function wrapperEnv(envConf: Recordable): ViteEnv {
if (envName === 'VITE_PORT') {
realName = Number(realName);
}
if (envName === 'VITE_PROXY') {
if (envName === 'VITE_PROXY' && realName) {
try {
realName = JSON.parse(realName);
realName = JSON.parse(realName.replace(/'/g, '"'));
} catch (error) {
realName = '';
}

View File

@@ -14,7 +14,62 @@ export function configStyleImportPlugin(isBuild: boolean) {
libraryName: 'ant-design-vue',
esModule: true,
resolveStyle: (name) => {
return `ant-design-vue/es/${name}/style/index`;
// 这里是无需额外引入样式文件的“子组件”列表
const ignoreList = [
'anchor-link',
'sub-menu',
'menu-item',
'menu-item-group',
'breadcrumb-item',
'breadcrumb-separator',
'form-item',
'step',
'select-option',
'select-opt-group',
'card-grid',
'card-meta',
'collapse-panel',
'descriptions-item',
'list-item',
'list-item-meta',
'table-column',
'table-column-group',
'tab-pane',
'tab-content',
'timeline-item',
'tree-node',
'skeleton-input',
'skeleton-avatar',
'skeleton-title',
'skeleton-paragraph',
'skeleton-image',
'skeleton-button',
];
// 这里是需要额外引入样式的子组件列表
// 单独引入子组件时需引入组件样式,否则会在打包后导致子组件样式丢失
const replaceList = {
'typography-text': 'typography',
'typography-title': 'typography',
'typography-paragraph': 'typography',
'typography-link': 'typography',
'dropdown-button': 'dropdown',
'input-password': 'input',
'input-search': 'input',
'input-group': 'input',
'radio-group': 'radio',
'checkbox-group': 'checkbox',
'layout-sider': 'layout',
'layout-content': 'layout',
'layout-footer': 'layout',
'layout-header': 'layout',
'month-picker': 'date-picker',
};
return ignoreList.includes(name)
? ''
: replaceList.hasOwnProperty(name)
? `ant-design-vue/es/${replaceList[name]}/style/index`
: `ant-design-vue/es/${name}/style/index`;
},
},
],

View File

@@ -7,7 +7,7 @@ type ProxyItem = [string, string];
type ProxyList = ProxyItem[];
type ProxyTargetList = Record<string, ProxyOptions & { rewrite: (path: string) => string }>;
type ProxyTargetList = Record<string, ProxyOptions>;
const httpsRE = /^https:\/\//;

View File

@@ -7,6 +7,7 @@ module.exports = {
'header-max-length': [2, 'always', 108],
'subject-empty': [2, 'never'],
'type-empty': [2, 'never'],
'subject-case': [0],
'type-enum': [
2,
'always',

View File

@@ -1,5 +1,6 @@
import { MockMethod } from 'vite-plugin-mock';
import { resultSuccess, resultError } from '../_util';
import { ResultEnum } from '../../src/enums/httpEnum';
const userInfo = {
name: 'Vben',
@@ -59,4 +60,12 @@ export default [
return resultError();
},
},
{
url: '/basic-api/user/tokenExpired',
method: 'post',
statusCode: 200,
response: () => {
return resultError('Token Expired!', { code: ResultEnum.TIMEOUT as number });
},
},
] as MockMethod[];

325
mock/demo/api-cascader.ts Normal file
View File

@@ -0,0 +1,325 @@
import { MockMethod } from 'vite-plugin-mock';
import { resultSuccess } from '../_util';
const areaList: any[] = [
{
id: '530825900854620160',
code: '430000',
parentCode: '100000',
levelType: 1,
name: '湖南省',
province: '湖南省',
city: null,
district: null,
town: null,
village: null,
parentPath: '430000',
createTime: '2020-11-30 15:47:31',
updateTime: '2020-11-30 16:33:42',
customized: false,
usable: true,
},
{
id: '530825900883980288',
code: '430100',
parentCode: '430000',
levelType: 2,
name: '长沙市',
province: '湖南省',
city: '长沙市',
district: null,
town: null,
village: null,
parentPath: '430000,430100',
createTime: '2020-11-30 15:47:31',
updateTime: '2020-11-30 16:33:42',
customized: false,
usable: true,
},
{
id: '530825900951089152',
code: '430102',
parentCode: '430100',
levelType: 3,
name: '芙蓉区',
province: '湖南省',
city: '长沙市',
district: '芙蓉区',
town: null,
village: null,
parentPath: '430000,430100,430102',
createTime: '2020-11-30 15:47:31',
updateTime: '2020-11-30 16:33:42',
customized: false,
usable: true,
},
{
id: '530825901014003712',
code: '430104',
parentCode: '430100',
levelType: 3,
name: '岳麓区',
province: '湖南省',
city: '长沙市',
district: '岳麓区',
town: null,
village: null,
parentPath: '430000,430100,430104',
createTime: '2020-11-30 15:47:31',
updateTime: '2020-11-30 16:33:42',
customized: false,
usable: true,
},
{
id: '530825900988837888',
code: '430103',
parentCode: '430100',
levelType: 3,
name: '天心区',
province: '湖南省',
city: '长沙市',
district: '天心区',
town: null,
village: null,
parentPath: '430000,430100,430103',
createTime: '2020-11-30 15:47:31',
updateTime: '2020-11-30 16:33:42',
customized: false,
usable: true,
},
{
id: '530826672489115648',
code: '430103002',
parentCode: '430103',
levelType: 4,
name: '坡子街街道',
province: '湖南省',
city: '长沙市',
district: '天心区',
town: '坡子街街道',
village: null,
parentPath: '430000,430100,430103,430103002',
createTime: '2020-11-30 15:47:31',
updateTime: '2020-12-14 15:26:43',
customized: false,
usable: true,
},
{
id: '530840241171607552',
code: '430103002001',
parentCode: '430103002',
levelType: 5,
name: '八角亭社区',
province: '湖南省',
city: '长沙市',
district: '天心区',
town: '坡子街街道',
village: '八角亭社区',
parentPath: '430000,430100,430103,430103002,430103002001',
createTime: '2020-11-30 15:47:31',
updateTime: '2021-01-20 14:07:23',
customized: false,
usable: true,
},
{
id: '530840241200967680',
code: '430103002002',
parentCode: '430103002',
levelType: 5,
name: '西牌楼社区',
province: '湖南省',
city: '长沙市',
district: '天心区',
town: '坡子街街道',
village: '西牌楼社区',
parentPath: '430000,430100,430103,430103002,430103002002',
createTime: '2020-11-30 15:47:31',
updateTime: '2020-11-30 17:30:41',
customized: false,
usable: true,
},
{
id: '530840241230327808',
code: '430103002003',
parentCode: '430103002',
levelType: 5,
name: '太平街社区',
province: '湖南省',
city: '长沙市',
district: '天心区',
town: '坡子街街道',
village: '太平街社区',
parentPath: '430000,430100,430103,430103002,430103002003',
createTime: '2020-11-30 15:47:31',
updateTime: '2020-11-30 17:30:41',
customized: false,
usable: true,
},
{
id: '530840241259687936',
code: '430103002005',
parentCode: '430103002',
levelType: 5,
name: '坡子街社区',
province: '湖南省',
city: '长沙市',
district: '天心区',
town: '坡子街街道',
village: '坡子街社区',
parentPath: '430000,430100,430103,430103002,430103002005',
createTime: '2020-11-30 15:47:31',
updateTime: '2020-11-30 17:30:41',
customized: false,
usable: true,
},
{
id: '530840241284853760',
code: '430103002006',
parentCode: '430103002',
levelType: 5,
name: '青山祠社区',
province: '湖南省',
city: '长沙市',
district: '天心区',
town: '坡子街街道',
village: '青山祠社区',
parentPath: '430000,430100,430103,430103002,430103002006',
createTime: '2020-11-30 15:47:31',
updateTime: '2020-11-30 17:30:41',
customized: false,
usable: true,
},
{
id: '530840241310019584',
code: '430103002007',
parentCode: '430103002',
levelType: 5,
name: '沙河社区',
province: '湖南省',
city: '长沙市',
district: '天心区',
town: '坡子街街道',
village: '沙河社区',
parentPath: '430000,430100,430103,430103002,430103002007',
createTime: '2020-11-30 15:47:31',
updateTime: '2020-11-30 17:30:41',
customized: false,
usable: true,
},
{
id: '530840241381322752',
code: '430103002008',
parentCode: '430103002',
levelType: 5,
name: '碧湘社区',
province: '湖南省',
city: '长沙市',
district: '天心区',
town: '坡子街街道',
village: '碧湘社区',
parentPath: '430000,430100,430103,430103002,430103002008',
createTime: '2020-11-30 15:47:31',
updateTime: '2020-11-30 17:30:41',
customized: false,
usable: true,
},
{
id: '530840241410682880',
code: '430103002009',
parentCode: '430103002',
levelType: 5,
name: '创远社区',
province: '湖南省',
city: '长沙市',
district: '天心区',
town: '坡子街街道',
village: '创远社区',
parentPath: '430000,430100,430103,430103002,430103002009',
createTime: '2020-11-30 15:47:31',
updateTime: '2020-11-30 17:30:41',
customized: false,
usable: true,
},
{
id: '530840241431654400',
code: '430103002010',
parentCode: '430103002',
levelType: 5,
name: '楚湘社区',
province: '湖南省',
city: '长沙市',
district: '天心区',
town: '坡子街街道',
village: '楚湘社区',
parentPath: '430000,430100,430103,430103002,430103002010',
createTime: '2020-11-30 15:47:31',
updateTime: '2020-11-30 17:30:41',
customized: false,
usable: true,
},
{
id: '530840241465208832',
code: '430103002011',
parentCode: '430103002',
levelType: 5,
name: '西湖社区',
province: '湖南省',
city: '长沙市',
district: '天心区',
town: '坡子街街道',
village: '西湖社区',
parentPath: '430000,430100,430103,430103002,430103002011',
createTime: '2020-11-30 15:47:31',
updateTime: '2020-11-30 17:30:41',
customized: false,
usable: true,
},
{
id: '530840241502957568',
code: '430103002012',
parentCode: '430103002',
levelType: 5,
name: '登仁桥社区',
province: '湖南省',
city: '长沙市',
district: '天心区',
town: '坡子街街道',
village: '登仁桥社区',
parentPath: '430000,430100,430103,430103002,430103002012',
createTime: '2020-11-30 15:47:31',
updateTime: '2020-11-30 17:30:41',
customized: false,
usable: true,
},
{
id: '530840241553289216',
code: '430103002013',
parentCode: '430103002',
levelType: 5,
name: '文庙坪社区',
province: '湖南省',
city: '长沙市',
district: '天心区',
town: '坡子街街道',
village: '文庙坪社区',
parentPath: '430000,430100,430103,430103002,430103002013',
createTime: '2020-11-30 15:47:31',
updateTime: '2020-11-30 17:30:41',
customized: false,
usable: true,
},
];
export default [
{
url: '/basic-api/cascader/getAreaRecord',
timeout: 1000,
method: 'post',
response: ({ body }) => {
const { parentCode } = body || {};
if (!parentCode) {
return resultSuccess(areaList.filter((it) => it.code === '430000'));
}
return resultSuccess(areaList.filter((it) => it.parentCode === parentCode));
},
},
] as MockMethod[];

View File

@@ -1,11 +1,11 @@
import { MockMethod } from 'vite-plugin-mock';
import { resultSuccess } from '../_util';
const demoList = (keyword) => {
const demoList = (keyword, count = 20) => {
const result = {
list: [] as any[],
};
for (let index = 0; index < 20; index++) {
for (let index = 0; index < count; index++) {
result.list.push({
name: `${keyword ?? ''}选项${index}`,
id: `${index}`,
@@ -20,9 +20,9 @@ export default [
timeout: 1000,
method: 'get',
response: ({ query }) => {
const { keyword } = query;
const { keyword, count } = query;
console.log(keyword);
return resultSuccess(demoList(keyword));
return resultSuccess(demoList(keyword, count));
},
},
] as MockMethod[];

View File

@@ -12,7 +12,7 @@ function getRandomPics(count = 10): string[] {
const demoList = (() => {
const result: any[] = [];
for (let index = 0; index < 60; index++) {
for (let index = 0; index < 200; index++) {
result.push({
id: `${index}`,
beginTime: '@datetime',

View File

@@ -24,120 +24,124 @@
"lint:prettier": "prettier --write \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"",
"lint:stylelint": "stylelint --cache --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
"lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.js",
"lint:pretty": "pretty-quick --staged",
"test:unit": "jest",
"test:unit-coverage": "jest --coverage",
"test:gzip": "http-server dist --cors --gzip -c-1",
"test:br": "http-server dist --cors --brotli -c-1",
"test:gzip": "npx http-server dist --cors --gzip -c-1",
"test:br": "npx http-server dist --cors --brotli -c-1",
"reinstall": "rimraf yarn.lock && rimraf package.lock.json && rimraf node_modules && npm run bootstrap",
"prepare": "husky install",
"gen:icon": "esno ./build/generate/icon/index.ts"
},
"dependencies": {
"@ant-design/colors": "^6.0.0",
"@ant-design/icons-vue": "^6.0.1",
"@iconify/iconify": "^2.0.4",
"@logicflow/core": "^0.6.16",
"@logicflow/extension": "^0.6.16",
"@vueuse/core": "^6.3.3",
"@logicflow/core": "^0.7.2",
"@logicflow/extension": "^0.7.2",
"@vueuse/core": "^6.7.4",
"@vueuse/shared": "^6.7.4",
"@zxcvbn-ts/core": "^1.0.0-beta.0",
"ant-design-vue": "2.2.7",
"axios": "^0.21.4",
"codemirror": "^5.62.3",
"ant-design-vue": "2.2.8",
"axios": "^0.24.0",
"codemirror": "^5.63.3",
"cropperjs": "^1.5.12",
"crypto-js": "^4.1.1",
"echarts": "^5.2.0",
"echarts": "^5.2.2",
"intro.js": "^4.2.2",
"lodash-es": "^4.17.21",
"mockjs": "^1.1.0",
"moment": "^2.29.1",
"nprogress": "^0.2.0",
"path-to-regexp": "^6.2.0",
"pinia": "2.0.0-rc.9",
"pinia": "2.0.0",
"print-js": "^1.6.0",
"qrcode": "^1.4.4",
"qs": "^6.10.1",
"resize-observer-polyfill": "^1.5.1",
"showdown": "^1.9.1",
"sortablejs": "^1.14.0",
"tinymce": "^5.9.2",
"vditor": "^3.8.6",
"vue": "3.2.11",
"vue-i18n": "9.1.7",
"tinymce": "^5.10.0",
"vditor": "^3.8.7",
"vue": "^3.2.21",
"vue-i18n": "^9.1.9",
"vue-json-pretty": "^2.0.4",
"vue-router": "^4.0.11",
"vue-types": "^4.1.0",
"xlsx": "^0.17.1"
"vue-router": "^4.0.12",
"vue-types": "^4.1.1",
"xlsx": "^0.17.3"
},
"devDependencies": {
"@commitlint/cli": "^13.1.0",
"@commitlint/config-conventional": "^13.1.0",
"@iconify/json": "^1.1.401",
"@commitlint/cli": "^14.1.0",
"@commitlint/config-conventional": "^14.1.0",
"@iconify/json": "^1.1.422",
"@purge-icons/generated": "^0.7.0",
"@types/codemirror": "^5.60.2",
"@types/codemirror": "^5.60.5",
"@types/crypto-js": "^4.0.2",
"@types/fs-extra": "^9.0.12",
"@types/inquirer": "^8.1.1",
"@types/fs-extra": "^9.0.13",
"@types/inquirer": "^8.1.3",
"@types/intro.js": "^3.0.2",
"@types/jest": "^27.0.1",
"@types/jest": "^27.0.2",
"@types/lodash-es": "^4.17.5",
"@types/mockjs": "^1.0.4",
"@types/node": "^16.9.1",
"@types/node": "^16.11.6",
"@types/nprogress": "^0.2.0",
"@types/qrcode": "^1.4.1",
"@types/qs": "^6.9.7",
"@types/showdown": "^1.9.4",
"@types/sortablejs": "^1.10.7",
"@typescript-eslint/eslint-plugin": "^4.31.0",
"@typescript-eslint/parser": "^4.31.0",
"@vitejs/plugin-legacy": "^1.5.3",
"@vitejs/plugin-vue": "^1.6.2",
"@vitejs/plugin-vue-jsx": "^1.1.8",
"@vue/compiler-sfc": "3.2.11",
"@vue/test-utils": "^2.0.0-rc.14",
"autoprefixer": "^10.3.4",
"@typescript-eslint/eslint-plugin": "^5.3.0",
"@typescript-eslint/parser": "^5.3.0",
"@vitejs/plugin-legacy": "^1.6.2",
"@vitejs/plugin-vue": "^1.9.4",
"@vitejs/plugin-vue-jsx": "^1.2.0",
"@vue/compiler-sfc": "3.2.21",
"@vue/test-utils": "^2.0.0-rc.16",
"autoprefixer": "^10.4.0",
"commitizen": "^4.2.4",
"conventional-changelog-cli": "^2.1.1",
"cross-env": "^7.0.3",
"dotenv": "^10.0.0",
"eslint": "^7.32.0",
"eslint": "^8.1.0",
"eslint-config-prettier": "^8.3.0",
"eslint-define-config": "^1.0.9",
"eslint-plugin-jest": "^24.4.0",
"eslint-define-config": "^1.1.2",
"eslint-plugin-jest": "^25.2.2",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^7.17.0",
"esno": "^0.9.1",
"eslint-plugin-vue": "^8.0.3",
"esno": "^0.10.1",
"fs-extra": "^10.0.0",
"http-server": "^13.0.1",
"husky": "^7.0.2",
"inquirer": "^8.1.2",
"is-ci": "^3.0.0",
"jest": "^27.2.0",
"less": "^4.1.1",
"lint-staged": "^11.1.2",
"husky": "^7.0.4",
"inquirer": "^8.2.0",
"jest": "^27.3.1",
"less": "^4.1.2",
"lint-staged": "11.2.6",
"npm-run-all": "^4.1.5",
"postcss": "^8.3.6",
"prettier": "^2.4.0",
"pretty-quick": "^3.1.1",
"postcss": "^8.3.11",
"postcss-html": "^1.2.0",
"postcss-less": "^5.0.0",
"prettier": "^2.4.1",
"rimraf": "^3.0.2",
"rollup-plugin-visualizer": "5.5.2",
"stylelint": "^13.13.1",
"stylelint-config-prettier": "^8.0.2",
"stylelint-config-standard": "^22.0.0",
"stylelint-order": "^4.1.0",
"ts-jest": "^27.0.5",
"ts-node": "^10.2.1",
"typescript": "4.4.3",
"vite": "2.5.7",
"rollup-plugin-visualizer": "^5.5.2",
"stylelint": "^14.0.1",
"stylelint-config-html": "^1.0.0",
"stylelint-config-prettier": "^9.0.3",
"stylelint-config-standard": "^23.0.0",
"stylelint-order": "^5.0.0",
"ts-jest": "^27.0.7",
"ts-node": "^10.4.0",
"typescript": "^4.4.4",
"vite": "^2.6.13",
"vite-plugin-compression": "^0.3.5",
"vite-plugin-html": "^2.1.0",
"vite-plugin-imagemin": "^0.4.5",
"vite-plugin-html": "^2.1.1",
"vite-plugin-imagemin": "^0.4.6",
"vite-plugin-mock": "^2.9.6",
"vite-plugin-purge-icons": "^0.7.0",
"vite-plugin-pwa": "^0.11.2",
"vite-plugin-style-import": "^1.2.1",
"vite-plugin-svg-icons": "^1.0.4",
"vite-plugin-pwa": "^0.11.3",
"vite-plugin-style-import": "^1.3.0",
"vite-plugin-svg-icons": "^1.0.5",
"vite-plugin-theme": "^0.8.1",
"vite-plugin-vue-setup-extend": "^0.1.0",
"vite-plugin-windicss": "^1.4.2",
"vue-eslint-parser": "^7.11.0",
"vue-tsc": "^0.3.0"
"vite-plugin-windicss": "^1.4.12",
"vue-eslint-parser": "^8.0.1",
"vue-tsc": "^0.28.10"
},
"resolutions": {
"//": "Used to install imagemin dependencies, because imagemin may not be installed in China. If it is abroad, you can delete it",

11963
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +1,9 @@
module.exports = {
printWidth: 100,
tabWidth: 2,
useTabs: false,
semi: true,
vueIndentScriptAndStyle: true,
singleQuote: true,
quoteProps: 'as-needed',
bracketSpacing: true,
trailingComma: 'all',
jsxSingleQuote: false,
arrowParens: 'always',
insertPragma: false,
requirePragma: false,
proseWrap: 'never',
htmlWhitespaceSensitivity: 'strict',
endOfLine: 'auto',

View File

@@ -4,6 +4,7 @@ import { GetAccountInfoModel } from './model/accountModel';
enum Api {
ACCOUNT_INFO = '/account/getAccountInfo',
SESSION_TIMEOUT = '/user/sessionTimeout',
TOKEN_EXPIRED = '/user/tokenExpired',
}
// Get personal center-basic settings
@@ -11,3 +12,5 @@ enum Api {
export const accountInfoApi = () => defHttp.get<GetAccountInfoModel>({ url: Api.ACCOUNT_INFO });
export const sessionTimeoutApi = () => defHttp.post<void>({ url: Api.SESSION_TIMEOUT });
export const tokenExpiredApi = () => defHttp.post<void>({ url: Api.TOKEN_EXPIRED });

9
src/api/demo/cascader.ts Normal file
View File

@@ -0,0 +1,9 @@
import { defHttp } from '/@/utils/http/axios';
import { AreaModel, AreaParams } from '/@/api/demo/model/areaModel';
enum Api {
AREA_RECORD = '/cascader/getAreaRecord',
}
export const areaRecord = (data: AreaParams) =>
defHttp.post<AreaModel>({ url: Api.AREA_RECORD, data });

View File

@@ -0,0 +1,12 @@
export interface AreaModel {
id: string;
code: string;
parentCode: string;
name: string;
levelType: number;
[key: string]: string | number;
}
export interface AreaParams {
parentCode: string;
}

View File

@@ -3,7 +3,7 @@ export interface BasicPageParams {
pageSize: number;
}
export interface BasicFetchResult<T extends any> {
export interface BasicFetchResult<T> {
items: T[];
total: number;
}

View File

@@ -39,7 +39,7 @@
html[data-theme='dark'] {
.@{prefix-cls} {
border: 1px solid rgb(196, 188, 188);
border: 1px solid rgb(196 188 188);
}
}

View File

@@ -87,6 +87,7 @@
font-size: 16px;
font-weight: 700;
transition: all 0.5s;
line-height: normal;
}
}
</style>

View File

@@ -42,7 +42,7 @@
background-color: linear-gradient(-225deg, #d5dbe4, #f8f8f8);
border-radius: 2px;
box-shadow: inset 0 -2px 0 0 #cdcde6, inset 0 0 1px 1px #fff,
0 1px 2px 1px rgba(30, 35, 90, 0.4);
0 1px 2px 1px rgb(30 35 90 / 40%);
align-items: center;
justify-content: center;

View File

@@ -125,7 +125,7 @@
width: 100%;
height: 100%;
padding-top: 50px;
background-color: rgba(0, 0, 0, 0.25);
background-color: rgb(0 0 0 / 25%);
justify-content: center;
&--mobile {
@@ -159,7 +159,7 @@
&__item {
&-enter {
opacity: 0 !important;
opacity: 0% !important;
}
}
}
@@ -168,16 +168,16 @@
&-content {
position: relative;
width: 632px;
margin: 0 auto auto auto;
margin: 0 auto auto;
background-color: @component-background;
border-radius: 16px;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
box-shadow: 0 25px 50px -12px rgb(0 0 0 / 25%);
flex-direction: column;
}
&-input__wrapper {
display: flex;
padding: 14px 14px 0 14px;
padding: 14px 14px 0;
justify-content: space-between;
align-items: center;
}
@@ -245,7 +245,7 @@
background-color: @primary-color;
.@{prefix-cls}-list__item-enter {
opacity: 1;
opacity: 100%;
}
}
@@ -259,7 +259,7 @@
&-enter {
width: 30px;
opacity: 0;
opacity: 0%;
}
}
}

View File

@@ -1,10 +1,10 @@
<template>
<div class="p-2">
<div class="bg-white mb-2 p-4">
<div class="p-4 mb-2 bg-white">
<BasicForm @register="registerForm" />
</div>
{{ sliderProp.width }}
<div class="bg-white p-2">
<div class="p-2 bg-white">
<List
:grid="{ gutter: 5, xs: 1, sm: 2, md: 4, lg: 4, xl: 6, xxl: grid }"
:data-source="data"
@@ -167,7 +167,7 @@
pageSize.value = pz;
fetch();
}
function pageSizeChange(current, size) {
function pageSizeChange(_current, size) {
pageSize.value = size;
fetch();
}

View File

@@ -4,3 +4,5 @@ import jsonPreview from './src/json-preview/JsonPreview.vue';
export const CodeEditor = withInstall(codeEditor);
export const JsonPreview = withInstall(jsonPreview);
export * from './src/typing';

View File

@@ -8,22 +8,22 @@
/>
</div>
</template>
<script lang="ts">
export const MODE = {
JSON: 'application/json',
html: 'htmlmixed',
js: 'javascript',
};
</script>
<script lang="ts" setup>
import { computed } from 'vue';
import CodeMirrorEditor from './codemirror/CodeMirror.vue';
import { isString } from '/@/utils/is';
import { MODE } from './typing';
const props = defineProps({
value: { type: [Object, String] as PropType<Record<string, any> | string> },
mode: { type: String, default: MODE.JSON },
mode: {
type: String as PropType<MODE>,
default: MODE.JSON,
validator(value: any) {
// 这个值必须匹配下列字符串中的一个
return Object.values(MODE).includes(value);
},
},
readonly: { type: Boolean },
autoFormat: { type: Boolean, default: true },
});

View File

@@ -8,6 +8,7 @@
import { useAppStore } from '/@/store/modules/app';
import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn';
import CodeMirror from 'codemirror';
import { MODE } from './../typing';
// css
import './codemirror.css';
import 'codemirror/theme/idea.css';
@@ -18,7 +19,14 @@
import 'codemirror/mode/htmlmixed/htmlmixed';
const props = defineProps({
mode: { type: String, default: 'application/json' },
mode: {
type: String as PropType<MODE>,
default: MODE.JSON,
validator(value: any) {
// 这个值必须匹配下列字符串中的一个
return Object.values(MODE).includes(value);
},
},
value: { type: String, default: '' },
readonly: { type: Boolean, default: false },
});

View File

@@ -2,7 +2,7 @@
.CodeMirror {
--base: #545281;
--comment: hsl(210, 25%, 60%);
--comment: hsl(210deg 25% 60%);
--keyword: #af4ab1;
--variable: #0055d1;
--function: #c25205;
@@ -53,7 +53,7 @@
color: var(--comment);
text-align: right;
white-space: nowrap;
opacity: 0.6;
opacity: 60%;
}
.CodeMirror-guttermarker {
@@ -90,7 +90,7 @@
display: inline-block;
font-size: 0.8em;
content: '>';
opacity: 0.8;
opacity: 80%;
transform: rotate(90deg);
transition: transform 0.2s;
}
@@ -125,9 +125,7 @@
}
.cm-fat-cursor-mark {
background-color: rgba(20, 255, 20, 0.5);
-webkit-animation: blink 1.06s steps(1) infinite;
-moz-animation: blink 1.06s steps(1) infinite;
background-color: rgb(20 255 20 / 50%);
animation: blink 1.06s steps(1) infinite;
}
@@ -135,16 +133,14 @@
width: auto;
background-color: #7e7;
border: 0;
-webkit-animation: blink 1.06s steps(1) infinite;
-moz-animation: blink 1.06s steps(1) infinite;
animation: blink 1.06s steps(1) infinite;
}
@-moz-keyframes blink {
@keyframes blink {
50% {
background-color: transparent;
}
}
@-webkit-keyframes blink {
@keyframes blink {
50% {
background-color: transparent;
}
@@ -294,7 +290,7 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {
}
.CodeMirror-matchingtag {
background: rgba(255, 150, 0, 0.3);
background: rgb(255 150 0 / 30%);
}
.CodeMirror-activeline-background {
@@ -394,7 +390,7 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {
background-color: transparent;
}
.CodeMirror-gutter-wrapper ::-moz-selection {
.CodeMirrorwrapper ::selection {
background-color: transparent;
}
@@ -414,11 +410,8 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {
border-width: 0;
/* Reset some styles that the rest of the page might have set */
-moz-border-radius: 0;
-webkit-border-radius: 0;
border-radius: 0;
-webkit-tap-highlight-color: transparent;
-webkit-font-variant-ligatures: contextual;
font-variant-ligatures: contextual;
}
@@ -457,7 +450,6 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {
.CodeMirror-gutter,
.CodeMirror-gutters,
.CodeMirror-linenumber {
-moz-box-sizing: content-box;
box-sizing: content-box;
}
@@ -505,15 +497,9 @@ div.CodeMirror-dragcursors {
background: #d7d4f0;
}
.CodeMirror-line::-moz-selection,
.CodeMirror-line > span::-moz-selection,
.CodeMirror-line > span > span::-moz-selection {
background: #d7d4f0;
}
.cm-searching {
background-color: #ffa;
background-color: rgba(255, 255, 0, 0.4);
background-color: rgb(255 255 0 / 40%);
}
/* Used to force a border model for a node */

View File

@@ -0,0 +1,5 @@
export enum MODE {
JSON = 'application/json',
HTML = 'htmlmixed',
JS = 'javascript',
}

View File

@@ -178,22 +178,22 @@
margin: 0;
list-style: none;
background-color: @component-background;
border: 1px solid rgba(0, 0, 0, 0.08);
border: 1px solid rgb(0 0 0 / 8%);
border-radius: 0.25rem;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.1),
0 1px 5px 0 rgba(0, 0, 0, 0.06);
box-shadow: 0 2px 2px 0 rgb(0 0 0 / 14%), 0 3px 1px -2px rgb(0 0 0 / 10%),
0 1px 5px 0 rgb(0 0 0 / 6%);
background-clip: padding-box;
user-select: none;
.item-style();
.ant-divider {
margin: 0 0;
margin: 0;
}
&__popup {
.ant-divider {
margin: 0 0;
margin: 0;
}
.item-style();

View File

@@ -234,17 +234,17 @@
background: #eee;
background-image: linear-gradient(
45deg,
rgba(0, 0, 0, 0.25) 25%,
rgb(0 0 0 / 25%) 25%,
transparent 0,
transparent 75%,
rgba(0, 0, 0, 0.25) 0
rgb(0 0 0 / 25%) 0
),
linear-gradient(
45deg,
rgba(0, 0, 0, 0.25) 25%,
rgb(0 0 0 / 25%) 25%,
transparent 0,
transparent 75%,
rgba(0, 0, 0, 0.25) 0
rgb(0 0 0 / 25%) 0
);
background-position: 0 0, 12px 12px;
background-size: 24px 24px;

View File

@@ -135,15 +135,14 @@
}
&-image-mask {
opacity: 0;
opacity: 0%;
position: absolute;
width: inherit;
height: inherit;
border-radius: inherit;
border: inherit;
background: rgba(0, 0, 0, 0.4);
background: rgb(0 0 0 / 40%);
cursor: pointer;
-webkit-transition: opacity 0.4s;
transition: opacity 0.4s;
::v-deep(svg) {
@@ -152,7 +151,7 @@
}
&-image-mask:hover {
opacity: 40;
opacity: 4000%;
}
&-upload-btn {

View File

@@ -1,17 +1,17 @@
<template>
<Dropdown :trigger="trigger" v-bind="$attrs">
<a-dropdown :trigger="trigger" v-bind="$attrs">
<span>
<slot></slot>
</span>
<template #overlay>
<Menu :selectedKeys="selectedKeys">
<a-menu :selectedKeys="selectedKeys">
<template v-for="item in dropMenuList" :key="`${item.event}`">
<MenuItem
<a-menu-item
v-bind="getAttr(item.event)"
@click="handleClickMenu(item)"
:disabled="item.disabled"
>
<Popconfirm
<a-popconfirm
v-if="popconfirm && item.popConfirm"
v-bind="getPopConfirmAttrs(item.popConfirm)"
>
@@ -22,86 +22,75 @@
<Icon :icon="item.icon" v-if="item.icon" />
<span class="ml-1">{{ item.text }}</span>
</div>
</Popconfirm>
</a-popconfirm>
<template v-else>
<Icon :icon="item.icon" v-if="item.icon" />
<span class="ml-1">{{ item.text }}</span>
</template>
</MenuItem>
<MenuDivider v-if="item.divider" :key="`d-${item.event}`" />
</a-menu-item>
<a-menu-divider v-if="item.divider" :key="`d-${item.event}`" />
</template>
</Menu>
</a-menu>
</template>
</Dropdown>
</a-dropdown>
</template>
<script lang="ts">
<script lang="ts" setup>
import { computed, PropType } from 'vue';
import type { DropMenu } from './typing';
import { defineComponent } from 'vue';
import { Dropdown, Menu, Popconfirm } from 'ant-design-vue';
import { Icon } from '/@/components/Icon';
import { omit } from 'lodash-es';
import { isFunction } from '/@/utils/is';
export default defineComponent({
name: 'BasicDropdown',
components: {
Dropdown,
Menu,
MenuItem: Menu.Item,
MenuDivider: Menu.Divider,
Icon,
Popconfirm,
},
props: {
popconfirm: Boolean,
/**
* the trigger mode which executes the drop-down action
* @default ['hover']
* @type string[]
*/
trigger: {
type: [Array] as PropType<('contextmenu' | 'click' | 'hover')[]>,
default: () => {
return ['contextmenu'];
},
},
dropMenuList: {
type: Array as PropType<(DropMenu & Recordable)[]>,
default: () => [],
},
selectedKeys: {
type: Array as PropType<string[]>,
default: () => [],
const ADropdown = Dropdown;
const AMenu = Menu;
const AMenuItem = Menu.Item;
const AMenuDivider = Menu.Divider;
const APopconfirm = Popconfirm;
const props = defineProps({
popconfirm: Boolean,
/**
* the trigger mode which executes the drop-down action
* @default ['hover']
* @type string[]
*/
trigger: {
type: [Array] as PropType<('contextmenu' | 'click' | 'hover')[]>,
default: () => {
return ['contextmenu'];
},
},
emits: ['menuEvent'],
setup(props, { emit }) {
function handleClickMenu(item: DropMenu) {
const { event } = item;
const menu = props.dropMenuList.find((item) => `${item.event}` === `${event}`);
emit('menuEvent', menu);
item.onClick?.();
}
const getPopConfirmAttrs = computed(() => {
return (attrs) => {
const originAttrs = omit(attrs, ['confirm', 'cancel', 'icon']);
if (!attrs.onConfirm && attrs.confirm && isFunction(attrs.confirm))
originAttrs['onConfirm'] = attrs.confirm;
if (!attrs.onCancel && attrs.cancel && isFunction(attrs.cancel))
originAttrs['onCancel'] = attrs.cancel;
return originAttrs;
};
});
return {
handleClickMenu,
getPopConfirmAttrs,
getAttr: (key: string | number) => ({ key }),
};
dropMenuList: {
type: Array as PropType<(DropMenu & Recordable)[]>,
default: () => [],
},
selectedKeys: {
type: Array as PropType<string[]>,
default: () => [],
},
});
const emit = defineEmits(['menuEvent']);
function handleClickMenu(item: DropMenu) {
const { event } = item;
const menu = props.dropMenuList.find((item) => `${item.event}` === `${event}`);
emit('menuEvent', menu);
item.onClick?.();
}
const getPopConfirmAttrs = computed(() => {
return (attrs) => {
const originAttrs = omit(attrs, ['confirm', 'cancel', 'icon']);
if (!attrs.onConfirm && attrs.confirm && isFunction(attrs.confirm))
originAttrs['onConfirm'] = attrs.confirm;
if (!attrs.onCancel && attrs.cancel && isFunction(attrs.cancel))
originAttrs['onCancel'] = attrs.cancel;
return originAttrs;
};
});
const getAttr = (key: string | number) => ({ key });
</script>

View File

@@ -15,12 +15,25 @@
<script lang="ts">
import { defineComponent, ref, unref } from 'vue';
import XLSX from 'xlsx';
import { dateUtil } from '/@/utils/dateUtil';
import type { ExcelData } from './typing';
export default defineComponent({
name: 'ImportExcel',
props: {
// 日期时间格式。如果不提供或者提供空值将返回原始Date对象
dateFormat: {
type: String,
},
// 时区调整。实验性功能,仅为了解决读取日期时间值有偏差的问题。目前仅提供了+08:00时区的偏差修正值
// https://github.com/SheetJS/sheetjs/issues/1470#issuecomment-501108554
timeZone: {
type: Number,
default: 8,
},
},
emits: ['success', 'error'],
setup(_, { emit }) {
setup(props, { emit }) {
const inputRef = ref<HTMLInputElement | null>(null);
const loadingRef = ref<Boolean>(false);
@@ -51,10 +64,28 @@
*/
function getExcelData(workbook: XLSX.WorkBook) {
const excelData: ExcelData[] = [];
const { dateFormat, timeZone } = props;
for (const sheetName of workbook.SheetNames) {
const worksheet = workbook.Sheets[sheetName];
const header: string[] = getHeaderRow(worksheet);
const results = XLSX.utils.sheet_to_json(worksheet);
let results = XLSX.utils.sheet_to_json(worksheet, {
raw: true,
dateNF: dateFormat, //Not worked
}) as object[];
results = results.map((row: object) => {
for (let field in row) {
if (row[field] instanceof Date) {
if (timeZone === 8) {
row[field].setSeconds(row[field].getSeconds() + 43);
}
if (dateFormat) {
row[field] = dateUtil(row[field]).format(dateFormat);
}
}
}
return row;
});
excelData.push({
header,
results,
@@ -76,7 +107,7 @@
reader.onload = async (e) => {
try {
const data = e.target && e.target.result;
const workbook = XLSX.read(data, { type: 'array' });
const workbook = XLSX.read(data, { type: 'array', cellDates: true });
// console.log(workbook);
/* DO SOMETHING WITH workbook HERE */
const excelData = getExcelData(workbook);

View File

@@ -9,5 +9,7 @@ export { useForm } from './src/hooks/useForm';
export { default as ApiSelect } from './src/components/ApiSelect.vue';
export { default as RadioButtonGroup } from './src/components/RadioButtonGroup.vue';
export { default as ApiTreeSelect } from './src/components/ApiTreeSelect.vue';
export { default as ApiRadioGroup } from './src/components/ApiRadioGroup.vue';
export { default as ApiCascader } from './src/components/ApiCascader.vue';
export { BasicForm };

View File

@@ -21,9 +21,11 @@ import {
Divider,
} from 'ant-design-vue';
import ApiRadioGroup from './components/ApiRadioGroup.vue';
import RadioButtonGroup from './components/RadioButtonGroup.vue';
import ApiSelect from './components/ApiSelect.vue';
import ApiTreeSelect from './components/ApiTreeSelect.vue';
import ApiCascader from './components/ApiCascader.vue';
import { BasicUpload } from '/@/components/Upload';
import { StrengthMeter } from '/@/components/StrengthMeter';
import { IconPicker } from '/@/components/Icon';
@@ -43,11 +45,13 @@ componentMap.set('Select', Select);
componentMap.set('ApiSelect', ApiSelect);
componentMap.set('TreeSelect', TreeSelect);
componentMap.set('ApiTreeSelect', ApiTreeSelect);
componentMap.set('ApiRadioGroup', ApiRadioGroup);
componentMap.set('Switch', Switch);
componentMap.set('RadioButtonGroup', RadioButtonGroup);
componentMap.set('RadioGroup', Radio.Group);
componentMap.set('Checkbox', Checkbox);
componentMap.set('CheckboxGroup', Checkbox.Group);
componentMap.set('ApiCascader', ApiCascader);
componentMap.set('Cascader', Cascader);
componentMap.set('Slider', Slider);
componentMap.set('Rate', Rate);

View File

@@ -0,0 +1,197 @@
<template>
<a-cascader
v-model:value="state"
:options="options"
:load-data="loadData"
change-on-select
@change="handleChange"
:displayRender="handleRenderDisplay"
>
<template #suffixIcon v-if="loading">
<LoadingOutlined spin />
</template>
<template #notFoundContent v-if="loading">
<span>
<LoadingOutlined spin class="mr-1" />
{{ t('component.form.apiSelectNotFound') }}
</span>
</template>
</a-cascader>
</template>
<script lang="ts">
import { defineComponent, PropType, ref, unref, watch, watchEffect } from 'vue';
import { Cascader } from 'ant-design-vue';
import { propTypes } from '/@/utils/propTypes';
import { isFunction } from '/@/utils/is';
import { get, omit } from 'lodash-es';
import { useRuleFormItem } from '/@/hooks/component/useFormItem';
import { LoadingOutlined } from '@ant-design/icons-vue';
interface Option {
value: string;
label: string;
loading?: boolean;
isLeaf?: boolean;
children?: Option[];
}
export default defineComponent({
name: 'ApiCascader',
components: {
LoadingOutlined,
[Cascader.name]: Cascader,
},
props: {
value: {
type: Array,
},
api: {
type: Function as PropType<(arg?: Recordable) => Promise<Option[]>>,
default: null,
},
numberToString: propTypes.bool,
resultField: propTypes.string.def(''),
labelField: propTypes.string.def('label'),
valueField: propTypes.string.def('value'),
childrenField: propTypes.string.def('children'),
asyncFetchParamKey: propTypes.string.def('parentCode'),
immediate: propTypes.bool.def(true),
// init fetch params
initFetchParams: {
type: Object as PropType<Recordable>,
default: () => ({}),
},
// 是否有下级,默认是
isLeaf: {
type: Function as PropType<(arg: Recordable) => boolean>,
default: null,
},
displayRenderArray: {
type: Array,
},
},
emits: ['change', 'defaultChange'],
setup(props, { emit }) {
const apiData = ref<any[]>([]);
const options = ref<Option[]>([]);
const loading = ref<boolean>(false);
const emitData = ref<any[]>([]);
const isFirstLoad = ref(true);
// Embedded in the form, just use the hook binding to perform form verification
const [state] = useRuleFormItem(props, 'value', 'change', emitData);
watch(
apiData,
(data) => {
const opts = generatorOptions(data);
options.value = opts;
},
{ deep: true },
);
function generatorOptions(options: any[]): Option[] {
const { labelField, valueField, numberToString, childrenField, isLeaf } = props;
return options.reduce((prev, next: Recordable) => {
if (next) {
const value = next[valueField];
const item = {
...omit(next, [labelField, valueField]),
label: next[labelField],
value: numberToString ? `${value}` : value,
isLeaf: isLeaf && typeof isLeaf === 'function' ? isLeaf(next) : false,
};
const children = Reflect.get(next, childrenField);
if (children) {
Reflect.set(item, childrenField, generatorOptions(children));
}
prev.push(item);
}
return prev;
}, [] as Option[]);
}
async function initialFetch() {
const api = props.api;
if (!api || !isFunction(api)) return;
apiData.value = [];
loading.value = true;
try {
const res = await api(props.initFetchParams);
if (Array.isArray(res)) {
apiData.value = res;
return;
}
if (props.resultField) {
apiData.value = get(res, props.resultField) || [];
}
} catch (error) {
console.warn(error);
} finally {
loading.value = false;
}
}
async function loadData(selectedOptions: Option[]) {
const targetOption = selectedOptions[selectedOptions.length - 1];
targetOption.loading = true;
const api = props.api;
if (!api || !isFunction(api)) return;
try {
const res = await api({
[props.asyncFetchParamKey]: Reflect.get(targetOption, 'value'),
});
if (Array.isArray(res)) {
const children = generatorOptions(res);
targetOption.children = children;
return;
}
if (props.resultField) {
const children = generatorOptions(get(res, props.resultField) || []);
targetOption.children = children;
}
} catch (e) {
console.error(e);
} finally {
targetOption.loading = false;
}
}
watchEffect(() => {
props.immediate && initialFetch();
});
watch(
() => props.initFetchParams,
() => {
!unref(isFirstLoad) && initialFetch();
},
{ deep: true },
);
function handleChange(keys, args) {
emitData.value = keys;
emit('defaultChange', keys, args);
}
function handleRenderDisplay({ labels, selectedOptions }) {
if (unref(emitData).length === selectedOptions.length) {
return labels.join(' / ');
}
if (props.displayRenderArray) {
return props.displayRenderArray.join(' / ');
}
return '';
}
return {
state,
options,
loading,
handleChange,
loadData,
handleRenderDisplay,
};
},
});
</script>

View File

@@ -0,0 +1,130 @@
<!--
* @Description:It is troublesome to implement radio button group in the form. So it is extracted independently as a separate component
-->
<template>
<RadioGroup v-bind="attrs" v-model:value="state" button-style="solid" @change="handleChange">
<template v-for="item in getOptions" :key="`${item.value}`">
<RadioButton v-if="props.isBtn" :value="item.value" :disabled="item.disabled">
{{ item.label }}
</RadioButton>
<Radio v-else :value="item.value" :disabled="item.disabled">
{{ item.label }}
</Radio>
</template>
</RadioGroup>
</template>
<script lang="ts">
import { defineComponent, PropType, ref, watchEffect, computed, unref, watch } from 'vue';
import { Radio } from 'ant-design-vue';
import { isFunction } from '/@/utils/is';
import { useRuleFormItem } from '/@/hooks/component/useFormItem';
import { useAttrs } from '/@/hooks/core/useAttrs';
import { propTypes } from '/@/utils/propTypes';
import { get, omit } from 'lodash-es';
import { useI18n } from '/@/hooks/web/useI18n';
type OptionsItem = { label: string; value: string | number | boolean; disabled?: boolean };
export default defineComponent({
name: 'ApiRadioGroup',
components: {
RadioGroup: Radio.Group,
RadioButton: Radio.Button,
Radio,
},
props: {
api: {
type: Function as PropType<(arg?: Recordable | string) => Promise<OptionsItem[]>>,
default: null,
},
params: {
type: [Object, String] as PropType<Recordable | string>,
default: () => ({}),
},
value: {
type: [String, Number, Boolean] as PropType<string | number | boolean>,
},
isBtn: {
type: [Boolean] as PropType<boolean>,
default: false,
},
numberToString: propTypes.bool,
resultField: propTypes.string.def(''),
labelField: propTypes.string.def('label'),
valueField: propTypes.string.def('value'),
immediate: propTypes.bool.def(true),
},
emits: ['options-change', 'change'],
setup(props, { emit }) {
const options = ref<OptionsItem[]>([]);
const loading = ref(false);
const isFirstLoad = ref(true);
const emitData = ref<any[]>([]);
const attrs = useAttrs();
const { t } = useI18n();
// Embedded in the form, just use the hook binding to perform form verification
const [state] = useRuleFormItem(props);
// Processing options value
const getOptions = computed(() => {
const { labelField, valueField, numberToString } = props;
return unref(options).reduce((prev, next: Recordable) => {
if (next) {
const value = next[valueField];
prev.push({
label: next[labelField],
value: numberToString ? `${value}` : value,
...omit(next, [labelField, valueField]),
});
}
return prev;
}, [] as OptionsItem[]);
});
watchEffect(() => {
props.immediate && fetch();
});
watch(
() => props.params,
() => {
!unref(isFirstLoad) && fetch();
},
{ deep: true },
);
async function fetch() {
const api = props.api;
if (!api || !isFunction(api)) return;
options.value = [];
try {
loading.value = true;
const res = await api(props.params);
if (Array.isArray(res)) {
options.value = res;
emitChange();
return;
}
if (props.resultField) {
options.value = get(res, props.resultField) || [];
}
emitChange();
} catch (error) {
console.warn(error);
} finally {
loading.value = false;
}
}
function emitChange() {
emit('options-change', unref(getOptions));
}
function handleChange(_, ...args) {
emitData.value = args;
}
return { state, getOptions, attrs, loading, t, handleChange, props };
},
});
</script>

View File

@@ -77,9 +77,9 @@
if (next) {
const value = next[valueField];
prev.push({
...omit(next, [labelField, valueField]),
label: next[labelField],
value: numberToString ? `${value}` : value,
...omit(next, [labelField, valueField]),
});
}
return prev;

View File

@@ -44,7 +44,7 @@
watch(
() => props.params,
() => {
isFirstLoaded.value && fetch();
!unref(isFirstLoaded) && fetch();
},
{ deep: true },
);

View File

@@ -340,7 +340,7 @@
wrapperCol={wrapperCol}
>
<div style="display:flex">
<div style="flex:1">{getContent()}</div>
<div style="flex:1;">{getContent()}</div>
{showSuffix && <span class="suffix">{getSuffix}</span>}
</div>
</Form.Item>

View File

@@ -242,7 +242,7 @@ export function useFormEvents({
const values = await validate();
const res = handleFormValues(values);
emit('submit', res);
} catch (error) {
} catch (error: any) {
throw new Error(error);
}
}

View File

@@ -129,7 +129,7 @@ export interface FormSchema {
// Variable name bound to v-model Default value
valueField?: string;
// Label name
label: string;
label: string | VNode;
// Auxiliary text
subLabel?: string;
// Help text on the right side of the text

View File

@@ -92,11 +92,13 @@ export type ComponentType =
| 'ApiSelect'
| 'TreeSelect'
| 'ApiTreeSelect'
| 'ApiRadioGroup'
| 'RadioButtonGroup'
| 'RadioGroup'
| 'Checkbox'
| 'CheckboxGroup'
| 'AutoComplete'
| 'ApiCascader'
| 'Cascader'
| 'DatePicker'
| 'MonthPicker'

View File

@@ -7,7 +7,7 @@
v-model:value="currentSelect"
>
<template #addonAfter>
<Popover
<a-popover
placement="bottomLeft"
trigger="click"
v-model="visible"
@@ -17,7 +17,7 @@
<div class="flex justify-between">
<a-input
:placeholder="t('component.icon.search')"
@change="handleSearchChange"
@change="debounceHandleSearchChange"
allowClear
/>
</div>
@@ -53,7 +53,7 @@
</ul>
</ScrollContainer>
<div class="flex py-2 items-center justify-center" v-if="getTotal >= pageSize">
<Pagination
<a-pagination
showLessItems
size="small"
:pageSize="pageSize"
@@ -63,7 +63,7 @@
</div>
</div>
<template v-else
><div class="p-5"><Empty /></div>
><div class="p-5"><a-empty /></div>
</template>
</template>
@@ -71,16 +71,14 @@
<SvgIcon :name="currentSelect" />
</span>
<Icon :icon="currentSelect || 'ion:apps-outline'" class="cursor-pointer px-2 py-1" v-else />
</Popover>
</a-popover>
</template>
</a-input>
</template>
<script lang="ts">
import { defineComponent, ref, watchEffect, watch, unref } from 'vue';
<script lang="ts" setup>
import { ref, watchEffect, watch, unref } from 'vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { ScrollContainer } from '/@/components/Container';
import { Input, Popover, Pagination, Empty } from 'ant-design-vue';
import Icon from './Icon.vue';
import SvgIcon from './SvgIcon.vue';
@@ -94,6 +92,12 @@
import { useMessage } from '/@/hooks/web/useMessage';
import svgIcons from 'virtual:svg-icons-names';
// 没有使用别名引入是因为WebStorm当前版本还不能正确识别会报unused警告
const AInput = Input;
const APopover = Popover;
const APagination = Pagination;
const AEmpty = Empty;
function getIcons() {
const data = iconsData as any;
const prefix: string = data?.prefix ?? '';
@@ -110,88 +114,70 @@
return svgIcons.map((icon) => icon.replace('icon-', ''));
}
export default defineComponent({
name: 'IconPicker',
components: { [Input.name]: Input, Icon, Popover, ScrollContainer, Pagination, Empty, SvgIcon },
inheritAttrs: false,
props: {
value: propTypes.string,
width: propTypes.string.def('100%'),
pageSize: propTypes.number.def(140),
copy: propTypes.bool.def(false),
mode: propTypes.oneOf<('svg' | 'iconify')[]>(['svg', 'iconify']).def('iconify'),
},
emits: ['change', 'update:value'],
setup(props, { emit }) {
const isSvgMode = props.mode === 'svg';
const icons = isSvgMode ? getSvgIcons() : getIcons();
const currentSelect = ref('');
const visible = ref(false);
const currentList = ref(icons);
const { t } = useI18n();
const { prefixCls } = useDesign('icon-picker');
const debounceHandleSearchChange = useDebounceFn(handleSearchChange, 100);
const { clipboardRef, isSuccessRef } = useCopyToClipboard(props.value);
const { createMessage } = useMessage();
const { getPaginationList, getTotal, setCurrentPage } = usePagination(
currentList,
props.pageSize,
);
watchEffect(() => {
currentSelect.value = props.value;
});
watch(
() => currentSelect.value,
(v) => {
emit('update:value', v);
return emit('change', v);
},
);
function handlePageChange(page: number) {
setCurrentPage(page);
}
function handleClick(icon: string) {
currentSelect.value = icon;
if (props.copy) {
clipboardRef.value = icon;
if (unref(isSuccessRef)) {
createMessage.success(t('component.icon.copy'));
}
}
}
function handleSearchChange(e: ChangeEvent) {
const value = e.target.value;
if (!value) {
setCurrentPage(1);
currentList.value = icons;
return;
}
currentList.value = icons.filter((item) => item.includes(value));
}
return {
t,
prefixCls,
visible,
isSvgMode,
getTotal,
getPaginationList,
handlePageChange,
handleClick,
currentSelect,
handleSearchChange: debounceHandleSearchChange,
};
},
const props = defineProps({
value: propTypes.string,
width: propTypes.string.def('100%'),
pageSize: propTypes.number.def(140),
copy: propTypes.bool.def(false),
mode: propTypes.oneOf<('svg' | 'iconify')[]>(['svg', 'iconify']).def('iconify'),
});
const emit = defineEmits(['change', 'update:value']);
const isSvgMode = props.mode === 'svg';
const icons = isSvgMode ? getSvgIcons() : getIcons();
const currentSelect = ref('');
const visible = ref(false);
const currentList = ref(icons);
const { t } = useI18n();
const { prefixCls } = useDesign('icon-picker');
const debounceHandleSearchChange = useDebounceFn(handleSearchChange, 100);
const { clipboardRef, isSuccessRef } = useCopyToClipboard(props.value);
const { createMessage } = useMessage();
const { getPaginationList, getTotal, setCurrentPage } = usePagination(
currentList,
props.pageSize,
);
watchEffect(() => {
currentSelect.value = props.value;
});
watch(
() => currentSelect.value,
(v) => {
emit('update:value', v);
return emit('change', v);
},
);
function handlePageChange(page: number) {
setCurrentPage(page);
}
function handleClick(icon: string) {
currentSelect.value = icon;
if (props.copy) {
clipboardRef.value = icon;
if (unref(isSuccessRef)) {
createMessage.success(t('component.icon.copy'));
}
}
}
function handleSearchChange(e: ChangeEvent) {
const value = e.target.value;
if (!value) {
setCurrentPage(1);
currentList.value = icons;
return;
}
currentList.value = icons.filter((item) => item.includes(value));
}
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-icon-picker';

View File

@@ -1,5 +1,10 @@
<template>
<section class="full-loading" :class="{ absolute }" v-show="loading">
<section
class="full-loading"
:class="{ absolute, [theme]: !!theme }"
:style="[background ? `background-color: ${background}` : '']"
v-show="loading"
>
<Spin v-bind="$attrs" :tip="tip" :size="size" :spinning="loading" />
</section>
</template>
@@ -35,6 +40,9 @@
background: {
type: String as PropType<string>,
},
theme: {
type: String as PropType<'dark' | 'light'>,
},
},
});
</script>
@@ -49,7 +57,7 @@
height: 100%;
justify-content: center;
align-items: center;
background-color: rgba(240, 242, 245, 0.4);
background-color: rgb(240 242 245 / 40%);
&.absolute {
position: absolute;
@@ -60,8 +68,12 @@
}
html[data-theme='dark'] {
.full-loading {
.full-loading:not(.light) {
background-color: @modal-mask-bg;
}
}
.full-loading.dark {
background-color: @modal-mask-bg;
}
</style>

View File

@@ -1,4 +1,5 @@
<template>
<!-- eslint-disable vue/no-v-html -->
<div v-html="getHtmlData" :class="$props.class" class="markdown-viewer"></div>
</template>

View File

@@ -134,7 +134,9 @@
isClickGo.value = false;
return;
}
const path = (route || unref(currentRoute)).path;
const path =
(route || unref(currentRoute)).meta?.currentActiveMenu ||
(route || unref(currentRoute)).path;
setOpenKeys(path);
if (unref(currentActiveMenu)) return;
if (props.isHorizontal && unref(getSplit)) {

View File

@@ -1,5 +1,5 @@
<template>
<Modal v-bind="getBindValue">
<Modal v-bind="getBindValue" @cancel="handleCancel">
<template #closeIcon v-if="!$slots.closeIcon">
<ModalClose
:canFullscreen="getProps.canFullscreen"
@@ -72,6 +72,7 @@
import { basicProps } from './props';
import { useFullScreen } from './hooks/useModalFullScreen';
import { omit } from 'lodash-es';
import { useDesign } from '/@/hooks/web/useDesign';
export default defineComponent({
name: 'BasicModal',
@@ -83,6 +84,7 @@
const visibleRef = ref(false);
const propsRef = ref<Partial<ModalProps> | null>(null);
const modalWrapperRef = ref<any>(null);
const { prefixCls } = useDesign('basic-modal');
// modal Bottom and top height
const extHeightRef = ref(0);
@@ -175,7 +177,8 @@
// 取消事件
async function handleCancel(e: Event) {
e?.stopPropagation();
// 过滤自定义关闭按钮的空白区域
if ((e.target as HTMLElement)?.classList?.contains(prefixCls + '-close--custom')) return;
if (props.closeFunc && isFunction(props.closeFunc)) {
const isClose: boolean = await props.closeFunc();
visibleRef.value = !isClose;

View File

@@ -9,6 +9,7 @@ export default defineComponent({
name: 'Modal',
inheritAttrs: false,
props: basicProps,
emits: ['cancel'],
setup(props, { slots }) {
const { visible, draggable, destroyOnClose } = toRefs(props);
const attrs = useAttrs();

View File

@@ -97,7 +97,7 @@
}
}
& span:nth-child(2) {
& span:last-child {
&:hover {
color: @error-color;
}

View File

@@ -17,5 +17,6 @@
},
title: { type: String },
},
emits: ['dblclick'],
});
</script>

View File

@@ -54,7 +54,7 @@
}
&-content {
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
box-shadow: 0 4px 8px 0 rgb(0 0 0 / 20%), 0 6px 20px 0 rgb(0 0 0 / 19%);
}
&-footer {

View File

@@ -39,8 +39,8 @@
line-height: 44px;
background-color: @component-background;
border-top: 1px solid @border-color-base;
box-shadow: 0 -6px 16px -8px rgba(0, 0, 0, 0.08), 0 -9px 28px 0 rgba(0, 0, 0, 0.05),
0 -12px 48px 16px rgba(0, 0, 0, 0.03);
box-shadow: 0 -6px 16px -8px rgb(0 0 0 / 8%), 0 -9px 28px 0 rgb(0 0 0 / 5%),
0 -12px 48px 16px rgb(0 0 0 / 3%);
transition: width 0.2s;
&__left {

View File

@@ -432,7 +432,7 @@
bottom: 0;
left: 0;
z-index: @preview-comp-z-index;
background: rgba(0, 0, 0, 0.5);
background: rgb(0 0 0 / 50%);
user-select: none;
&-content {
@@ -458,7 +458,7 @@
overflow: hidden;
color: @white;
cursor: pointer;
background-color: rgba(0, 0, 0, 0.5);
background-color: rgb(0 0 0 / 50%);
border-radius: 50%;
transition: all 0.2s;
@@ -470,7 +470,7 @@
}
&:hover {
background-color: rgba(0, 0, 0, 0.8);
background-color: rgb(0 0 0 / 80%);
}
}
@@ -480,7 +480,7 @@
left: 50%;
padding: 0 22px;
font-size: 16px;
background: rgba(109, 109, 109, 0.6);
background: rgb(109 109 109 / 60%);
border-radius: 15px;
transform: translateX(-50%);
}
@@ -494,7 +494,7 @@
height: 44px;
padding: 0 22px;
margin-left: -139px;
background: rgba(109, 109, 109, 0.6);
background: rgb(109 109 109 / 60%);
border-radius: 22px;
justify-content: center;
@@ -526,12 +526,12 @@
height: 50px;
font-size: 28px;
cursor: pointer;
background-color: rgba(0, 0, 0, 0.5);
background-color: rgb(0 0 0 / 50%);
border-radius: 50%;
transition: all 0.2s;
&:hover {
background-color: rgba(0, 0, 0, 0.8);
background-color: rgb(0 0 0 / 80%);
}
&.left {

View File

@@ -88,7 +88,7 @@
}
.ant-image-preview-operations {
background-color: rgba(0, 0, 0, 0.4);
background-color: rgb(0 0 0 / 40%);
}
}
</style>

View File

@@ -148,7 +148,7 @@
display: none;
width: 0;
height: 0;
opacity: 0;
opacity: 0%;
}
}
}
@@ -159,12 +159,12 @@
width: 0;
height: 0;
cursor: pointer;
background-color: rgba(144, 147, 153, 0.3);
background-color: rgb(144 147 153 / 30%);
border-radius: inherit;
transition: 0.3s background-color;
&:hover {
background-color: rgba(144, 147, 153, 0.5);
background-color: rgb(144 147 153 / 50%);
}
}
@@ -174,8 +174,7 @@
bottom: 2px;
z-index: 1;
border-radius: 4px;
opacity: 0;
-webkit-transition: opacity 80ms ease;
opacity: 0%;
transition: opacity 80ms ease;
&.is-vertical {
@@ -201,7 +200,7 @@
.scrollbar:active > .scrollbar__bar,
.scrollbar:focus > .scrollbar__bar,
.scrollbar:hover > .scrollbar__bar {
opacity: 1;
opacity: 100%;
transition: opacity 340ms ease-out;
}
</style>

View File

@@ -188,7 +188,7 @@
&-vertical&-collapse &-item,
&-vertical&-collapse &-submenu-title {
padding: 0 0;
padding: 0;
}
&-vertical &-submenu-title-icon {

View File

@@ -92,7 +92,7 @@
background-color: transparent;
border-color: @white;
border-style: solid;
border-width: 0 5px 0 5px;
border-width: 0 5px;
content: '';
}

View File

@@ -222,7 +222,6 @@
const getBindValues = computed(() => {
const dataSource = unref(getDataSourceRef);
let propsData: Recordable = {
size: 'middle',
// ...(dataSource.length === 0 ? { getPopupContainer: () => document.body } : {}),
...attrs,
customRow,
@@ -358,19 +357,13 @@
padding: 16px;
.ant-form {
padding: 12px 10px 6px 10px;
padding: 12px 10px 6px;
margin-bottom: 16px;
background-color: @component-background;
border-radius: 2px;
}
}
&--inset {
.ant-table-wrapper {
padding: 0;
}
}
.ant-tag {
margin-right: 0;
}
@@ -382,7 +375,7 @@
.ant-table-title {
min-height: 40px;
padding: 0 0 8px 0 !important;
padding: 0 0 8px !important;
}
.ant-table.ant-table-bordered .ant-table-title {
@@ -408,7 +401,7 @@
}
.ant-pagination {
margin: 10px 0 0 0;
margin: 10px 0 0;
}
.ant-table-footer {
@@ -431,5 +424,11 @@
padding: 12px 8px;
}
}
&--inset {
.ant-table-wrapper {
padding: 0;
}
}
}
</style>

View File

@@ -42,7 +42,7 @@
.@{prefix-cls} {
&__help {
margin-left: 8px;
color: rgba(0, 0, 0, 0.65) !important;
color: rgb(0 0 0 / 65%) !important;
}
}
</style>

View File

@@ -246,7 +246,7 @@
if (!record) return false;
const { key, dataIndex } = column;
const value = unref(currentValueRef);
if (!key || !dataIndex) return;
if (!key && !dataIndex) return;
const dataKey = (dataIndex || key) as string;
@@ -265,7 +265,7 @@
result = await beforeEditSubmit({
record: pick(record, keys),
index,
key,
key: key as string,
value,
});
} catch (e) {

View File

@@ -420,7 +420,7 @@
&__fixed-left,
&__fixed-right {
color: rgba(0, 0, 0, 0.45);
color: rgb(0 0 0 / 45%);
cursor: pointer;
&.active,

View File

@@ -2,7 +2,14 @@ import componentSetting from '/@/settings/componentSetting';
const { table } = componentSetting;
const { pageSizeOptions, defaultPageSize, fetchSetting, defaultSortFn, defaultFilterFn } = table;
const {
pageSizeOptions,
defaultPageSize,
fetchSetting,
defaultSize,
defaultSortFn,
defaultFilterFn,
} = table;
export const ROW_KEY = 'key';
@@ -15,6 +22,9 @@ export const PAGE_SIZE = defaultPageSize;
// Common interface field settings
export const FETCH_SETTING = fetchSetting;
// Default Size
export const DEFAULT_SIZE = defaultSize;
// Configure general sort function
export const DEFAULT_SORT_FN = defaultSortFn;

View File

@@ -46,6 +46,14 @@ export function useCustomRow(
const isCheckbox = rowSelection.type === 'checkbox';
if (isCheckbox) {
// 找到tr
const tr: HTMLElement = (e as MouseEvent)
.composedPath?.()
.find((dom: HTMLElement) => dom.tagName === 'TR') as HTMLElement;
if (!tr) return;
// 找到Checkbox检查是否为disabled
const checkBox = tr.querySelector('input[type=checkbox]');
if (!checkBox || checkBox.hasAttribute('disabled')) return;
if (!keys.includes(key)) {
setSelectedRowKeys([...keys, key]);
return;

View File

@@ -160,21 +160,39 @@ export function useDataSource(
}
}
function deleteTableDataRecord(record: Recordable | Recordable[]): Recordable | undefined {
function deleteTableDataRecord(rowKey: string | number | string[] | number[]) {
if (!dataSourceRef.value || dataSourceRef.value.length == 0) return;
const records = !Array.isArray(record) ? [record] : record;
const recordIndex = records
.map((item) => dataSourceRef.value.findIndex((s) => s.key === item.key)) // 取序号
.filter((item) => item !== undefined)
.sort((a, b) => b - a); // 从大到小排序
for (const index of recordIndex) {
unref(dataSourceRef).splice(index, 1);
unref(propsRef).dataSource?.splice(index, 1);
const rowKeyName = unref(getRowKey);
if (!rowKeyName) return;
const rowKeys = !Array.isArray(rowKey) ? [rowKey] : rowKey;
for (const key of rowKeys) {
let index: number | undefined = dataSourceRef.value.findIndex((row) => {
let targetKeyName: string;
if (typeof rowKeyName === 'function') {
targetKeyName = rowKeyName(row);
} else {
targetKeyName = rowKeyName as string;
}
return row[targetKeyName] === key;
});
if (index >= 0) {
dataSourceRef.value.splice(index, 1);
}
index = unref(propsRef).dataSource?.findIndex((row) => {
let targetKeyName: string;
if (typeof rowKeyName === 'function') {
targetKeyName = rowKeyName(row);
} else {
targetKeyName = rowKeyName as string;
}
return row[targetKeyName] === key;
});
if (typeof index !== 'undefined' && index !== -1)
unref(propsRef).dataSource?.splice(index, 1);
}
setPagination({
total: unref(propsRef).dataSource?.length,
});
return unref(propsRef).dataSource;
}
function insertTableDataRecord(record: Recordable, index: number): Recordable | undefined {
@@ -223,8 +241,16 @@ export function useDataSource(
}
async function fetch(opt?: FetchParams) {
const { api, searchInfo, fetchSetting, beforeFetch, afterFetch, useSearchForm, pagination } =
unref(propsRef);
const {
api,
searchInfo,
defSort,
fetchSetting,
beforeFetch,
afterFetch,
useSearchForm,
pagination,
} = unref(propsRef);
if (!api || !isFunction(api)) return;
try {
setLoading(true);
@@ -251,6 +277,7 @@ export function useDataSource(
...(useSearchForm ? getFieldsValue() : {}),
...searchInfo,
...(opt?.searchInfo ?? {}),
...defSort,
...sortInfo,
...filterInfo,
...(opt?.sortInfo ?? {}),
@@ -275,7 +302,7 @@ export function useDataSource(
setPagination({
current: currentTotalPage,
});
fetch(opt);
return await fetch(opt);
}
}
@@ -295,6 +322,7 @@ export function useDataSource(
items: unref(resultItems),
total: resultTotal,
});
return resultItems;
} catch (error) {
emit('fetch-error', error);
dataSourceRef.value = [];
@@ -319,7 +347,7 @@ export function useDataSource(
}
async function reload(opt?: FetchParams) {
await fetch(opt);
return await fetch(opt);
}
onMounted(() => {

View File

@@ -1,6 +1,6 @@
import type { PaginationProps } from '../types/pagination';
import type { BasicTableProps } from '../types/table';
import { computed, unref, ref, ComputedRef, watchEffect } from 'vue';
import { computed, unref, ref, ComputedRef, watch } from 'vue';
import { LeftOutlined, RightOutlined } from '@ant-design/icons-vue';
import { isBoolean } from '/@/utils/is';
import { PAGE_SIZE, PAGE_SIZE_OPTIONS } from '../const';
@@ -27,15 +27,17 @@ export function usePagination(refProps: ComputedRef<BasicTableProps>) {
const configRef = ref<PaginationProps>({});
const show = ref(true);
watchEffect(() => {
const { pagination } = unref(refProps);
if (!isBoolean(pagination) && pagination) {
configRef.value = {
...unref(configRef),
...(pagination ?? {}),
};
}
});
watch(
() => unref(refProps).pagination,
(pagination) => {
if (!isBoolean(pagination) && pagination) {
configRef.value = {
...unref(configRef),
...(pagination ?? {}),
};
}
},
);
const getPaginationInfo = computed((): PaginationProps | boolean => {
const { pagination } = unref(refProps);

View File

@@ -68,7 +68,7 @@ export function useTable(tableProps?: Props): [
getForm: () => FormActionType;
} = {
reload: async (opt?: FetchParams) => {
getTableInstance().reload(opt);
return await getTableInstance().reload(opt);
},
setProps: (props: Partial<BasicTableProps>) => {
getTableInstance().setProps(props);
@@ -122,8 +122,8 @@ export function useTable(tableProps?: Props): [
updateTableData: (index: number, key: string, value: any) => {
return getTableInstance().updateTableData(index, key, value);
},
deleteTableDataRecord: (record: Recordable | Recordable[]) => {
return getTableInstance().deleteTableDataRecord(record);
deleteTableDataRecord: (rowKey: string | number | string[] | number[]) => {
return getTableInstance().deleteTableDataRecord(rowKey);
},
insertTableDataRecord: (record: Recordable | Recordable[], index?: number) => {
return getTableInstance().insertTableDataRecord(record, index);

View File

@@ -7,9 +7,10 @@ import type {
SorterResult,
TableCustomRecord,
TableRowSelection,
SizeType,
} from './types/table';
import type { FormProps } from '/@/components/Form';
import { DEFAULT_FILTER_FN, DEFAULT_SORT_FN, FETCH_SETTING } from './const';
import { DEFAULT_FILTER_FN, DEFAULT_SORT_FN, FETCH_SETTING, DEFAULT_SIZE } from './const';
import { propTypes } from '/@/utils/propTypes';
export const basicProps = {
@@ -69,6 +70,11 @@ export const basicProps = {
type: Object as PropType<Recordable>,
default: null,
},
// 默认的排序参数
defSort: {
type: Object as PropType<Recordable>,
default: null,
},
// 使用搜索表单
useSearchForm: propTypes.bool,
// 表单配置
@@ -136,4 +142,8 @@ export const basicProps = {
}) => Promise<any>
>,
},
size: {
type: String as PropType<SizeType>,
default: DEFAULT_SIZE,
},
};

View File

@@ -95,7 +95,7 @@ export interface TableActionType {
setPagination: (info: Partial<PaginationProps>) => void;
setTableData: <T = Recordable>(values: T[]) => void;
updateTableDataRecord: (rowKey: string | number, record: Recordable) => Recordable | void;
deleteTableDataRecord: (record: Recordable | Recordable[]) => Recordable | void;
deleteTableDataRecord: (rowKey: string | number | string[] | number[]) => void;
insertTableDataRecord: (record: Recordable, index?: number) => Recordable | void;
findTableDataRecord: (rowKey: string | number) => Recordable | void;
getColumns: (opt?: GetColumnsParams) => BasicColumn[];
@@ -176,6 +176,8 @@ export interface BasicTableProps<T = any> {
emptyDataIsShowTable?: boolean;
// 额外的请求参数
searchInfo?: Recordable;
// 默认的排序参数
defSort?: Recordable;
// 使用搜索表单
useSearchForm?: boolean;
// 表单配置

View File

@@ -399,7 +399,7 @@
const children = get(item, childrenField) || [];
const title = get(item, titleField);
const searchIdx = title.indexOf(searchText);
const searchIdx = searchText ? title.indexOf(searchText) : -1;
const isHighlight =
searchState.startSearch && !isEmpty(searchText) && highlight && searchIdx !== -1;
const highlightStyle = `color: ${isBoolean(highlight) ? '#f50' : highlight}`;
@@ -408,7 +408,7 @@
<span class={unref(getBindValues)?.blockNode ? `${prefixCls}__content` : ''}>
<span>{title.substr(0, searchIdx)}</span>
<span style={highlightStyle}>{searchText}</span>
<span>{title.substr(searchIdx + searchText.length)}</span>
<span>{title.substr(searchIdx + (searchText as string).length)}</span>
</span>
) : (
title
@@ -422,8 +422,8 @@
class={`${prefixCls}-title pl-2`}
onClick={handleClickNode.bind(null, item[keyField], item[childrenField])}
>
{slots?.title ? (
getSlot(slots, 'title', item)
{item.slots?.title ? (
getSlot(slots, item.slots?.title, item)
) : (
<>
{icon && <TreeIcon icon={icon} />}

View File

@@ -187,8 +187,12 @@
item.status = UploadResultStatus.UPLOADING;
const { data } = await props.api?.(
{
...(props.uploadParams || {}),
data: {
...(props.uploadParams || {}),
},
file: item.file,
name: props.name,
filename: props.filename,
},
function onUploadProgress(progressEvent: ProgressEvent) {
const complete = ((progressEvent.loaded / progressEvent.total) * 100) | 0;

View File

@@ -34,6 +34,14 @@ export const basicProps = {
default: null,
required: true,
},
name: {
type: String as PropType<string>,
default: 'file',
},
filename: {
type: String as PropType<string>,
default: null,
},
};
export const uploadContainerProps = {

View File

@@ -292,7 +292,7 @@
position: relative;
overflow: hidden;
text-align: center;
background-color: rgb(238, 238, 238);
background-color: rgb(238 238 238);
border: 1px solid #ddd;
border-radius: @radius;
@@ -313,7 +313,7 @@
position: absolute;
top: 0;
font-size: 12px;
-webkit-text-size-adjust: none;
text-size-adjust: none;
background-color: -webkit-gradient(
linear,
left top,

View File

@@ -209,7 +209,7 @@
}
&.normal {
background-color: rgba(0, 0, 0, 0.3);
background-color: rgb(0 0 0 / 30%);
}
}

View File

@@ -67,7 +67,7 @@
}
[data-theme='light'] &.ant-btn-link.is-disabled {
color: rgba(0, 0, 0, 0.25);
color: rgb(0 0 0 / 25%);
text-shadow: none;
cursor: not-allowed !important;
background-color: transparent !important;
@@ -76,7 +76,7 @@
}
[data-theme='dark'] &.ant-btn-link.is-disabled {
color: rgba(255, 255, 255, 0.25) !important;
color: rgb(255 255 255 / 25%) !important;
text-shadow: none;
cursor: not-allowed !important;
background-color: transparent !important;

View File

@@ -30,12 +30,12 @@ span.anticon:not(.app-iconify) {
}
.ant-image-preview-operations {
background-color: rgba(0, 0, 0, 0.3);
background-color: rgb(0 0 0 / 30%);
}
.ant-popover {
&-content {
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
}
}

View File

@@ -5,7 +5,7 @@
@import './theme.less';
input:-webkit-autofill {
-webkit-box-shadow: 0 0 0 1000px white inset !important;
box-shadow: 0 0 0 1000px white inset !important;
}
:-webkit-autofill {
@@ -14,7 +14,7 @@ input:-webkit-autofill {
html {
overflow: hidden;
-webkit-text-size-adjust: 100%;
text-size-adjust: 100%;
}
html,

View File

@@ -17,15 +17,15 @@
// }
::-webkit-scrollbar-track {
background-color: rgba(0, 0, 0, 0.05);
background-color: rgb(0 0 0 / 5%);
}
::-webkit-scrollbar-thumb {
// background: rgba(0, 0, 0, 0.6);
background-color: rgba(144, 147, 153, 0.3);
background-color: rgb(144 147 153 / 30%);
// background-color: rgba(144, 147, 153, 0.3);
border-radius: 2px;
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.2);
box-shadow: inset 0 0 6px rgb(0 0 0 / 20%);
}
::-webkit-scrollbar-thumb:hover {
@@ -46,6 +46,6 @@
width: 100%;
height: 2px;
background-color: @primary-color;
opacity: 0.75;
opacity: 75%;
}
}

View File

@@ -4,7 +4,7 @@
html[data-theme='light'] {
.text-secondary {
color: rgba(0, 0, 0, 0.45);
color: rgb(0 0 0 / 45%);
}
.ant-alert-success {
@@ -43,10 +43,10 @@ html[data-theme='light'] {
}
.ant-calendar-selected-day .ant-calendar-date {
color: rgba(0, 0, 0, 0.8);
color: rgb(0 0 0 / 80%);
}
.ant-select-tree li .ant-select-tree-node-content-wrapper.ant-select-tree-node-selected {
color: rgba(0, 0, 0, 0.9);
color: rgb(0 0 0 / 90%);
}
}

View File

@@ -5,7 +5,7 @@
.fade-enter-from,
.fade-leave-to {
opacity: 0;
opacity: 0%;
}
/* fade-slide */
@@ -15,12 +15,12 @@
}
.fade-slide-enter-from {
opacity: 0;
opacity: 0%;
transform: translateX(-30px);
}
.fade-slide-leave-to {
opacity: 0;
opacity: 0%;
transform: translateX(30px);
}
@@ -35,12 +35,12 @@
}
.fade-bottom-enter-from {
opacity: 0;
opacity: 0%;
transform: translateY(-10%);
}
.fade-bottom-leave-to {
opacity: 0;
opacity: 0%;
transform: translateY(10%);
}
@@ -51,12 +51,12 @@
}
.fade-scale-enter-from {
opacity: 0;
opacity: 0%;
transform: scale(1.2);
}
.fade-scale-leave-to {
opacity: 0;
opacity: 0%;
transform: scale(0.8);
}
@@ -71,11 +71,11 @@
}
.fade-top-enter-from {
opacity: 0;
opacity: 0%;
transform: translateY(8%);
}
.fade-top-leave-to {
opacity: 0;
opacity: 0%;
transform: translateY(-8%);
}

View File

@@ -4,7 +4,7 @@
&-enter-from,
&-leave,
&-leave-to {
opacity: 0;
opacity: 0%;
transform: scale(0);
}
}
@@ -15,7 +15,7 @@
&-enter-from,
&-leave,
&-leave-to {
opacity: 0;
opacity: 0%;
transform: scale(0) rotate(-45deg);
}
}

View File

@@ -3,7 +3,7 @@
&-enter-from,
&-leave-to {
opacity: 0;
opacity: 0%;
}
&-enter-from {
@@ -20,7 +20,7 @@
&-enter-from,
&-leave-to {
opacity: 0;
opacity: 0%;
}
&-enter-from {
@@ -37,7 +37,7 @@
&-enter-from,
&-leave-to {
opacity: 0;
opacity: 0%;
}
&-enter-from {
@@ -54,7 +54,7 @@
&-enter-from,
&-leave-to {
opacity: 0;
opacity: 0%;
}
&-enter-from {

View File

@@ -3,7 +3,7 @@
&-enter-from,
&-leave-to {
opacity: 0;
opacity: 0%;
transform: translateY(-15px);
}
}
@@ -13,7 +13,7 @@
&-enter-from,
&-leave-to {
opacity: 0;
opacity: 0%;
transform: translateY(15px);
}
}
@@ -23,7 +23,7 @@
&-enter-from,
&-leave-to {
opacity: 0;
opacity: 0%;
transform: translateX(-15px);
}
}
@@ -33,7 +33,7 @@
&-enter-from,
&-leave-to {
opacity: 0;
opacity: 0%;
transform: translateX(15px);
}
}

View File

@@ -6,7 +6,7 @@
.zoom-out-enter-from,
.zoom-out-leave-to {
opacity: 0;
opacity: 0%;
transform: scale(0);
}
@@ -17,11 +17,11 @@
}
.zoom-fade-enter-from {
opacity: 0;
opacity: 0%;
transform: scale(0.92);
}
.zoom-fade-leave-to {
opacity: 0;
opacity: 0%;
transform: scale(1.06);
}

View File

@@ -1,4 +1,4 @@
import type { UnwrapRef, Ref } from 'vue';
import type { UnwrapRef, Ref, WritableComputedRef, DeepReadonly } from 'vue';
import {
reactive,
readonly,
@@ -12,6 +12,13 @@ import {
import { isEqual } from 'lodash-es';
export function useRuleFormItem<T extends Recordable, K extends keyof T, V = UnwrapRef<T[K]>>(
props: T,
key?: K,
changeEvent?,
emitData?: Ref<any[]>,
): [WritableComputedRef<V>, (val: V) => void, DeepReadonly<V>];
export function useRuleFormItem<T extends Recordable>(
props: T,
key: keyof T = 'value',

View File

@@ -1,8 +1,6 @@
import { ref, unref } from 'vue';
export function useLockFn<P extends any[] = any[], V extends any = any>(
fn: (...args: P) => Promise<V>,
) {
export function useLockFn<P extends any[] = any[], V = any>(fn: (...args: P) => Promise<V>) {
const lockRef = ref(false);
return async function (...args: P) {
if (unref(lockRef)) return;

View File

@@ -51,7 +51,7 @@ export function copyTextToClipboard(input: string, { target = document.body }: O
let isSuccess = false;
try {
isSuccess = document.execCommand('copy');
} catch (e) {
} catch (e: any) {
throw new Error(e);
}

View File

@@ -3,6 +3,7 @@ import { useI18n } from '/@/hooks/web/useI18n';
import { useTitle as usePageTitle } from '@vueuse/core';
import { useGlobSetting } from '/@/hooks/setting';
import { useRouter } from 'vue-router';
import { useLocaleStore } from '/@/store/modules/locale';
import { REDIRECT_NAME } from '/@/router/constant';
@@ -13,11 +14,12 @@ export function useTitle() {
const { title } = useGlobSetting();
const { t } = useI18n();
const { currentRoute } = useRouter();
const localeStore = useLocaleStore();
const pageTitle = usePageTitle();
watch(
() => currentRoute.value.path,
[() => currentRoute.value.path, () => localeStore.getLocale],
() => {
const route = unref(currentRoute);

View File

@@ -101,12 +101,12 @@
if (!meta) {
return !!name;
}
const { title, hideBreadcrumb, hideMenu } = meta;
if (!title || hideBreadcrumb || hideMenu) {
const { title, hideBreadcrumb } = meta;
if (!title || hideBreadcrumb) {
return false;
}
return true;
}).filter((item) => !item.meta?.hideBreadcrumb || !item.meta?.hideMenu);
}).filter((item) => !item.meta?.hideBreadcrumb);
}
function handleClick(route: RouteLocationMatched, paths: string[], e: Event) {
@@ -169,7 +169,7 @@
color: @breadcrumb-item-normal-color;
a {
color: rgba(0, 0, 0, 0.65);
color: rgb(0 0 0 / 65%);
&:hover {
color: @primary-color;
@@ -184,10 +184,10 @@
&--dark {
.ant-breadcrumb-link {
color: rgba(255, 255, 255, 0.6);
color: rgb(255 255 255 / 60%);
a {
color: rgba(255, 255, 255, 0.8);
color: rgb(255 255 255 / 80%);
&:hover {
color: @white;
@@ -197,7 +197,7 @@
.ant-breadcrumb-separator,
.anticon {
color: rgba(255, 255, 255, 0.8);
color: rgb(255 255 255 / 80%);
}
}
}

View File

@@ -95,7 +95,7 @@
&__entry {
position: relative;
//height: 240px;
padding: 130px 30px 30px 30px;
padding: 130px 30px 30px;
border-radius: 10px;
}

View File

@@ -67,7 +67,7 @@
.@{header-trigger-prefix-cls} {
display: flex;
height: 100%;
padding: 1px 10px 0 10px;
padding: 1px 10px 0;
cursor: pointer;
align-items: center;

View File

@@ -189,7 +189,7 @@
&--mobile {
.@{logo-prefix-cls} {
&__title {
opacity: 1;
opacity: 100%;
}
}
}

View File

@@ -65,7 +65,7 @@
updateColorWeak(colorWeak);
updateGrayMode(grayMode);
createMessage.success(t('layout.setting.resetSuccess'));
} catch (error) {
} catch (error: any) {
createMessage.error(error);
}
}

View File

@@ -66,7 +66,7 @@
cursor: pointer;
background-color: #f0f2f5;
border-radius: 4px;
box-shadow: 0 1px 2.5px 0 rgba(0, 0, 0, 0.18);
box-shadow: 0 1px 2.5px 0 rgb(0 0 0 / 18%);
&::before,
&::after {

View File

@@ -60,7 +60,7 @@
&:hover {
background-color: @primary-color;
box-shadow: 0 0 4px 0 rgba(28, 36, 56, 0.15);
box-shadow: 0 0 4px 0 rgb(28 36 56 / 15%);
}
}
</style>

View File

@@ -80,13 +80,14 @@
<script lang="ts">
import type { Menu } from '/@/router/types';
import type { CSSProperties } from 'vue';
import { computed, defineComponent, onMounted, ref, unref } from 'vue';
import { computed, defineComponent, onMounted, ref, unref, watch } from 'vue';
import type { RouteLocationNormalized } from 'vue-router';
import { ScrollContainer } from '/@/components/Container';
import { SimpleMenu, SimpleMenuTag } from '/@/components/SimpleMenu';
import { Icon } from '/@/components/Icon';
import { AppLogo } from '/@/components/Application';
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
import { usePermissionStore } from '/@/store/modules/permission';
import { useDragLine } from './useLayoutSider';
import { useGlobSetting } from '/@/hooks/setting';
import { useDesign } from '/@/hooks/web/useDesign';
@@ -138,6 +139,7 @@
} = useMenuSetting();
const { title } = useGlobSetting();
const permissionStore = usePermissionStore();
useDragLine(sideRef, dragBarRef, true);
@@ -191,6 +193,17 @@
menuModules.value = await getShallowMenus();
});
// Menu changes
watch(
[() => permissionStore.getLastBuildMenuTime, () => permissionStore.getBackMenuList],
async () => {
menuModules.value = await getShallowMenus();
},
{
immediate: true,
},
);
listenerRouteChange((route) => {
currentRoute.value = route;
setActive(true);
@@ -362,19 +375,19 @@
&.light {
.@{prefix-cls}-logo {
border-bottom: 1px solid rgb(238, 238, 238);
border-bottom: 1px solid rgb(238 238 238);
}
&.open {
> .scrollbar {
border-right: 1px solid rgb(238, 238, 238);
border-right: 1px solid rgb(238 238 238);
}
}
.@{prefix-cls}-module {
&__item {
font-weight: normal;
color: rgba(0, 0, 0, 0.65);
color: rgb(0 0 0 / 65%);
&--active {
color: @primary-color;
@@ -384,15 +397,15 @@
}
.@{prefix-cls}-menu-list {
&__content {
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.1);
box-shadow: 0 0 4px 0 rgb(0 0 0 / 10%);
}
&__title {
.pushpin {
color: rgba(0, 0, 0, 0.35);
color: rgb(0 0 0 / 35%);
&:hover {
color: rgba(0, 0, 0, 0.85);
color: rgb(0 0 0 / 85%);
}
}
}
@@ -442,7 +455,7 @@
&__item {
position: relative;
padding: 12px 0;
color: rgba(255, 255, 255, 0.65);
color: rgb(255 255 255 / 65%);
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
@@ -487,7 +500,7 @@
left: 0;
width: 100%;
font-size: 14px;
color: rgba(255, 255, 255, 0.65);
color: rgb(255 255 255 / 65%);
text-align: center;
cursor: pointer;
background-color: @trigger-dark-bg-color;
@@ -496,7 +509,7 @@
}
&.light &-trigger {
color: rgba(0, 0, 0, 0.65);
color: rgb(0 0 0 / 65%);
background-color: #fff;
border-top: 1px solid #eee;
}
@@ -515,21 +528,21 @@
// margin-left: -6px;
font-size: 18px;
color: @primary-color;
border-bottom: 1px solid rgb(238, 238, 238);
opacity: 0;
border-bottom: 1px solid rgb(238 238 238);
opacity: 0%;
transition: unset;
align-items: center;
justify-content: space-between;
&.show {
min-width: 130px;
opacity: 1;
opacity: 100%;
transition: all 0.5s ease;
}
.pushpin {
margin-right: 6px;
color: rgba(255, 255, 255, 0.65);
color: rgb(255 255 255 / 65%);
cursor: pointer;
&:hover {
@@ -572,7 +585,7 @@
background-color: #f8f8f9;
border-top: none;
border-bottom: none;
box-shadow: 0 0 4px 0 rgba(28, 36, 56, 0.15);
box-shadow: 0 0 4px 0 rgb(28 36 56 / 15%);
}
}
</style>

View File

@@ -50,7 +50,7 @@ html[data-theme='light'] {
&:hover {
.ant-tabs-close-x {
opacity: 1;
opacity: 100%;
}
}
@@ -59,7 +59,7 @@ html[data-theme='light'] {
height: 12px;
font-size: 12px;
color: inherit;
opacity: 0;
opacity: 0%;
transition: none;
&:hover {
@@ -95,7 +95,7 @@ html[data-theme='light'] {
transition: none;
.ant-tabs-close-x {
opacity: 1;
opacity: 100%;
}
svg {
@@ -135,7 +135,7 @@ html[data-theme='light'] {
&--hide-close {
.ant-tabs-close-x {
opacity: 0 !important;
opacity: 0% !important;
}
}

View File

@@ -34,18 +34,21 @@ export function useTabDropdown(tabContentProps: TabContentProps, getIsTabs: Comp
const { meta } = unref(getTargetTab);
const { path } = unref(currentRoute);
// Refresh button
const curItem = state.current;
const isCurItem = curItem ? curItem.path === path : false;
// Refresh button
const index = state.currentIndex;
const refreshDisabled = curItem ? curItem.path !== path : true;
const refreshDisabled = !isCurItem;
// Close left
const closeLeftDisabled = index === 0;
const closeLeftDisabled = index === 0 || !isCurItem;
const disabled = tabStore.getTabList.length === 1;
// Close right
const closeRightDisabled =
index === tabStore.getTabList.length - 1 && tabStore.getLastDragEndIndex >= 0;
!isCurItem || (index === tabStore.getTabList.length - 1 && tabStore.getLastDragEndIndex >= 0);
const dropMenuList: DropMenu[] = [
{
icon: 'ion:reload-sharp',
@@ -78,7 +81,7 @@ export function useTabDropdown(tabContentProps: TabContentProps, getIsTabs: Comp
icon: 'dashicons:align-center',
event: MenuEventEnum.CLOSE_OTHER,
text: t('layout.multipleTab.closeOther'),
disabled: disabled,
disabled: disabled || !isCurItem,
},
{
icon: 'clarity:minus-line',

Some files were not shown because too many files have changed in this diff Show More