mirror of
https://github.com/m-xlsea/ruoyi-plus-soybean.git
synced 2025-09-24 07:49:47 +08:00
Merge branch 'master' into tauri
This commit is contained in:
73
.drone.yml
Normal file
73
.drone.yml
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
kind: pipeline
|
||||||
|
type: docker
|
||||||
|
name: Build and Deploy
|
||||||
|
|
||||||
|
clone:
|
||||||
|
depth: 10
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- name: go_cache
|
||||||
|
host:
|
||||||
|
path: /data/drone_cache/go_cache
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: restore-cache
|
||||||
|
image: drillster/drone-volume-cache
|
||||||
|
volumes:
|
||||||
|
- name: go_cache
|
||||||
|
path: /cache
|
||||||
|
settings:
|
||||||
|
restore: true
|
||||||
|
mount:
|
||||||
|
- ./.npm-cache
|
||||||
|
- ./node_modules
|
||||||
|
|
||||||
|
- name: build
|
||||||
|
image: node:alpine
|
||||||
|
pull: if-not-exists
|
||||||
|
commands:
|
||||||
|
- export NODE_OPTIONS=--max_old_space_size=6144
|
||||||
|
- echo ${DRONE_BRANCH}
|
||||||
|
- echo ${DRONE_TAG}
|
||||||
|
- echo ${DRONE_COMMIT}
|
||||||
|
- echo ${DRONE_COMMIT:0-7}
|
||||||
|
- npm config set registry https://registry.npmmirror.com
|
||||||
|
- npm install -g pnpm
|
||||||
|
- pnpm config set registry https://registry.npmmirror.com
|
||||||
|
- pnpm i
|
||||||
|
- pnpm build
|
||||||
|
|
||||||
|
- name: rebuild-cache
|
||||||
|
image: drillster/drone-volume-cache
|
||||||
|
volumes:
|
||||||
|
- name: go_cache
|
||||||
|
path: /cache
|
||||||
|
settings:
|
||||||
|
rebuild: true
|
||||||
|
mount:
|
||||||
|
- ./.npm-cache
|
||||||
|
- ./node_modules
|
||||||
|
|
||||||
|
- name: scp files
|
||||||
|
image: appleboy/drone-scp
|
||||||
|
pull: if-not-exists
|
||||||
|
settings:
|
||||||
|
host:
|
||||||
|
from_secret: HOST
|
||||||
|
username:
|
||||||
|
from_secret: USERNAME
|
||||||
|
password:
|
||||||
|
from_secret: PASSWORD
|
||||||
|
port:
|
||||||
|
from_secret: PORT
|
||||||
|
target:
|
||||||
|
from_secret: TARGET_PATH
|
||||||
|
source: dist/*
|
||||||
|
overwrite: true
|
||||||
|
rm: true
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
branch:
|
||||||
|
- master
|
||||||
|
event:
|
||||||
|
- push
|
@ -1,30 +0,0 @@
|
|||||||
---
|
|
||||||
name: Lint Code
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches: [main]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
lint:
|
|
||||||
name: Lint All Code
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout Code
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Lint Code Base
|
|
||||||
uses: github/super-linter@v4
|
|
||||||
env:
|
|
||||||
VALIDATE_ALL_CODEBASE: false
|
|
||||||
DEFAULT_BRANCH: main
|
|
||||||
# To change branch master or main
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
FILTER_REGEX_EXCLUDE: (docs|.github)
|
|
||||||
VALIDATE_MARKDOWN: false
|
|
@ -1,25 +0,0 @@
|
|||||||
name: Release
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- "v*"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
release:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: 18.x
|
|
||||||
|
|
||||||
- run: npx githublogen
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
|
43
.github/workflows/deploy.yml
vendored
Normal file
43
.github/workflows/deploy.yml
vendored
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
name: Build and Deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "master" ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 10.x
|
||||||
|
run_install: false
|
||||||
|
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20.x
|
||||||
|
cache: 'pnpm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install
|
||||||
|
|
||||||
|
- name: Build project
|
||||||
|
run: pnpm build
|
||||||
|
|
||||||
|
- name: Upload via SCP
|
||||||
|
uses: appleboy/scp-action@v1
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.SSH_HOST }}
|
||||||
|
username: ${{ secrets.SSH_USERNAME }}
|
||||||
|
password: ${{ secrets.SSH_PASSWORD }}
|
||||||
|
port: ${{ secrets.SSH_PORT }}
|
||||||
|
source: "dist/*"
|
||||||
|
target: ${{ secrets.TARGET_PATH }}
|
||||||
|
rm: true
|
||||||
|
overwrite: true
|
4
.github/workflows/linter.yml
vendored
4
.github/workflows/linter.yml
vendored
@ -6,7 +6,7 @@ permissions:
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [main]
|
branches: [master]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
@ -23,7 +23,7 @@ jobs:
|
|||||||
uses: github/super-linter@v4
|
uses: github/super-linter@v4
|
||||||
env:
|
env:
|
||||||
VALIDATE_ALL_CODEBASE: false
|
VALIDATE_ALL_CODEBASE: false
|
||||||
DEFAULT_BRANCH: main
|
DEFAULT_BRANCH: master
|
||||||
# To change branch master or main
|
# To change branch master or main
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
FILTER_REGEX_EXCLUDE: (docs|.github)
|
FILTER_REGEX_EXCLUDE: (docs|.github)
|
||||||
|
13
CHANGELOG.md
Normal file
13
CHANGELOG.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# 更新日志
|
||||||
|
|
||||||
|
## [v1.0.0](https://gitee.com/xlsea/ruoyi-plus-soybean/releases/tag/v1.0.0) (2025-06-05)
|
||||||
|
|
||||||
|
### 🚀 新功能
|
||||||
|
|
||||||
|
1.0.0 版本正式发布,此版本不包含工作流与多语言,请期待后续版本发布。
|
||||||
|
|
||||||
|
### ❤️ 贡献者
|
||||||
|
|
||||||
|
首次发版不展示过多贡献者,敬请谅解
|
||||||
|
|
||||||
|
[](https://github.com/honghuangdc) [](https://gitee.com/xlsea) [](https://gitee.com/elio-an) [](https://github.com/wangqiqi95)
|
@ -7,7 +7,8 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<p>一个基于 <a href="https://gitee.com/dromara/RuoYi-Vue-Plus" target="_blank">RuoYi-Vue-Plus</a> 的后端能力和 <a href="https://github.com/soybeanjs/soybean-admin" target="_blank">Soybean Admin</a> 前端特性的现代化多租户管理系统</p>
|
<p>一个基于 <a href="https://gitee.com/dromara/RuoYi-Vue-Plus" target="_blank">RuoYi-Vue-Plus</a> 的后端能力和 <a href="https://github.com/soybeanjs/soybean-admin" target="_blank">Soybean Admin</a> 前端特性的现代化多租户管理系统</p>
|
||||||
<p>
|
<p>
|
||||||
<img src="https://gitee.com/xlsea/ruoyi-plus-soybean/badge/star.svg?theme=blue" alt="Gitee">
|
<img src="https://gitee.com/xlsea/ruoyi-plus-soybean/badge/star.svg" alt="Gitee">
|
||||||
|
<img src="https://img.shields.io/github/stars/m-xlsea/ruoyi-plus-soybean" alt="Github">
|
||||||
<img src="https://img.shields.io/badge/Vue-3.5-brightgreen" alt="vue">
|
<img src="https://img.shields.io/badge/Vue-3.5-brightgreen" alt="vue">
|
||||||
<img src="https://img.shields.io/badge/TypeScript-5.8-blue" alt="typescript">
|
<img src="https://img.shields.io/badge/TypeScript-5.8-blue" alt="typescript">
|
||||||
<img src="https://img.shields.io/badge/Vite-6.2-orange" alt="vite">
|
<img src="https://img.shields.io/badge/Vite-6.2-orange" alt="vite">
|
||||||
@ -346,13 +347,14 @@ console.log(t('common.confirm'));
|
|||||||
- **邮箱**: xlsea@linux.do
|
- **邮箱**: xlsea@linux.do
|
||||||
- **作者主页**: https://gitee.com/xlsea
|
- **作者主页**: https://gitee.com/xlsea
|
||||||
|
|
||||||
|
|
||||||
- **作者**: Elio
|
- **作者**: Elio
|
||||||
- **邮箱**: 1983933789@qq.com
|
- **邮箱**: 1983933789@qq.com
|
||||||
- **作者主页**: https://gitee.com/ahcode
|
- **作者主页**: https://gitee.com/ahcode
|
||||||
|
|
||||||
## 💬 交流群
|
## 💬 交流群
|
||||||
|
|
||||||
<img src="https://foruda.gitee.com/images/1747707357650535622/a9fcf43e_5601833.png" width="300px" />
|
<img src="https://foruda.gitee.com/images/1748404753216665472/3d8b1a0b_5601833.png" width="300px" />
|
||||||
|
|
||||||
## 🧧 捐献作者
|
## 🧧 捐献作者
|
||||||
|
|
||||||
|
@ -26,9 +26,13 @@ UPDATE `sys_menu` SET `icon` = 'carbon:operations-record' WHERE `menu_id` = 500;
|
|||||||
UPDATE `sys_menu` SET `icon` = 'tabler:login-2' WHERE `menu_id` = 501;
|
UPDATE `sys_menu` SET `icon` = 'tabler:login-2' WHERE `menu_id` = 501;
|
||||||
UPDATE `sys_menu` SET `icon` = 'gg:debug' WHERE `menu_id` = 1500;
|
UPDATE `sys_menu` SET `icon` = 'gg:debug' WHERE `menu_id` = 1500;
|
||||||
UPDATE `sys_menu` SET `icon` = 'gg:debug' WHERE `menu_id` = 1506;
|
UPDATE `sys_menu` SET `icon` = 'gg:debug' WHERE `menu_id` = 1506;
|
||||||
|
UPDATE `sys_menu` SET `path` = 'oss/config', `component` = 'system/oss-config/index', `icon` = 'hugeicons:configuration-01' WHERE `menu_id` = 133;
|
||||||
|
|
||||||
-- IFrame 类型
|
-- IFrame 类型
|
||||||
UPDATE `sys_menu` SET `component` = 'FrameView', `query_param` = 'https://ruoyi.xlsea.cn/admin/', `is_frame` = 2, `icon` = 'bx:bxl-spring-boot' WHERE `menu_id` = 117;
|
UPDATE `sys_menu` SET `component` = 'FrameView', `query_param` = 'https://ruoyi.xlsea.cn/admin/', `is_frame` = 2, `icon` = 'bx:bxl-spring-boot' WHERE `menu_id` = 117;
|
||||||
UPDATE `sys_menu` SET `component` = 'FrameView', `query_param` = 'https://preview.snailjob.opensnail.com/', `is_frame` = 2, `icon` = 'gridicons:scheduled' WHERE `menu_id` = 120;
|
UPDATE `sys_menu` SET `component` = 'FrameView', `query_param` = 'https://preview.snailjob.opensnail.com/', `is_frame` = 2, `icon` = 'gridicons:scheduled' WHERE `menu_id` = 120;
|
||||||
-- 外链类型
|
-- 外链类型
|
||||||
UPDATE `sys_menu` SET `path` = 'https://gitee.com/xlsea/ruoyi-plus-soybean', `component` = 'FrameView', `icon` = 'local-icon-gitee' WHERE `menu_id` = 4;
|
UPDATE `sys_menu` SET `path` = 'https://gitee.com/xlsea/ruoyi-plus-soybean', `component` = 'FrameView', `icon` = 'local-icon-gitee' WHERE `menu_id` = 4;
|
||||||
|
|
||||||
|
-- plus-ui 需要禁用的页面
|
||||||
|
UPDATE `sys_menu` SET `status` = '1' WHERE `menu_id` IN ( '116', '130', '131', '132', '11700', '11701' );
|
||||||
|
6
docs/template/index-tree.vue.vm
vendored
6
docs/template/index-tree.vue.vm
vendored
@ -56,12 +56,6 @@ const {
|
|||||||
align: 'center',
|
align: 'center',
|
||||||
width: 48
|
width: 48
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: 'index',
|
|
||||||
title: $t('common.index'),
|
|
||||||
align: 'center',
|
|
||||||
width: 64
|
|
||||||
},
|
|
||||||
#foreach ($column in $columns)
|
#foreach ($column in $columns)
|
||||||
#if($column.list)
|
#if($column.list)
|
||||||
{
|
{
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "ruoyi-vue-plus",
|
"name": "ruoyi-vue-plus",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "1.0.0-beta.1",
|
"version": "1.0.0",
|
||||||
"description": "RuoYi-Vue-Plus多租户管理系统",
|
"description": "RuoYi-Vue-Plus多租户管理系统",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "xlsea",
|
"name": "xlsea",
|
||||||
|
@ -2,40 +2,59 @@ import { computed, onScopeDispose, ref } from 'vue';
|
|||||||
import { useRafFn } from '@vueuse/core';
|
import { useRafFn } from '@vueuse/core';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* count down
|
* A hook for implementing a countdown timer. It uses `requestAnimationFrame` for smooth and accurate timing,
|
||||||
|
* independent of the screen refresh rate.
|
||||||
*
|
*
|
||||||
* @param seconds - count down seconds
|
* @param initialSeconds - The total number of seconds for the countdown.
|
||||||
*/
|
*/
|
||||||
export default function useCountDown(seconds: number) {
|
export default function useCountDown(initialSeconds: number) {
|
||||||
const FPS_PER_SECOND = 60;
|
const remainingSeconds = ref(0);
|
||||||
|
|
||||||
const fps = ref(0);
|
const count = computed(() => Math.ceil(remainingSeconds.value));
|
||||||
|
|
||||||
const count = computed(() => Math.ceil(fps.value / FPS_PER_SECOND));
|
const isCounting = computed(() => remainingSeconds.value > 0);
|
||||||
|
|
||||||
const isCounting = computed(() => fps.value > 0);
|
|
||||||
|
|
||||||
const { pause, resume } = useRafFn(
|
const { pause, resume } = useRafFn(
|
||||||
() => {
|
({ delta }) => {
|
||||||
if (fps.value > 0) {
|
// delta: milliseconds elapsed since the last frame.
|
||||||
fps.value -= 1;
|
|
||||||
} else {
|
// If countdown already reached zero or below, ensure it's 0 and stop.
|
||||||
|
if (remainingSeconds.value <= 0) {
|
||||||
|
remainingSeconds.value = 0;
|
||||||
|
pause();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate seconds passed since the last frame.
|
||||||
|
const secondsPassed = delta / 1000;
|
||||||
|
remainingSeconds.value -= secondsPassed;
|
||||||
|
|
||||||
|
// If countdown has finished after decrementing.
|
||||||
|
if (remainingSeconds.value <= 0) {
|
||||||
|
remainingSeconds.value = 0;
|
||||||
pause();
|
pause();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ immediate: false }
|
{ immediate: false } // The timer does not start automatically.
|
||||||
);
|
);
|
||||||
|
|
||||||
function start(updateSeconds: number = seconds) {
|
/**
|
||||||
fps.value = FPS_PER_SECOND * updateSeconds;
|
* Starts the countdown.
|
||||||
|
*
|
||||||
|
* @param [updatedSeconds=initialSeconds] - Optionally, start with a new duration. Default is `initialSeconds`
|
||||||
|
*/
|
||||||
|
function start(updatedSeconds: number = initialSeconds) {
|
||||||
|
remainingSeconds.value = updatedSeconds;
|
||||||
resume();
|
resume();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Stops the countdown and resets the remaining time to 0. */
|
||||||
function stop() {
|
function stop() {
|
||||||
fps.value = 0;
|
remainingSeconds.value = 0;
|
||||||
pause();
|
pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure the rAF loop is cleaned up when the component is unmounted.
|
||||||
onScopeDispose(() => {
|
onScopeDispose(() => {
|
||||||
pause();
|
pause();
|
||||||
});
|
});
|
||||||
|
1
src/assets/svg-icon/bell.svg
Normal file
1
src/assets/svg-icon/bell.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg data-v-0a0a4a97="" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bell-icon size-4"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"></path><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"></path></svg>
|
After Width: | Height: | Size: 353 B |
35
src/components/common/data-table.vue
Normal file
35
src/components/common/data-table.vue
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useAttrs } from 'vue';
|
||||||
|
import type { DataTableProps } from 'naive-ui';
|
||||||
|
import type { CreateRowKey } from 'naive-ui/es/data-table/src/interface';
|
||||||
|
import { useThemeStore } from '@/store/modules/theme';
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'DataTable',
|
||||||
|
inheritAttrs: false
|
||||||
|
});
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
rowKey?: CreateRowKey<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
defineProps<Props>();
|
||||||
|
|
||||||
|
const { table } = useThemeStore();
|
||||||
|
const attrs: DataTableProps = useAttrs();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NDataTable
|
||||||
|
:bordered="table.bordered"
|
||||||
|
:bottom-bordered="table.bottomBordered"
|
||||||
|
:single-column="table.singleColumn"
|
||||||
|
:single-line="table.singleLine"
|
||||||
|
:size="table.size"
|
||||||
|
:striped="table.striped"
|
||||||
|
:row-key="rowKey"
|
||||||
|
v-bind="attrs"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, useAttrs } from 'vue';
|
import { type VNode, computed, useAttrs } from 'vue';
|
||||||
import type { ButtonProps, PopoverPlacement } from 'naive-ui';
|
import type { ButtonProps, PopoverPlacement } from 'naive-ui';
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
@ -11,6 +11,8 @@ defineOptions({
|
|||||||
interface Props {
|
interface Props {
|
||||||
/** Button class */
|
/** Button class */
|
||||||
class?: string;
|
class?: string;
|
||||||
|
/** Show popconfirm icon */
|
||||||
|
showPopconfirmIcon?: boolean;
|
||||||
/** Iconify icon name */
|
/** Iconify icon name */
|
||||||
icon?: string;
|
icon?: string;
|
||||||
/** Local icon name */
|
/** Local icon name */
|
||||||
@ -19,8 +21,8 @@ interface Props {
|
|||||||
tooltipContent?: string;
|
tooltipContent?: string;
|
||||||
/** Tooltip placement */
|
/** Tooltip placement */
|
||||||
tooltipPlacement?: PopoverPlacement;
|
tooltipPlacement?: PopoverPlacement;
|
||||||
/** Popconfirm content */
|
/** Popconfirm content - can be string or VNode */
|
||||||
popconfirmContent?: string;
|
popconfirmContent?: string | VNode;
|
||||||
zIndex?: number;
|
zIndex?: number;
|
||||||
quaternary?: boolean;
|
quaternary?: boolean;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
@ -28,6 +30,7 @@ interface Props {
|
|||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
class: '',
|
class: '',
|
||||||
|
showPopconfirmIcon: true,
|
||||||
icon: '',
|
icon: '',
|
||||||
localIcon: '',
|
localIcon: '',
|
||||||
tooltipContent: '',
|
tooltipContent: '',
|
||||||
@ -59,8 +62,11 @@ const handlePositiveClick = () => {
|
|||||||
<template>
|
<template>
|
||||||
<NTooltip :placement="tooltipPlacement" :z-index="zIndex" :disabled="!tooltipContent">
|
<NTooltip :placement="tooltipPlacement" :z-index="zIndex" :disabled="!tooltipContent">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<NPopconfirm :disabled="!popconfirmContent" @positive-click="handlePositiveClick">
|
<NPopconfirm :show-icon="showPopconfirmIcon" :disabled="!popconfirmContent" @positive-click="handlePositiveClick">
|
||||||
{{ popconfirmContent }}
|
<template #default>
|
||||||
|
<component :is="popconfirmContent" v-if="typeof popconfirmContent !== 'string'" />
|
||||||
|
<template v-else>{{ popconfirmContent }}</template>
|
||||||
|
</template>
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<NButton
|
<NButton
|
||||||
:quaternary="quaternary"
|
:quaternary="quaternary"
|
||||||
|
@ -44,8 +44,8 @@ const dictTagData = computed<Api.System.DictData[]>(() => {
|
|||||||
:key="item.dictValue"
|
:key="item.dictValue"
|
||||||
class="m-1"
|
class="m-1"
|
||||||
:class="[item.cssClass]"
|
:class="[item.cssClass]"
|
||||||
:type="item.listClass"
|
|
||||||
v-bind="attrs"
|
v-bind="attrs"
|
||||||
|
:type="item.listClass || 'default'"
|
||||||
>
|
>
|
||||||
{{ item.dictLabel }}
|
{{ item.dictLabel }}
|
||||||
</NTag>
|
</NTag>
|
||||||
|
@ -33,21 +33,18 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
|
|
||||||
const attrs: UploadProps = useAttrs();
|
const attrs: UploadProps = useAttrs();
|
||||||
|
|
||||||
const value = defineModel<CommonType.IdType[]>('value', { required: false, default: [] });
|
|
||||||
|
|
||||||
let fileNum = 0;
|
let fileNum = 0;
|
||||||
const fileList = ref<UploadFileInfo[]>([]);
|
const fileList = ref<UploadFileInfo[]>([]);
|
||||||
const needRelaodData = defineModel<boolean>('needRelaodData', {
|
|
||||||
default: false
|
const needRelaodData = ref(false);
|
||||||
});
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
refreshList: needRelaodData
|
needRelaodData
|
||||||
});
|
});
|
||||||
watch(
|
watch(
|
||||||
() => fileList.value,
|
() => fileList.value,
|
||||||
newValue => {
|
newValue => {
|
||||||
needRelaodData.value = newValue.length > 0;
|
needRelaodData.value = newValue.length > 0;
|
||||||
value.value = newValue.map(item => item.id);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -9,11 +9,13 @@ defineOptions({ name: 'MenuTree' });
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
immediate?: boolean;
|
immediate?: boolean;
|
||||||
|
showHeader?: boolean;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
immediate: true
|
immediate: true,
|
||||||
|
showHeader: true
|
||||||
});
|
});
|
||||||
|
|
||||||
const { bool: expandAll } = useBoolean();
|
const { bool: expandAll } = useBoolean();
|
||||||
@ -106,7 +108,7 @@ defineExpose({
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="w-full flex-col gap-12px">
|
<div class="w-full flex-col gap-12px">
|
||||||
<div class="w-full flex-center">
|
<div v-if="showHeader" class="w-full flex-center">
|
||||||
<NCheckbox v-model:checked="expandAll" :checked-value="true" :unchecked-value="false">展开/折叠</NCheckbox>
|
<NCheckbox v-model:checked="expandAll" :checked-value="true" :unchecked-value="false">展开/折叠</NCheckbox>
|
||||||
<NCheckbox
|
<NCheckbox
|
||||||
v-model:checked="checkAll"
|
v-model:checked="checkAll"
|
||||||
|
@ -63,3 +63,11 @@ export const resetCacheStrategyRecord: Record<UnionKey.ResetCacheStrategy, App.I
|
|||||||
export const resetCacheStrategyOptions = transformRecordToOption(resetCacheStrategyRecord);
|
export const resetCacheStrategyOptions = transformRecordToOption(resetCacheStrategyRecord);
|
||||||
|
|
||||||
export const DARK_CLASS = 'dark';
|
export const DARK_CLASS = 'dark';
|
||||||
|
|
||||||
|
export const themeTableSizeRecord: Record<UnionKey.ThemeTableSize, App.I18n.I18nKey> = {
|
||||||
|
small: 'theme.table.size.small',
|
||||||
|
medium: 'theme.table.size.medium',
|
||||||
|
large: 'theme.table.size.large'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const themeTableSizeOptions = transformRecordToOption(themeTableSizeRecord);
|
||||||
|
@ -3,5 +3,6 @@ export enum SetupStoreId {
|
|||||||
Theme = 'theme-store',
|
Theme = 'theme-store',
|
||||||
Auth = 'auth-store',
|
Auth = 'auth-store',
|
||||||
Route = 'route-store',
|
Route = 'route-store',
|
||||||
Tab = 'tab-store'
|
Tab = 'tab-store',
|
||||||
|
Notice = 'notice-store'
|
||||||
}
|
}
|
||||||
|
152
src/layouts/modules/global-header/components/message-button.vue
Normal file
152
src/layouts/modules/global-header/components/message-button.vue
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
import { useNoticeStore } from '@/store/modules/notice';
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'MessgaeButton'
|
||||||
|
});
|
||||||
|
|
||||||
|
const show = ref(false);
|
||||||
|
const noticeStore = useNoticeStore();
|
||||||
|
const { state } = storeToRefs(noticeStore);
|
||||||
|
|
||||||
|
const noticeNum = computed(() => {
|
||||||
|
return state.value.notices.filter(notice => !notice.read).length || 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
const toGitee = () => {
|
||||||
|
window.open('https://gitee.com/xlsea/ruoyi-plus-soybean', '_blank');
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NPopover v-model:show="show" trigger="click" arrow-point-to-center raw class="border-rounded-6px">
|
||||||
|
<template #trigger>
|
||||||
|
<NTooltip :disabled="show">
|
||||||
|
<template #trigger>
|
||||||
|
<NButton quaternary class="bell-button h-36px text-icon" :focusable="false">
|
||||||
|
<NBadge :value="noticeNum" :max="99" :offset="[2, -2]">
|
||||||
|
<div class="bell-icon flex-center gap-8px">
|
||||||
|
<SvgIcon local-icon="bell" />
|
||||||
|
</div>
|
||||||
|
</NBadge>
|
||||||
|
</NButton>
|
||||||
|
</template>
|
||||||
|
消息
|
||||||
|
</NTooltip>
|
||||||
|
</template>
|
||||||
|
<NCard
|
||||||
|
size="small"
|
||||||
|
:bordered="false"
|
||||||
|
class="w-340px"
|
||||||
|
header-class="p-0"
|
||||||
|
:segmented="{ content: true, footer: 'soft' }"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<span>通知公告</span>
|
||||||
|
</template>
|
||||||
|
<template #header-extra>
|
||||||
|
<NTooltip placement="left" :z-index="98">
|
||||||
|
<template #trigger>
|
||||||
|
<NPopconfirm @positive-click="() => noticeStore.readAll()">
|
||||||
|
<template #trigger>
|
||||||
|
<NButton quaternary>
|
||||||
|
<div class="flex-center gap-8px">
|
||||||
|
<SvgIcon icon="lucide:mail-check" class="text-16px" />
|
||||||
|
</div>
|
||||||
|
</NButton>
|
||||||
|
</template>
|
||||||
|
确定全部已读吗?
|
||||||
|
</NPopconfirm>
|
||||||
|
</template>
|
||||||
|
一键已读
|
||||||
|
</NTooltip>
|
||||||
|
</template>
|
||||||
|
<div>
|
||||||
|
<template v-if="state?.notices?.length">
|
||||||
|
<template v-for="(message, index) in state?.notices" :key="index">
|
||||||
|
<NDivider v-show="index !== 0" />
|
||||||
|
<div class="flex cursor-pointer" @click="() => noticeStore.readNotice(message)">
|
||||||
|
<div class="flex-col justify-between gap-3px">
|
||||||
|
<NEllipsis class="w-260px">{{ message.message }}</NEllipsis>
|
||||||
|
<span class="text-#898989">
|
||||||
|
{{ message.time }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<NTag :type="message.read ? 'success' : 'error'">{{ message.read ? '已读' : '未读' }}</NTag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<NEmpty v-else class="h-180px flex-center" />
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex items-center justify-end">
|
||||||
|
<NButton type="primary" size="small" @click="toGitee">前往 Gitee</NButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</NCard>
|
||||||
|
</NPopover>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
:deep(.n-divider) {
|
||||||
|
margin: 12px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-thing-header) {
|
||||||
|
margin-bottom: 1px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-thing-main__content) {
|
||||||
|
margin-top: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.messgae-popover) {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-badge-sup) {
|
||||||
|
padding: 0 5px !important;
|
||||||
|
font-size: 10px !important;
|
||||||
|
height: 15px !important;
|
||||||
|
line-height: 15px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bell-button {
|
||||||
|
&:hover {
|
||||||
|
.bell-icon {
|
||||||
|
animation: bell-ring 1s both;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bell-ring {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform-origin: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
15% {
|
||||||
|
transform: rotateZ(10deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
30% {
|
||||||
|
transform: rotateZ(-10deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
45% {
|
||||||
|
transform: rotateZ(5deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
60% {
|
||||||
|
transform: rotateZ(-5deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
75% {
|
||||||
|
transform: rotateZ(2deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -10,6 +10,7 @@ import GlobalBreadcrumb from '../global-breadcrumb/index.vue';
|
|||||||
import GlobalSearch from '../global-search/index.vue';
|
import GlobalSearch from '../global-search/index.vue';
|
||||||
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';
|
||||||
|
import MessageButton from './components/message-button.vue';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'GlobalHeader'
|
name: 'GlobalHeader'
|
||||||
@ -44,7 +45,8 @@ const tenantId = ref<CommonType.IdType>(authStore.userInfo?.user?.tenantId || '0
|
|||||||
</div>
|
</div>
|
||||||
<div class="h-full flex-y-center justify-end">
|
<div class="h-full flex-y-center justify-end">
|
||||||
<TenantSelect v-if="!appStore.isMobile" v-model:value="tenantId" class="mr-12px w-150px" />
|
<TenantSelect v-if="!appStore.isMobile" v-model:value="tenantId" class="mr-12px w-150px" />
|
||||||
<GlobalSearch />
|
<GlobalSearch v-if="themeStore.header.globalSearch.visible" />
|
||||||
|
<MessageButton />
|
||||||
<FullScreen v-if="!appStore.isMobile" :full="isFullscreen" @click="toggle" />
|
<FullScreen v-if="!appStore.isMobile" :full="isFullscreen" @click="toggle" />
|
||||||
<LangSwitch
|
<LangSwitch
|
||||||
v-if="themeStore.header.multilingual.visible"
|
v-if="themeStore.header.multilingual.visible"
|
||||||
|
@ -5,7 +5,6 @@ import { useElementBounding } from '@vueuse/core';
|
|||||||
import { PageTab } from '@sa/materials';
|
import { PageTab } from '@sa/materials';
|
||||||
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 { useTabStore } from '@/store/modules/tab';
|
import { useTabStore } from '@/store/modules/tab';
|
||||||
import { isPC } from '@/utils/agent';
|
import { isPC } from '@/utils/agent';
|
||||||
import BetterScroll from '@/components/custom/better-scroll.vue';
|
import BetterScroll from '@/components/custom/better-scroll.vue';
|
||||||
@ -18,7 +17,6 @@ defineOptions({
|
|||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const themeStore = useThemeStore();
|
const themeStore = useThemeStore();
|
||||||
const routeStore = useRouteStore();
|
|
||||||
const tabStore = useTabStore();
|
const tabStore = useTabStore();
|
||||||
|
|
||||||
const bsWrapper = ref<HTMLElement>();
|
const bsWrapper = ref<HTMLElement>();
|
||||||
@ -82,12 +80,8 @@ function getContextMenuDisabledKeys(tabId: string) {
|
|||||||
return disabledKeys;
|
return disabledKeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleCloseTab(tab: App.Global.Tab) {
|
function handleCloseTab(tab: App.Global.Tab) {
|
||||||
await tabStore.removeTab(tab.id);
|
tabStore.removeTab(tab.id);
|
||||||
|
|
||||||
if (themeStore.resetCacheStrategy === 'close') {
|
|
||||||
routeStore.resetRouteCache(tab.routeKey);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refresh() {
|
async function refresh() {
|
||||||
|
@ -6,6 +6,7 @@ import LayoutMode from './modules/layout-mode.vue';
|
|||||||
import ThemeColor from './modules/theme-color.vue';
|
import ThemeColor from './modules/theme-color.vue';
|
||||||
import PageFun from './modules/page-fun.vue';
|
import PageFun from './modules/page-fun.vue';
|
||||||
import ConfigOperation from './modules/config-operation.vue';
|
import ConfigOperation from './modules/config-operation.vue';
|
||||||
|
import TableProps from './modules/table-props.vue';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'ThemeDrawer'
|
name: 'ThemeDrawer'
|
||||||
@ -21,6 +22,7 @@ const appStore = useAppStore();
|
|||||||
<LayoutMode />
|
<LayoutMode />
|
||||||
<ThemeColor />
|
<ThemeColor />
|
||||||
<PageFun />
|
<PageFun />
|
||||||
|
<TableProps />
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<ConfigOperation />
|
<ConfigOperation />
|
||||||
</template>
|
</template>
|
||||||
|
@ -130,6 +130,9 @@ const isWrapperScrollMode = computed(() => themeStore.layout.scrollMode === 'wra
|
|||||||
<SettingItem key="9" :label="$t('theme.header.multilingual.visible')">
|
<SettingItem key="9" :label="$t('theme.header.multilingual.visible')">
|
||||||
<NSwitch v-model:value="themeStore.header.multilingual.visible" />
|
<NSwitch v-model:value="themeStore.header.multilingual.visible" />
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
<SettingItem key="10" :label="$t('theme.header.globalSearch.visible')">
|
||||||
|
<NSwitch v-model:value="themeStore.header.globalSearch.visible" />
|
||||||
|
</SettingItem>
|
||||||
</TransitionGroup>
|
</TransitionGroup>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
44
src/layouts/modules/theme-drawer/modules/table-props.vue
Normal file
44
src/layouts/modules/theme-drawer/modules/table-props.vue
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { themeTableSizeOptions } from '@/constants/app';
|
||||||
|
import { useThemeStore } from '@/store/modules/theme';
|
||||||
|
import { translateOptions } from '@/utils/common';
|
||||||
|
import { $t } from '@/locales';
|
||||||
|
import SettingItem from '../components/setting-item.vue';
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'TableProps'
|
||||||
|
});
|
||||||
|
|
||||||
|
const themeStore = useThemeStore();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NDivider>{{ $t('theme.tablePropsTitle') }}</NDivider>
|
||||||
|
<TransitionGroup tag="div" name="setting-list" class="flex-col-stretch gap-12px">
|
||||||
|
<SettingItem key="0" :label="$t('theme.table.size.title')">
|
||||||
|
<NSelect
|
||||||
|
v-model:value="themeStore.table.size"
|
||||||
|
:options="translateOptions(themeTableSizeOptions)"
|
||||||
|
size="small"
|
||||||
|
class="w-120px"
|
||||||
|
/>
|
||||||
|
</SettingItem>
|
||||||
|
<SettingItem key="1" :label="$t('theme.table.bordered')">
|
||||||
|
<NSwitch v-model:value="themeStore.table.bordered" />
|
||||||
|
</SettingItem>
|
||||||
|
<SettingItem key="2" :label="$t('theme.table.bottomBordered')">
|
||||||
|
<NSwitch v-model:value="themeStore.table.bottomBordered" />
|
||||||
|
</SettingItem>
|
||||||
|
<SettingItem key="3" :label="$t('theme.table.singleColumn')">
|
||||||
|
<NSwitch v-model:value="themeStore.table.singleColumn" :checked-value="false" :unchecked-value="true" />
|
||||||
|
</SettingItem>
|
||||||
|
<SettingItem key="4" :label="$t('theme.table.singleLine')">
|
||||||
|
<NSwitch v-model:value="themeStore.table.singleLine" :checked-value="false" :unchecked-value="true" />
|
||||||
|
</SettingItem>
|
||||||
|
<SettingItem key="5" :label="$t('theme.table.striped')">
|
||||||
|
<NSwitch v-model:value="themeStore.table.striped" />
|
||||||
|
</SettingItem>
|
||||||
|
</TransitionGroup>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
@ -135,6 +135,9 @@ const local: App.I18n.Schema = {
|
|||||||
},
|
},
|
||||||
multilingual: {
|
multilingual: {
|
||||||
visible: 'Display multilingual button'
|
visible: 'Display multilingual button'
|
||||||
|
},
|
||||||
|
globalSearch: {
|
||||||
|
visible: 'Display GlobalSearch button'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
tab: {
|
tab: {
|
||||||
@ -165,6 +168,20 @@ const local: App.I18n.Schema = {
|
|||||||
visible: 'Watermark Full Screen Visible',
|
visible: 'Watermark Full Screen Visible',
|
||||||
text: 'Watermark Text'
|
text: 'Watermark Text'
|
||||||
},
|
},
|
||||||
|
tablePropsTitle: 'Table Props',
|
||||||
|
table: {
|
||||||
|
size: {
|
||||||
|
title: 'Table Size',
|
||||||
|
small: 'Small',
|
||||||
|
medium: 'Medium',
|
||||||
|
large: 'Large'
|
||||||
|
},
|
||||||
|
bordered: 'Bordered',
|
||||||
|
bottomBordered: 'Bottom Bordered',
|
||||||
|
singleColumn: 'Single Column',
|
||||||
|
singleLine: 'Single Line',
|
||||||
|
striped: 'Striped'
|
||||||
|
},
|
||||||
themeDrawerTitle: 'Theme Configuration',
|
themeDrawerTitle: 'Theme Configuration',
|
||||||
pageFunTitle: 'Page Function',
|
pageFunTitle: 'Page Function',
|
||||||
resetCacheStrategy: {
|
resetCacheStrategy: {
|
||||||
@ -574,13 +591,14 @@ const local: App.I18n.Schema = {
|
|||||||
buttonPermissionList: 'Button Permission List',
|
buttonPermissionList: 'Button Permission List',
|
||||||
emptyMenu: 'Empty Menu',
|
emptyMenu: 'Empty Menu',
|
||||||
menuDetail: 'Menu Detail',
|
menuDetail: 'Menu Detail',
|
||||||
|
cascadeDeleteContent: 'Cascade delete menu will delete the selected menu and all its sub-menus, are you sure?',
|
||||||
iconifyTip: 'iconify address:`https://icones.js.org`',
|
iconifyTip: 'iconify address:`https://icones.js.org`',
|
||||||
isFrameTip: 'If you choose External Link, the routing address needs to start with `http(s)://`',
|
isFrameTip: 'If you choose External Link, the routing address needs to start with `http(s)://`',
|
||||||
isCacheTip:
|
isCacheTip:
|
||||||
'If you select yes, it will be cached by `keep-alive`, and the `name` and address of the matching component must be consistent',
|
'If you select yes, it will be cached by `keep-alive`, and the `name` and address of the matching component must be consistent',
|
||||||
visibleTip: 'If you choose Hide, the route will not appear in the sidebar, but it can still be accessed.',
|
visibleTip: 'If you choose Hide, the route will not appear in the sidebar, but it can still be accessed.',
|
||||||
statusTip: 'If you choose to disable, the route will not appear in the sidebar and cannot be accessed.',
|
statusTip: 'If you choose to disable, the route will not appear in the sidebar and cannot be accessed.',
|
||||||
permsTip: "Permission string defined in the controller, such as: @SaCheckPermission('system:user:list')",
|
permsTip: "Permission string defined in the controller, such as: {'@'}SaCheckPermission('system:user:list')",
|
||||||
componentTip:
|
componentTip:
|
||||||
'The component path to access, such as: `system/user/index`, which is in the `views` directory by default',
|
'The component path to access, such as: `system/user/index`, which is in the `views` directory by default',
|
||||||
pathTip:
|
pathTip:
|
||||||
@ -598,6 +616,10 @@ const local: App.I18n.Schema = {
|
|||||||
required: 'Please select Menu Icon',
|
required: 'Please select Menu Icon',
|
||||||
invalid: 'Menu Icon cannot be empty'
|
invalid: 'Menu Icon cannot be empty'
|
||||||
},
|
},
|
||||||
|
menuIds: {
|
||||||
|
required: 'Please select Menu',
|
||||||
|
invalid: 'Menu cannot be empty'
|
||||||
|
},
|
||||||
menuName: {
|
menuName: {
|
||||||
required: 'Please enter Menu Name',
|
required: 'Please enter Menu Name',
|
||||||
invalid: 'Menu Name cannot be empty'
|
invalid: 'Menu Name cannot be empty'
|
||||||
@ -655,7 +677,8 @@ const local: App.I18n.Schema = {
|
|||||||
button: 'Button',
|
button: 'Button',
|
||||||
addMenu: 'Add Menu',
|
addMenu: 'Add Menu',
|
||||||
addChildMenu: 'Add Child Menu',
|
addChildMenu: 'Add Child Menu',
|
||||||
editMenu: 'Edit Menu'
|
editMenu: 'Edit Menu',
|
||||||
|
cascadeDelete: 'Cascade Delete Menu'
|
||||||
},
|
},
|
||||||
notice: {
|
notice: {
|
||||||
title: 'Notice List',
|
title: 'Notice List',
|
||||||
|
@ -135,6 +135,9 @@ const local: App.I18n.Schema = {
|
|||||||
},
|
},
|
||||||
multilingual: {
|
multilingual: {
|
||||||
visible: '显示多语言按钮'
|
visible: '显示多语言按钮'
|
||||||
|
},
|
||||||
|
globalSearch: {
|
||||||
|
visible: '显示全局搜索按钮'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
tab: {
|
tab: {
|
||||||
@ -165,6 +168,20 @@ const local: App.I18n.Schema = {
|
|||||||
visible: '显示全屏水印',
|
visible: '显示全屏水印',
|
||||||
text: '水印文本'
|
text: '水印文本'
|
||||||
},
|
},
|
||||||
|
tablePropsTitle: '表格配置',
|
||||||
|
table: {
|
||||||
|
size: {
|
||||||
|
title: '表格大小',
|
||||||
|
small: '小',
|
||||||
|
medium: '中',
|
||||||
|
large: '大'
|
||||||
|
},
|
||||||
|
bordered: '边框',
|
||||||
|
bottomBordered: '底部边框',
|
||||||
|
singleColumn: '设定行的分割线',
|
||||||
|
singleLine: '设定列的分割线',
|
||||||
|
striped: '斑马线条纹'
|
||||||
|
},
|
||||||
themeDrawerTitle: '主题配置',
|
themeDrawerTitle: '主题配置',
|
||||||
pageFunTitle: '页面功能',
|
pageFunTitle: '页面功能',
|
||||||
resetCacheStrategy: {
|
resetCacheStrategy: {
|
||||||
@ -574,12 +591,13 @@ const local: App.I18n.Schema = {
|
|||||||
buttonPermissionList: '按钮权限列表',
|
buttonPermissionList: '按钮权限列表',
|
||||||
emptyMenu: '暂无菜单',
|
emptyMenu: '暂无菜单',
|
||||||
menuDetail: '菜单详情',
|
menuDetail: '菜单详情',
|
||||||
|
cascadeDeleteContent: '级联删除菜单将删除所选中的菜单,是否继续?',
|
||||||
iconifyTip: 'iconify 地址:https://icones.js.org',
|
iconifyTip: 'iconify 地址:https://icones.js.org',
|
||||||
isFrameTip: '选择是外链则路由地址需要以`http(s)://`开头',
|
isFrameTip: '选择是外链则路由地址需要以`http(s)://`开头',
|
||||||
isCacheTip: '选择是则会被`keep-alive`缓存,需要匹配组件的`name`和地址保持一致',
|
isCacheTip: '选择是则会被`keep-alive`缓存,需要匹配组件的`name`和地址保持一致',
|
||||||
visibleTip: '选择隐藏则路由将不会出现在侧边栏,但仍然可以访问',
|
visibleTip: '选择隐藏则路由将不会出现在侧边栏,但仍然可以访问',
|
||||||
statusTip: '选择停用则路由将不会出现在侧边栏,也不能被访问',
|
statusTip: '选择停用则路由将不会出现在侧边栏,也不能被访问',
|
||||||
permsTip: "控制器中定义的权限字符,如:`@SaCheckPermission('system:user:list')`",
|
permsTip: "控制器中定义的权限字符,如:`{'@'}SaCheckPermission('system:user:list')`",
|
||||||
componentTip: '访问的组件路径,如:`system/user/index`,默认在`views`目录下',
|
componentTip: '访问的组件路径,如:`system/user/index`,默认在`views`目录下',
|
||||||
pathTip:
|
pathTip:
|
||||||
'Router path,Example:`user`,If the external network address needs to be accessed in the internal link,then `http(s)://` beginning',
|
'Router path,Example:`user`,If the external network address needs to be accessed in the internal link,then `http(s)://` beginning',
|
||||||
@ -592,6 +610,10 @@ const local: App.I18n.Schema = {
|
|||||||
required: '请选择菜单类型',
|
required: '请选择菜单类型',
|
||||||
invalid: '菜单类型不能为空'
|
invalid: '菜单类型不能为空'
|
||||||
},
|
},
|
||||||
|
menuIds: {
|
||||||
|
required: '请选择菜单',
|
||||||
|
invalid: '菜单不能为空'
|
||||||
|
},
|
||||||
icon: {
|
icon: {
|
||||||
required: '请选择菜单图标',
|
required: '请选择菜单图标',
|
||||||
invalid: '菜单图标不能为空'
|
invalid: '菜单图标不能为空'
|
||||||
@ -653,7 +675,8 @@ const local: App.I18n.Schema = {
|
|||||||
button: '按钮',
|
button: '按钮',
|
||||||
addMenu: '新增菜单',
|
addMenu: '新增菜单',
|
||||||
addChildMenu: '新增子菜单',
|
addChildMenu: '新增子菜单',
|
||||||
editMenu: '编辑菜单'
|
editMenu: '编辑菜单',
|
||||||
|
cascadeDelete: '级联删除菜单'
|
||||||
},
|
},
|
||||||
notice: {
|
notice: {
|
||||||
title: '通知公告列表',
|
title: '通知公告列表',
|
||||||
|
@ -25,8 +25,8 @@ export function setupAppVersionNotification() {
|
|||||||
|
|
||||||
const buildTime = await getHtmlBuildTime();
|
const buildTime = await getHtmlBuildTime();
|
||||||
|
|
||||||
// If build time hasn't changed, no update is needed
|
// If failed to get build time or build time hasn't changed, no update is needed.
|
||||||
if (buildTime === BUILD_TIME) {
|
if (!buildTime || buildTime === BUILD_TIME) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,16 +88,22 @@ export function setupAppVersionNotification() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getHtmlBuildTime() {
|
async function getHtmlBuildTime(): Promise<string | null> {
|
||||||
const baseUrl = import.meta.env.VITE_BASE_URL || '/';
|
const baseUrl = import.meta.env.VITE_BASE_URL || '/';
|
||||||
|
|
||||||
const res = await fetch(`${baseUrl}index.html?time=${Date.now()}`);
|
try {
|
||||||
|
const res = await fetch(`${baseUrl}index.html?time=${Date.now()}`);
|
||||||
|
|
||||||
const html = await res.text();
|
if (!res.ok) {
|
||||||
|
console.error('getHtmlBuildTime error:', res.status, res.statusText);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const match = html.match(/<meta name="buildTime" content="(.*)">/);
|
const html = await res.text();
|
||||||
|
const match = html.match(/<meta name="buildTime" content="(.*)">/);
|
||||||
const buildTime = match?.[1] || '';
|
return match?.[1] || null;
|
||||||
|
} catch (error) {
|
||||||
return buildTime;
|
console.error('getHtmlBuildTime error:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,31 +123,6 @@ const dynamicConstantRoutes: ElegantRoute[] = [
|
|||||||
icon: 'material-symbols:account-circle-full',
|
icon: 'material-symbols:account-circle-full',
|
||||||
hideInMenu: true
|
hideInMenu: true
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'system',
|
|
||||||
path: '/system',
|
|
||||||
component: 'layout.base',
|
|
||||||
meta: {
|
|
||||||
title: 'system',
|
|
||||||
i18nKey: 'route.system',
|
|
||||||
localIcon: 'menu-system',
|
|
||||||
order: 1
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
name: 'system_oss-config',
|
|
||||||
path: '/system/oss-config',
|
|
||||||
component: 'view.system_oss-config',
|
|
||||||
meta: {
|
|
||||||
title: 'system_oss-config',
|
|
||||||
i18nKey: 'route.system_oss-config',
|
|
||||||
constant: true,
|
|
||||||
hideInMenu: true,
|
|
||||||
icon: 'hugeicons:configuration-01'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -59,3 +59,11 @@ export function fetchGetTenantPackageMenuTreeSelect(packageId: CommonType.IdType
|
|||||||
method: 'get'
|
method: 'get'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 级联删除菜单
|
||||||
|
export function fetchCascadeDeleteMenu(menuIds: CommonType.IdType[]) {
|
||||||
|
return request<boolean>({
|
||||||
|
url: `/system/menu/cascade/${menuIds.join(',')}`,
|
||||||
|
method: 'delete'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -80,6 +80,9 @@ export function fetchResetUserPassword(userId: CommonType.IdType, password: stri
|
|||||||
return request<boolean>({
|
return request<boolean>({
|
||||||
url: '/system/user/resetPwd',
|
url: '/system/user/resetPwd',
|
||||||
method: 'put',
|
method: 'put',
|
||||||
|
headers: {
|
||||||
|
isEncrypt: true
|
||||||
|
},
|
||||||
data: { userId, password }
|
data: { userId, password }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { reactive } from 'vue';
|
import { reactive } from 'vue';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
|
import { SetupStoreId } from '@/enum';
|
||||||
|
|
||||||
interface NoticeItem {
|
interface NoticeItem {
|
||||||
title?: string;
|
title?: string;
|
||||||
@ -8,7 +9,7 @@ interface NoticeItem {
|
|||||||
time: string;
|
time: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useNoticeStore = defineStore('notice', () => {
|
export const useNoticeStore = defineStore(SetupStoreId.Notice, () => {
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
notices: [] as NoticeItem[]
|
notices: [] as NoticeItem[]
|
||||||
});
|
});
|
||||||
@ -21,6 +22,10 @@ export const useNoticeStore = defineStore('notice', () => {
|
|||||||
state.notices.splice(state.notices.indexOf(notice), 1);
|
state.notices.splice(state.notices.indexOf(notice), 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const readNotice = (notice: NoticeItem) => {
|
||||||
|
state.notices[state.notices.indexOf(notice)].read = true;
|
||||||
|
};
|
||||||
|
|
||||||
// 实现全部已读
|
// 实现全部已读
|
||||||
const readAll = () => {
|
const readAll = () => {
|
||||||
state.notices.forEach((item: any) => {
|
state.notices.forEach((item: any) => {
|
||||||
@ -31,10 +36,12 @@ export const useNoticeStore = defineStore('notice', () => {
|
|||||||
const clearNotice = () => {
|
const clearNotice = () => {
|
||||||
state.notices = [];
|
state.notices = [];
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
state,
|
state,
|
||||||
addNotice,
|
addNotice,
|
||||||
removeNotice,
|
removeNotice,
|
||||||
|
readNotice,
|
||||||
readAll,
|
readAll,
|
||||||
clearNotice
|
clearNotice
|
||||||
};
|
};
|
||||||
|
@ -100,6 +100,7 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
|
|||||||
authRoutes.value = Array.from(authRoutesMap.values());
|
authRoutes.value = Array.from(authRoutesMap.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line complexity
|
||||||
function parseRouter(route: ElegantConstRoute, parent?: ElegantConstRoute) {
|
function parseRouter(route: ElegantConstRoute, parent?: ElegantConstRoute) {
|
||||||
route.meta = route.meta ? route.meta : { title: route.name };
|
route.meta = route.meta ? route.meta : { title: route.name };
|
||||||
const isLayout = route.component === 'Layout';
|
const isLayout = route.component === 'Layout';
|
||||||
@ -129,6 +130,10 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
|
|||||||
|
|
||||||
// @ts-expect-error no hidden field
|
// @ts-expect-error no hidden field
|
||||||
route.meta.hideInMenu = route.hidden;
|
route.meta.hideInMenu = route.hidden;
|
||||||
|
if (route.meta.hideInMenu && parent) {
|
||||||
|
// @ts-expect-error parent.name is activeMenu type
|
||||||
|
route.meta.activeMenu = parent.name;
|
||||||
|
}
|
||||||
// 是否需要keepAlive
|
// 是否需要keepAlive
|
||||||
route.meta.keepAlive = !route.meta.noCache;
|
route.meta.keepAlive = !route.meta.noCache;
|
||||||
|
|
||||||
|
@ -98,13 +98,22 @@ export const useTabStore = defineStore(SetupStoreId.Tab, () => {
|
|||||||
const removeTabIndex = tabs.value.findIndex(tab => tab.id === tabId);
|
const removeTabIndex = tabs.value.findIndex(tab => tab.id === tabId);
|
||||||
if (removeTabIndex === -1) return;
|
if (removeTabIndex === -1) return;
|
||||||
|
|
||||||
|
const removedTabRouteKey = tabs.value[removeTabIndex].routeKey;
|
||||||
const isRemoveActiveTab = activeTabId.value === tabId;
|
const isRemoveActiveTab = activeTabId.value === tabId;
|
||||||
const nextTab = tabs.value[removeTabIndex + 1] || homeTab.value;
|
const nextTab = tabs.value[removeTabIndex + 1] || homeTab.value;
|
||||||
|
|
||||||
|
// remove tab
|
||||||
tabs.value.splice(removeTabIndex, 1);
|
tabs.value.splice(removeTabIndex, 1);
|
||||||
|
|
||||||
|
// if current tab is removed, then switch to next tab
|
||||||
if (isRemoveActiveTab && nextTab) {
|
if (isRemoveActiveTab && nextTab) {
|
||||||
await switchRouteByTab(nextTab);
|
await switchRouteByTab(nextTab);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reset route cache if cache strategy is close
|
||||||
|
if (themeStore.resetCacheStrategy === 'close') {
|
||||||
|
routeStore.resetRouteCache(removedTabRouteKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** remove active tab */
|
/** remove active tab */
|
||||||
@ -131,9 +140,26 @@ export const useTabStore = defineStore(SetupStoreId.Tab, () => {
|
|||||||
*/
|
*/
|
||||||
async function clearTabs(excludes: string[] = [], clearCache: boolean = false) {
|
async function clearTabs(excludes: string[] = [], clearCache: boolean = false) {
|
||||||
const remainTabIds = [...getFixedTabIds(tabs.value), ...excludes];
|
const remainTabIds = [...getFixedTabIds(tabs.value), ...excludes];
|
||||||
const removedTabsIds = tabs.value.map(tab => tab.id).filter(id => !remainTabIds.includes(id));
|
|
||||||
|
// Identify tabs to be removed and collect their routeKeys if strategy is 'close'
|
||||||
|
const tabsToRemove = tabs.value.filter(tab => !remainTabIds.includes(tab.id));
|
||||||
|
const routeKeysToReset: RouteKey[] = [];
|
||||||
|
|
||||||
|
if (themeStore.resetCacheStrategy === 'close') {
|
||||||
|
for (const tab of tabsToRemove) {
|
||||||
|
routeKeysToReset.push(tab.routeKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const removedTabsIds = tabsToRemove.map(tab => tab.id);
|
||||||
|
|
||||||
|
// If no tabs are actually being removed based on excludes and fixed tabs, exit
|
||||||
|
if (removedTabsIds.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const isRemoveActiveTab = removedTabsIds.includes(activeTabId.value);
|
const isRemoveActiveTab = removedTabsIds.includes(activeTabId.value);
|
||||||
|
// filterTabsByIds returns tabs NOT in removedTabsIds, so these are the tabs that will remain
|
||||||
const updatedTabs = filterTabsByIds(removedTabsIds, tabs.value);
|
const updatedTabs = filterTabsByIds(removedTabsIds, tabs.value);
|
||||||
|
|
||||||
if (clearCache) {
|
if (clearCache) {
|
||||||
@ -152,13 +178,21 @@ export const useTabStore = defineStore(SetupStoreId.Tab, () => {
|
|||||||
|
|
||||||
if (!isRemoveActiveTab) {
|
if (!isRemoveActiveTab) {
|
||||||
update();
|
update();
|
||||||
return;
|
} else {
|
||||||
|
const activeTabCandidate = updatedTabs[updatedTabs.length - 1] || homeTab.value;
|
||||||
|
|
||||||
|
if (activeTabCandidate) {
|
||||||
|
// Ensure there's a tab to switch to
|
||||||
|
await switchRouteByTab(activeTabCandidate);
|
||||||
|
}
|
||||||
|
// Update the tabs array regardless of switch success or if a candidate was found
|
||||||
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
const activeTab = updatedTabs[updatedTabs.length - 1] || homeTab.value;
|
// After tabs are updated and route potentially switched, reset cache for removed tabs
|
||||||
|
for (const routeKey of routeKeysToReset) {
|
||||||
await switchRouteByTab(activeTab);
|
routeStore.resetRouteCache(routeKey);
|
||||||
update();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { routerPushByKey } = useRouterPush();
|
const { routerPushByKey } = useRouterPush();
|
||||||
|
@ -30,6 +30,9 @@ export const themeSettings: App.Theme.ThemeSetting = {
|
|||||||
},
|
},
|
||||||
multilingual: {
|
multilingual: {
|
||||||
visible: true
|
visible: true
|
||||||
|
},
|
||||||
|
globalSearch: {
|
||||||
|
visible: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
tab: {
|
tab: {
|
||||||
@ -57,6 +60,14 @@ export const themeSettings: App.Theme.ThemeSetting = {
|
|||||||
visible: import.meta.env.VITE_WATERMARK === 'Y',
|
visible: import.meta.env.VITE_WATERMARK === 'Y',
|
||||||
text: 'RuoYi-Vue-Plus'
|
text: 'RuoYi-Vue-Plus'
|
||||||
},
|
},
|
||||||
|
table: {
|
||||||
|
bordered: true,
|
||||||
|
bottomBordered: true,
|
||||||
|
singleColumn: false,
|
||||||
|
singleLine: true,
|
||||||
|
size: 'small',
|
||||||
|
striped: false
|
||||||
|
},
|
||||||
tokens: {
|
tokens: {
|
||||||
light: {
|
light: {
|
||||||
colors: {
|
colors: {
|
||||||
|
15
src/typings/api/monitor.api.d.ts
vendored
15
src/typings/api/monitor.api.d.ts
vendored
@ -10,6 +10,9 @@ declare namespace Api {
|
|||||||
* backend api module: "monitor"
|
* backend api module: "monitor"
|
||||||
*/
|
*/
|
||||||
namespace Monitor {
|
namespace Monitor {
|
||||||
|
/** 业务操作类型 */
|
||||||
|
type BusinessType = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';
|
||||||
|
|
||||||
/** oper log */
|
/** oper log */
|
||||||
type OperLog = Common.CommonRecord<{
|
type OperLog = Common.CommonRecord<{
|
||||||
/** 日志主键 */
|
/** 日志主键 */
|
||||||
@ -19,13 +22,13 @@ declare namespace Api {
|
|||||||
/** 系统模块 */
|
/** 系统模块 */
|
||||||
title: string;
|
title: string;
|
||||||
/** 操作类型 */
|
/** 操作类型 */
|
||||||
businessType: number;
|
businessType: Monitor.BusinessType;
|
||||||
/** 方法名称 */
|
/** 方法名称 */
|
||||||
method: string;
|
method: string;
|
||||||
/** 请求方式 */
|
/** 请求方式 */
|
||||||
requestMethod: string;
|
requestMethod: string;
|
||||||
/** 操作类别 */
|
/** 操作类别 */
|
||||||
operatorType: number;
|
operatorType: string;
|
||||||
/** 操作人员 */
|
/** 操作人员 */
|
||||||
operName: string;
|
operName: string;
|
||||||
/** 部门名称 */
|
/** 部门名称 */
|
||||||
@ -41,7 +44,7 @@ declare namespace Api {
|
|||||||
/** 返回参数 */
|
/** 返回参数 */
|
||||||
jsonResult: string;
|
jsonResult: string;
|
||||||
/** 操作状态 */
|
/** 操作状态 */
|
||||||
status: number;
|
status: Common.EnableStatus;
|
||||||
/** 错误消息 */
|
/** 错误消息 */
|
||||||
errorMsg: string;
|
errorMsg: string;
|
||||||
/** 操作时间 */
|
/** 操作时间 */
|
||||||
@ -70,7 +73,7 @@ declare namespace Api {
|
|||||||
/** 客户端 */
|
/** 客户端 */
|
||||||
clientKey: string;
|
clientKey: string;
|
||||||
/** 设备类型 */
|
/** 设备类型 */
|
||||||
deviceType: string;
|
deviceType: System.DeviceType;
|
||||||
/** 登录IP地址 */
|
/** 登录IP地址 */
|
||||||
ipaddr: string;
|
ipaddr: string;
|
||||||
/** 登录地点 */
|
/** 登录地点 */
|
||||||
@ -80,7 +83,7 @@ declare namespace Api {
|
|||||||
/** 操作系统 */
|
/** 操作系统 */
|
||||||
os: string;
|
os: string;
|
||||||
/** 登录状态(0成功 1失败) */
|
/** 登录状态(0成功 1失败) */
|
||||||
status: string;
|
status: Common.EnableStatus;
|
||||||
/** 提示消息 */
|
/** 提示消息 */
|
||||||
msg: string;
|
msg: string;
|
||||||
/** 访问时间 */
|
/** 访问时间 */
|
||||||
@ -149,7 +152,7 @@ declare namespace Api {
|
|||||||
/** 所在部门 */
|
/** 所在部门 */
|
||||||
deptName: string;
|
deptName: string;
|
||||||
/** 设备类型 */
|
/** 设备类型 */
|
||||||
deviceType: string;
|
deviceType: System.DeviceType;
|
||||||
/** 登录时间 */
|
/** 登录时间 */
|
||||||
loginTime: number;
|
loginTime: number;
|
||||||
/** 令牌ID */
|
/** 令牌ID */
|
||||||
|
43
src/typings/api/system.api.d.ts
vendored
43
src/typings/api/system.api.d.ts
vendored
@ -34,7 +34,7 @@ declare namespace Api {
|
|||||||
/** 显示顺序 */
|
/** 显示顺序 */
|
||||||
roleSort: number;
|
roleSort: number;
|
||||||
/** 角色状态(0正常 1停用) */
|
/** 角色状态(0正常 1停用) */
|
||||||
status: string;
|
status: Common.EnableStatus;
|
||||||
/** 是否管理员 */
|
/** 是否管理员 */
|
||||||
superAdmin: boolean;
|
superAdmin: boolean;
|
||||||
}>;
|
}>;
|
||||||
@ -115,7 +115,7 @@ declare namespace Api {
|
|||||||
/** 密码 */
|
/** 密码 */
|
||||||
password: string;
|
password: string;
|
||||||
/** 帐号状态(0正常 1停用) */
|
/** 帐号状态(0正常 1停用) */
|
||||||
status: string;
|
status: Common.EnableStatus;
|
||||||
/** 最后登录IP */
|
/** 最后登录IP */
|
||||||
loginIp: string;
|
loginIp: string;
|
||||||
/** 最后登录时间 */
|
/** 最后登录时间 */
|
||||||
@ -356,7 +356,7 @@ declare namespace Api {
|
|||||||
/** 字典键值 */
|
/** 字典键值 */
|
||||||
dictValue: string;
|
dictValue: string;
|
||||||
/** 是否默认(Y是 N否) */
|
/** 是否默认(Y是 N否) */
|
||||||
isDefault: string;
|
isDefault: Common.YesOrNoStatus;
|
||||||
/** 表格回显样式 */
|
/** 表格回显样式 */
|
||||||
listClass: NaiveUI.ThemeColor;
|
listClass: NaiveUI.ThemeColor;
|
||||||
/** 备注 */
|
/** 备注 */
|
||||||
@ -402,7 +402,7 @@ declare namespace Api {
|
|||||||
/** 邮箱 */
|
/** 邮箱 */
|
||||||
email: string;
|
email: string;
|
||||||
/** 部门状态(0正常 1停用) */
|
/** 部门状态(0正常 1停用) */
|
||||||
status: string;
|
status: Common.EnableStatus;
|
||||||
/** 子部门 */
|
/** 子部门 */
|
||||||
children: Dept[];
|
children: Dept[];
|
||||||
}>;
|
}>;
|
||||||
@ -440,7 +440,7 @@ declare namespace Api {
|
|||||||
/** 显示顺序 */
|
/** 显示顺序 */
|
||||||
postSort: number;
|
postSort: number;
|
||||||
/** 状态(0正常 1停用) */
|
/** 状态(0正常 1停用) */
|
||||||
status: string;
|
status: Common.EnableStatus;
|
||||||
/** 备注 */
|
/** 备注 */
|
||||||
remark: string;
|
remark: string;
|
||||||
}>;
|
}>;
|
||||||
@ -476,7 +476,7 @@ declare namespace Api {
|
|||||||
/** 参数键值 */
|
/** 参数键值 */
|
||||||
configValue: string;
|
configValue: string;
|
||||||
/** 是否内置 */
|
/** 是否内置 */
|
||||||
configType: string;
|
configType: Common.YesOrNoStatus;
|
||||||
/** 备注 */
|
/** 备注 */
|
||||||
remark: string;
|
remark: string;
|
||||||
}>;
|
}>;
|
||||||
@ -523,7 +523,7 @@ declare namespace Api {
|
|||||||
/** 用户数量(-1不限制) */
|
/** 用户数量(-1不限制) */
|
||||||
accountCount: number;
|
accountCount: number;
|
||||||
/** 租户状态(0正常 1停用) */
|
/** 租户状态(0正常 1停用) */
|
||||||
status: string;
|
status: Common.EnableStatus;
|
||||||
/** 删除标志(0代表存在 1代表删除) */
|
/** 删除标志(0代表存在 1代表删除) */
|
||||||
delFlag: string;
|
delFlag: string;
|
||||||
}>;
|
}>;
|
||||||
@ -577,7 +577,7 @@ declare namespace Api {
|
|||||||
/** 菜单树选择项是否关联显示 */
|
/** 菜单树选择项是否关联显示 */
|
||||||
menuCheckStrictly: boolean;
|
menuCheckStrictly: boolean;
|
||||||
/** 状态(0正常 1停用) */
|
/** 状态(0正常 1停用) */
|
||||||
status: string;
|
status: Common.EnableStatus;
|
||||||
/** 删除标志(0代表存在 1代表删除) */
|
/** 删除标志(0代表存在 1代表删除) */
|
||||||
delFlag: string;
|
delFlag: string;
|
||||||
}>;
|
}>;
|
||||||
@ -602,6 +602,9 @@ declare namespace Api {
|
|||||||
/** tenant package select list */
|
/** tenant package select list */
|
||||||
type TenantPackageSelectList = Common.CommonRecord<Pick<TenantPackage, 'packageId' | 'packageName'>>;
|
type TenantPackageSelectList = Common.CommonRecord<Pick<TenantPackage, 'packageId' | 'packageName'>>;
|
||||||
|
|
||||||
|
/** 通知公告类型 */
|
||||||
|
type NoticeType = '1' | '2';
|
||||||
|
|
||||||
/** notice */
|
/** notice */
|
||||||
type Notice = Common.CommonRecord<{
|
type Notice = Common.CommonRecord<{
|
||||||
/** 公告ID */
|
/** 公告ID */
|
||||||
@ -611,11 +614,11 @@ declare namespace Api {
|
|||||||
/** 公告标题 */
|
/** 公告标题 */
|
||||||
noticeTitle: string;
|
noticeTitle: string;
|
||||||
/** 公告类型 */
|
/** 公告类型 */
|
||||||
noticeType: string;
|
noticeType: System.NoticeType;
|
||||||
/** 公告内容 */
|
/** 公告内容 */
|
||||||
noticeContent: string;
|
noticeContent: string;
|
||||||
/** 公告状态 */
|
/** 公告状态 */
|
||||||
status: string;
|
status: Common.EnableStatus;
|
||||||
/** 创建者 */
|
/** 创建者 */
|
||||||
createByName: string;
|
createByName: string;
|
||||||
/** 备注 */
|
/** 备注 */
|
||||||
@ -635,6 +638,12 @@ declare namespace Api {
|
|||||||
/** notice list */
|
/** notice list */
|
||||||
type NoticeList = Api.Common.PaginatingQueryRecord<Notice>;
|
type NoticeList = Api.Common.PaginatingQueryRecord<Notice>;
|
||||||
|
|
||||||
|
/** 授权类型 */
|
||||||
|
type GrantType = 'password' | 'sms' | 'password' | 'email' | 'xcx' | 'social';
|
||||||
|
|
||||||
|
/** 设备类型 */
|
||||||
|
type DeviceType = 'pc' | 'android' | 'ios' | 'xcx';
|
||||||
|
|
||||||
/** client */
|
/** client */
|
||||||
type Client = Common.CommonRecord<{
|
type Client = Common.CommonRecord<{
|
||||||
/** id */
|
/** id */
|
||||||
@ -646,17 +655,17 @@ declare namespace Api {
|
|||||||
/** 客户端秘钥 */
|
/** 客户端秘钥 */
|
||||||
clientSecret: string;
|
clientSecret: string;
|
||||||
/** 授权类型 */
|
/** 授权类型 */
|
||||||
grantType: string;
|
grantType: System.GrantType;
|
||||||
/** 授权类型列表 */
|
/** 授权类型列表 */
|
||||||
grantTypeList: string[];
|
grantTypeList: System.GrantType[];
|
||||||
/** 设备类型 */
|
/** 设备类型 */
|
||||||
deviceType: string;
|
deviceType: System.DeviceType;
|
||||||
/** token活跃超时时间 */
|
/** token活跃超时时间 */
|
||||||
activeTimeout: number;
|
activeTimeout: number;
|
||||||
/** token固定超时 */
|
/** token固定超时 */
|
||||||
timeout: number;
|
timeout: number;
|
||||||
/** 状态 */
|
/** 状态 */
|
||||||
status: string;
|
status: Common.EnableStatus;
|
||||||
/** 删除标志(0代表存在 1代表删除) */
|
/** 删除标志(0代表存在 1代表删除) */
|
||||||
delFlag: string;
|
delFlag: string;
|
||||||
}>;
|
}>;
|
||||||
@ -758,13 +767,13 @@ declare namespace Api {
|
|||||||
/** 自定义域名 */
|
/** 自定义域名 */
|
||||||
domain: string;
|
domain: string;
|
||||||
/** 是否https(Y=是,N=否) */
|
/** 是否https(Y=是,N=否) */
|
||||||
isHttps: Api.Common.YesOrNoStatus;
|
isHttps: Common.YesOrNoStatus;
|
||||||
/** 域 */
|
/** 域 */
|
||||||
region: string;
|
region: string;
|
||||||
/** 桶权限类型 */
|
/** 桶权限类型 */
|
||||||
accessPolicy: Api.System.OssAccessPolicy;
|
accessPolicy: System.OssAccessPolicy;
|
||||||
/** 是否默认(0=是,1=否) */
|
/** 是否默认(0=是,1=否) */
|
||||||
status: Api.Common.EnableStatus;
|
status: Common.EnableStatus;
|
||||||
/** 扩展字段 */
|
/** 扩展字段 */
|
||||||
ext1: string;
|
ext1: string;
|
||||||
/** 备注 */
|
/** 备注 */
|
||||||
|
33
src/typings/app.d.ts
vendored
33
src/typings/app.d.ts
vendored
@ -58,6 +58,10 @@ declare namespace App {
|
|||||||
/** Whether to show the multilingual */
|
/** Whether to show the multilingual */
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
};
|
};
|
||||||
|
globalSearch: {
|
||||||
|
/** Whether to show the GlobalSearch */
|
||||||
|
visible: boolean;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
/** Tab */
|
/** Tab */
|
||||||
tab: {
|
tab: {
|
||||||
@ -109,6 +113,20 @@ declare namespace App {
|
|||||||
/** Watermark text */
|
/** Watermark text */
|
||||||
text: string;
|
text: string;
|
||||||
};
|
};
|
||||||
|
table: {
|
||||||
|
/** Whether to show the table border */
|
||||||
|
bordered: boolean;
|
||||||
|
/** Whether to show the table bottom border */
|
||||||
|
bottomBordered: boolean;
|
||||||
|
/** Whether to show the table single column */
|
||||||
|
singleColumn: boolean;
|
||||||
|
/** Whether to show the table single line */
|
||||||
|
singleLine: boolean;
|
||||||
|
/** Whether to show the table size */
|
||||||
|
size: UnionKey.ThemeTableSize;
|
||||||
|
/** Whether to show the table striped */
|
||||||
|
striped: boolean;
|
||||||
|
};
|
||||||
/** define some theme settings tokens, will transform to css variables */
|
/** define some theme settings tokens, will transform to css variables */
|
||||||
tokens: {
|
tokens: {
|
||||||
light: ThemeSettingToken;
|
light: ThemeSettingToken;
|
||||||
@ -401,6 +419,9 @@ declare namespace App {
|
|||||||
multilingual: {
|
multilingual: {
|
||||||
visible: string;
|
visible: string;
|
||||||
};
|
};
|
||||||
|
globalSearch: {
|
||||||
|
visible: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
tab: {
|
tab: {
|
||||||
visible: string;
|
visible: string;
|
||||||
@ -426,6 +447,15 @@ declare namespace App {
|
|||||||
visible: string;
|
visible: string;
|
||||||
text: string;
|
text: string;
|
||||||
};
|
};
|
||||||
|
tablePropsTitle: string;
|
||||||
|
table: {
|
||||||
|
size: { title: string } & Record<UnionKey.ThemeTableSize, string>;
|
||||||
|
bordered: string;
|
||||||
|
bottomBordered: string;
|
||||||
|
singleColumn: string;
|
||||||
|
singleLine: string;
|
||||||
|
striped: string;
|
||||||
|
};
|
||||||
themeDrawerTitle: string;
|
themeDrawerTitle: string;
|
||||||
pageFunTitle: string;
|
pageFunTitle: string;
|
||||||
resetCacheStrategy: { title: string } & Record<UnionKey.ResetCacheStrategy, string>;
|
resetCacheStrategy: { title: string } & Record<UnionKey.ResetCacheStrategy, string>;
|
||||||
@ -680,6 +710,7 @@ declare namespace App {
|
|||||||
buttonPermissionList: string;
|
buttonPermissionList: string;
|
||||||
emptyMenu: string;
|
emptyMenu: string;
|
||||||
menuDetail: string;
|
menuDetail: string;
|
||||||
|
cascadeDeleteContent: string;
|
||||||
iconifyTip: string;
|
iconifyTip: string;
|
||||||
isFrameTip: string;
|
isFrameTip: string;
|
||||||
isCacheTip: string;
|
isCacheTip: string;
|
||||||
@ -691,6 +722,7 @@ declare namespace App {
|
|||||||
form: {
|
form: {
|
||||||
parentId: FormMsg;
|
parentId: FormMsg;
|
||||||
menuType: FormMsg;
|
menuType: FormMsg;
|
||||||
|
menuIds: FormMsg;
|
||||||
icon: FormMsg;
|
icon: FormMsg;
|
||||||
menuName: FormMsg;
|
menuName: FormMsg;
|
||||||
orderNum: FormMsg;
|
orderNum: FormMsg;
|
||||||
@ -717,6 +749,7 @@ declare namespace App {
|
|||||||
addMenu: string;
|
addMenu: string;
|
||||||
addChildMenu: string;
|
addChildMenu: string;
|
||||||
editMenu: string;
|
editMenu: string;
|
||||||
|
cascadeDelete: string;
|
||||||
};
|
};
|
||||||
notice: {
|
notice: {
|
||||||
title: string;
|
title: string;
|
||||||
|
3
src/typings/components.d.ts
vendored
3
src/typings/components.d.ts
vendored
@ -14,6 +14,7 @@ declare module 'vue' {
|
|||||||
ButtonIcon: typeof import('./../components/custom/button-icon.vue')['default']
|
ButtonIcon: typeof import('./../components/custom/button-icon.vue')['default']
|
||||||
CountTo: typeof import('./../components/custom/count-to.vue')['default']
|
CountTo: typeof import('./../components/custom/count-to.vue')['default']
|
||||||
DarkModeContainer: typeof import('./../components/common/dark-mode-container.vue')['default']
|
DarkModeContainer: typeof import('./../components/common/dark-mode-container.vue')['default']
|
||||||
|
DataTable: typeof import('./../components/common/data-table.vue')['default']
|
||||||
DeptTree: typeof import('./../components/custom/dept-tree.vue')['default']
|
DeptTree: typeof import('./../components/custom/dept-tree.vue')['default']
|
||||||
DeptTreeSelect: typeof import('./../components/custom/dept-tree-select.vue')['default']
|
DeptTreeSelect: typeof import('./../components/custom/dept-tree-select.vue')['default']
|
||||||
DictRadio: typeof import('./../components/custom/dict-radio.vue')['default']
|
DictRadio: typeof import('./../components/custom/dict-radio.vue')['default']
|
||||||
@ -62,6 +63,7 @@ declare module 'vue' {
|
|||||||
NA: typeof import('naive-ui')['NA']
|
NA: typeof import('naive-ui')['NA']
|
||||||
NAlert: typeof import('naive-ui')['NAlert']
|
NAlert: typeof import('naive-ui')['NAlert']
|
||||||
NAvatar: typeof import('naive-ui')['NAvatar']
|
NAvatar: typeof import('naive-ui')['NAvatar']
|
||||||
|
NBadge: typeof import('naive-ui')['NBadge']
|
||||||
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']
|
||||||
@ -81,6 +83,7 @@ declare module 'vue' {
|
|||||||
NDrawerContent: typeof import('naive-ui')['NDrawerContent']
|
NDrawerContent: typeof import('naive-ui')['NDrawerContent']
|
||||||
NDropdown: typeof import('naive-ui')['NDropdown']
|
NDropdown: typeof import('naive-ui')['NDropdown']
|
||||||
NDynamicInput: typeof import('naive-ui')['NDynamicInput']
|
NDynamicInput: typeof import('naive-ui')['NDynamicInput']
|
||||||
|
NEllipsis: typeof import('naive-ui')['NEllipsis']
|
||||||
NEmpty: typeof import('naive-ui')['NEmpty']
|
NEmpty: typeof import('naive-ui')['NEmpty']
|
||||||
NForm: typeof import('naive-ui')['NForm']
|
NForm: typeof import('naive-ui')['NForm']
|
||||||
NFormItem: typeof import('naive-ui')['NFormItem']
|
NFormItem: typeof import('naive-ui')['NFormItem']
|
||||||
|
9
src/typings/union-key.d.ts
vendored
9
src/typings/union-key.d.ts
vendored
@ -51,6 +51,15 @@ declare namespace UnionKey {
|
|||||||
*/
|
*/
|
||||||
type ThemeTabMode = import('@sa/materials').PageTabMode;
|
type ThemeTabMode = import('@sa/materials').PageTabMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The table size
|
||||||
|
*
|
||||||
|
* - small: small size
|
||||||
|
* - medium: medium size
|
||||||
|
* - large: large size
|
||||||
|
*/
|
||||||
|
type ThemeTableSize = 'small' | 'medium' | 'large';
|
||||||
|
|
||||||
/** Unocss animate key */
|
/** Unocss animate key */
|
||||||
type UnoCssAnimateKey =
|
type UnoCssAnimateKey =
|
||||||
| 'pulse'
|
| 'pulse'
|
||||||
|
@ -4,7 +4,7 @@ import { useLoading } from '@sa/hooks';
|
|||||||
import { fetchForceLogout, fetchGetOnlineDeviceList } from '@/service/api/monitor';
|
import { fetchForceLogout, fetchGetOnlineDeviceList } from '@/service/api/monitor';
|
||||||
import { useAppStore } from '@/store/modules/app';
|
import { useAppStore } from '@/store/modules/app';
|
||||||
import { useTable } from '@/hooks/common/table';
|
import { useTable } from '@/hooks/common/table';
|
||||||
import { getBrowserIcon, getOsIcon } from '@/utils/format';
|
import { getBrowserIcon, getOsIcon } from '@/utils/icon-tag-format';
|
||||||
import { $t } from '@/locales';
|
import { $t } from '@/locales';
|
||||||
import ButtonIcon from '@/components/custom/button-icon.vue';
|
import ButtonIcon from '@/components/custom/button-icon.vue';
|
||||||
import SvgIcon from '@/components/custom/svg-icon.vue';
|
import SvgIcon from '@/components/custom/svg-icon.vue';
|
||||||
|
@ -184,11 +184,10 @@ async function handleExport() {
|
|||||||
@refresh="getData"
|
@refresh="getData"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<NDataTable
|
<DataTable
|
||||||
v-model:checked-row-keys="checkedRowKeys"
|
v-model:checked-row-keys="checkedRowKeys"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:data="data"
|
:data="data"
|
||||||
size="small"
|
|
||||||
:flex-height="!appStore.isMobile"
|
:flex-height="!appStore.isMobile"
|
||||||
:scroll-x="962"
|
:scroll-x="962"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
|
@ -209,12 +209,11 @@ function handleExport() {
|
|||||||
</template>
|
</template>
|
||||||
</TableHeaderOperation>
|
</TableHeaderOperation>
|
||||||
</template>
|
</template>
|
||||||
<NDataTable
|
<DataTable
|
||||||
v-model:checked-row-keys="checkedRowKeys"
|
v-model:checked-row-keys="checkedRowKeys"
|
||||||
v-model:expanded-row-keys="expandedRowKeys"
|
v-model:expanded-row-keys="expandedRowKeys"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:data="data"
|
:data="data"
|
||||||
size="small"
|
|
||||||
:flex-height="!appStore.isMobile"
|
:flex-height="!appStore.isMobile"
|
||||||
:scroll-x="962"
|
:scroll-x="962"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
|
@ -11,7 +11,7 @@ import { useAuth } from '@/hooks/business/auth';
|
|||||||
import { useDownload } from '@/hooks/business/download';
|
import { useDownload } from '@/hooks/business/download';
|
||||||
import { useTable, useTableOperate } from '@/hooks/common/table';
|
import { useTable, useTableOperate } from '@/hooks/common/table';
|
||||||
import { useDict } from '@/hooks/business/dict';
|
import { useDict } from '@/hooks/business/dict';
|
||||||
import { getBrowserIcon, getOsIcon } from '@/utils/format';
|
import { getBrowserIcon, getOsIcon } from '@/utils/icon-tag-format';
|
||||||
import DictTag from '@/components/custom/dict-tag.vue';
|
import DictTag from '@/components/custom/dict-tag.vue';
|
||||||
import SvgIcon from '@/components/custom/svg-icon.vue';
|
import SvgIcon from '@/components/custom/svg-icon.vue';
|
||||||
import { $t } from '@/locales';
|
import { $t } from '@/locales';
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { getBrowserIcon, getOsIcon } from '@/utils/format';
|
import { getBrowserIcon, getOsIcon } from '@/utils/icon-tag-format';
|
||||||
import { $t } from '@/locales';
|
import { $t } from '@/locales';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
|
@ -5,7 +5,7 @@ import { useAppStore } from '@/store/modules/app';
|
|||||||
import { useAuth } from '@/hooks/business/auth';
|
import { useAuth } from '@/hooks/business/auth';
|
||||||
import { useTable } from '@/hooks/common/table';
|
import { useTable } from '@/hooks/common/table';
|
||||||
import { useDict } from '@/hooks/business/dict';
|
import { useDict } from '@/hooks/business/dict';
|
||||||
import { getBrowserIcon, getOsIcon } from '@/utils/format';
|
import { getBrowserIcon, getOsIcon } from '@/utils/icon-tag-format';
|
||||||
import ButtonIcon from '@/components/custom/button-icon.vue';
|
import ButtonIcon from '@/components/custom/button-icon.vue';
|
||||||
import DictTag from '@/components/custom/dict-tag.vue';
|
import DictTag from '@/components/custom/dict-tag.vue';
|
||||||
import SvgIcon from '@/components/custom/svg-icon.vue';
|
import SvgIcon from '@/components/custom/svg-icon.vue';
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
import { NDescriptions, NDescriptionsItem, NTag } from 'naive-ui';
|
import { NDescriptions, NDescriptionsItem, NTag } from 'naive-ui';
|
||||||
import { getRequestMethodTagType } from '@/utils/format';
|
import { getRequestMethodTagType } from '@/utils/icon-tag-format';
|
||||||
import { $t } from '@/locales';
|
import { $t } from '@/locales';
|
||||||
import DictTag from '@/components/custom/dict-tag.vue';
|
import DictTag from '@/components/custom/dict-tag.vue';
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ function createDefaultModel(): Model {
|
|||||||
configName: '',
|
configName: '',
|
||||||
configKey: '',
|
configKey: '',
|
||||||
configValue: '',
|
configValue: '',
|
||||||
configType: '',
|
configType: 'Y',
|
||||||
remark: ''
|
remark: ''
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -155,8 +155,13 @@ async function edit(row: TableDataWithIndex<Api.System.Dept>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function addInRow(row: TableDataWithIndex<Api.System.Dept>) {
|
async function addInRow(row: TableDataWithIndex<Api.System.Dept>) {
|
||||||
handleAdd();
|
|
||||||
editingData.value = jsonClone(row);
|
editingData.value = jsonClone(row);
|
||||||
|
handleAdd();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleAddOperate() {
|
||||||
|
editingData.value = null;
|
||||||
|
handleAdd();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -170,7 +175,7 @@ async function addInRow(row: TableDataWithIndex<Api.System.Dept>) {
|
|||||||
:loading="loading"
|
:loading="loading"
|
||||||
:show-add="hasAuth('system:dept:add')"
|
:show-add="hasAuth('system:dept:add')"
|
||||||
:show-delete="false"
|
:show-delete="false"
|
||||||
@add="handleAdd"
|
@add="handleAddOperate"
|
||||||
@refresh="getData"
|
@refresh="getData"
|
||||||
>
|
>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
|
@ -55,7 +55,7 @@ const model: Model = reactive(createDefaultModel());
|
|||||||
|
|
||||||
function createDefaultModel(): Model {
|
function createDefaultModel(): Model {
|
||||||
return {
|
return {
|
||||||
parentId: props.rowData?.deptId,
|
parentId: '',
|
||||||
deptName: '',
|
deptName: '',
|
||||||
deptCategory: '',
|
deptCategory: '',
|
||||||
orderNum: null,
|
orderNum: null,
|
||||||
@ -80,6 +80,7 @@ const rules: Record<RuleKey, App.Global.FormRule> = {
|
|||||||
function handleUpdateModelWhenEdit() {
|
function handleUpdateModelWhenEdit() {
|
||||||
if (props.operateType === 'add') {
|
if (props.operateType === 'add') {
|
||||||
Object.assign(model, createDefaultModel());
|
Object.assign(model, createDefaultModel());
|
||||||
|
model.parentId = props.rowData?.deptId || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.operateType === 'edit' && props.rowData) {
|
if (props.operateType === 'edit' && props.rowData) {
|
||||||
@ -185,7 +186,7 @@ watch(visible, () => {
|
|||||||
<NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
|
<NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
|
||||||
<NDrawerContent :title="title" :native-scrollbar="false" closable>
|
<NDrawerContent :title="title" :native-scrollbar="false" closable>
|
||||||
<NForm ref="formRef" :model="model" :rules="rules">
|
<NForm ref="formRef" :model="model" :rules="rules">
|
||||||
<NFormItem :label="$t('page.system.dept.parentId')" path="parentId">
|
<NFormItem v-if="model.parentId != 0" :label="$t('page.system.dept.parentId')" path="parentId">
|
||||||
<NTreeSelect
|
<NTreeSelect
|
||||||
v-model:value="model.parentId"
|
v-model:value="model.parentId"
|
||||||
:loading="deptLoading"
|
:loading="deptLoading"
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
import { ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import type { DataTableColumns, TreeInst, TreeOption } from 'naive-ui';
|
import type { DataTableColumns, TreeInst, TreeOption } from 'naive-ui';
|
||||||
import { NButton, NDivider, NIcon, NInput, NPopconfirm } from 'naive-ui';
|
import { NButton, NDivider, NIcon, NInput, NPopconfirm } from 'naive-ui';
|
||||||
import { useBoolean, useLoading } from '@sa/hooks';
|
import { useBoolean, useLoading } from '@sa/hooks';
|
||||||
@ -14,6 +14,7 @@ import SvgIcon from '@/components/custom/svg-icon.vue';
|
|||||||
import DictTag from '@/components/custom/dict-tag.vue';
|
import DictTag from '@/components/custom/dict-tag.vue';
|
||||||
import ButtonIcon from '@/components/custom/button-icon.vue';
|
import ButtonIcon from '@/components/custom/button-icon.vue';
|
||||||
import MenuOperateDrawer from './modules/menu-operate-drawer.vue';
|
import MenuOperateDrawer from './modules/menu-operate-drawer.vue';
|
||||||
|
import MenuCascadeDeleteModal from './modules/menu-cascade-delete-modal.vue';
|
||||||
|
|
||||||
useDict('sys_show_hide');
|
useDict('sys_show_hide');
|
||||||
useDict('sys_normal_disable');
|
useDict('sys_normal_disable');
|
||||||
@ -26,6 +27,7 @@ const editingData = ref<Api.System.Menu>();
|
|||||||
const operateType = ref<NaiveUI.TableOperateType>('add');
|
const operateType = ref<NaiveUI.TableOperateType>('add');
|
||||||
const { loading, startLoading, endLoading } = useLoading();
|
const { loading, startLoading, endLoading } = useLoading();
|
||||||
const { bool: drawerVisible, setTrue: openDrawer } = useBoolean();
|
const { bool: drawerVisible, setTrue: openDrawer } = useBoolean();
|
||||||
|
const { bool: cascadeDeleteVisible, setTrue: openCascadeDeleteDrawer } = useBoolean();
|
||||||
const { loading: btnLoading, startLoading: startBtnLoading, endLoading: endBtnLoading } = useLoading();
|
const { loading: btnLoading, startLoading: startBtnLoading, endLoading: endBtnLoading } = useLoading();
|
||||||
/** tree pattern name , use tree search */
|
/** tree pattern name , use tree search */
|
||||||
const name = ref<string>();
|
const name = ref<string>();
|
||||||
@ -36,6 +38,18 @@ const treeData = ref<Api.System.Menu[]>([]);
|
|||||||
const checkedKeys = ref<CommonType.IdType[]>([0]);
|
const checkedKeys = ref<CommonType.IdType[]>([0]);
|
||||||
const expandedKeys = ref<CommonType.IdType[]>([0]);
|
const expandedKeys = ref<CommonType.IdType[]>([0]);
|
||||||
|
|
||||||
|
// 是否为目录类型
|
||||||
|
const isCatalog = computed(() => currentMenu.value?.menuType === 'M');
|
||||||
|
|
||||||
|
// 是否为菜单类型
|
||||||
|
const isMenu = computed(() => currentMenu.value?.menuType === 'C');
|
||||||
|
|
||||||
|
// 外链类型
|
||||||
|
const isExternalType = computed(() => currentMenu.value?.isFrame === '0');
|
||||||
|
|
||||||
|
// iframe类型
|
||||||
|
const isIframeType = computed(() => currentMenu.value?.isFrame === '2');
|
||||||
|
|
||||||
const menuTreeRef = ref<TreeInst>();
|
const menuTreeRef = ref<TreeInst>();
|
||||||
const btnData = ref<Api.System.MenuList>([]);
|
const btnData = ref<Api.System.MenuList>([]);
|
||||||
|
|
||||||
@ -292,10 +306,18 @@ const btnColumns: DataTableColumns<Api.System.Menu> = [
|
|||||||
v-if="hasAuth('system:menu:add')"
|
v-if="hasAuth('system:menu:add')"
|
||||||
size="small"
|
size="small"
|
||||||
icon="material-symbols:add-rounded"
|
icon="material-symbols:add-rounded"
|
||||||
class="h-28px text-icon"
|
class="h-28px text-icon color-primary"
|
||||||
:tooltip-content="$t('page.system.menu.addMenu')"
|
:tooltip-content="$t('page.system.menu.addMenu')"
|
||||||
@click.stop="handleAddMenu(0)"
|
@click.stop="handleAddMenu(0)"
|
||||||
/>
|
/>
|
||||||
|
<ButtonIcon
|
||||||
|
v-if="hasAuth('system:menu:add')"
|
||||||
|
size="small"
|
||||||
|
icon="material-symbols:delete-outline"
|
||||||
|
class="h-28px text-icon color-error"
|
||||||
|
:tooltip-content="$t('page.system.menu.cascadeDelete')"
|
||||||
|
@click.stop="openCascadeDeleteDrawer"
|
||||||
|
/>
|
||||||
<ButtonIcon
|
<ButtonIcon
|
||||||
size="small"
|
size="small"
|
||||||
icon="material-symbols:refresh-rounded"
|
icon="material-symbols:refresh-rounded"
|
||||||
@ -347,7 +369,7 @@ const btnColumns: DataTableColumns<Api.System.Menu> = [
|
|||||||
<template #header-extra>
|
<template #header-extra>
|
||||||
<NSpace>
|
<NSpace>
|
||||||
<NButton
|
<NButton
|
||||||
v-if="currentMenu.menuType === 'M' && hasAuth('system:menu:add')"
|
v-if="isCatalog && hasAuth('system:menu:add')"
|
||||||
size="small"
|
size="small"
|
||||||
ghost
|
ghost
|
||||||
type="primary"
|
type="primary"
|
||||||
@ -391,30 +413,30 @@ const btnColumns: DataTableColumns<Api.System.Menu> = [
|
|||||||
label-class="w-20% min-w-88px"
|
label-class="w-20% min-w-88px"
|
||||||
content-class="w-100px"
|
content-class="w-100px"
|
||||||
>
|
>
|
||||||
<NDescriptionsItem :label="$t('page.system.menu.menuName')">
|
<NDescriptionsItem :label="$t('page.system.menu.menuType')">
|
||||||
<NTag class="m-1" size="small" type="primary">{{ menuTypeRecord[currentMenu.menuType!] }}</NTag>
|
<NTag class="m-1" size="small" type="primary">{{ menuTypeRecord[currentMenu.menuType!] }}</NTag>
|
||||||
</NDescriptionsItem>
|
</NDescriptionsItem>
|
||||||
<NDescriptionsItem :label="$t('page.system.menu.status')">
|
<NDescriptionsItem :label="$t('page.system.menu.status')">
|
||||||
<DictTag size="small" :value="currentMenu.status" dict-code="sys_normal_disable" />
|
<DictTag size="small" :value="currentMenu.status" dict-code="sys_normal_disable" />
|
||||||
</NDescriptionsItem>
|
</NDescriptionsItem>
|
||||||
<NDescriptionsItem :label="$t('page.system.menu.addChildMenu')">
|
<NDescriptionsItem :label="$t('page.system.menu.menuName')">
|
||||||
{{ currentMenu.menuName }}
|
{{ currentMenu.menuName }}
|
||||||
</NDescriptionsItem>
|
</NDescriptionsItem>
|
||||||
<NDescriptionsItem v-if="currentMenu.menuType === 'C'" :label="$t('page.system.menu.component')">
|
<NDescriptionsItem v-if="isMenu" :label="$t('page.system.menu.component')">
|
||||||
{{ currentMenu.component }}
|
{{ currentMenu.component }}
|
||||||
</NDescriptionsItem>
|
</NDescriptionsItem>
|
||||||
<NDescriptionsItem
|
<NDescriptionsItem
|
||||||
:label="currentMenu.isFrame !== '0' ? $t('page.system.menu.path') : $t('page.system.menu.externalPath')"
|
:label="!isExternalType ? $t('page.system.menu.path') : $t('page.system.menu.externalPath')"
|
||||||
>
|
>
|
||||||
{{ currentMenu.path }}
|
{{ currentMenu.path }}
|
||||||
</NDescriptionsItem>
|
</NDescriptionsItem>
|
||||||
<NDescriptionsItem
|
<NDescriptionsItem
|
||||||
v-if="currentMenu.menuType === 'C'"
|
v-if="isMenu && !isExternalType"
|
||||||
:label="currentMenu.isFrame !== '2' ? $t('page.system.menu.query') : $t('page.system.menu.iframeQuery')"
|
:label="!isIframeType ? $t('page.system.menu.query') : $t('page.system.menu.iframeQuery')"
|
||||||
>
|
>
|
||||||
{{ currentMenu.queryParam }}
|
{{ currentMenu.queryParam }}
|
||||||
</NDescriptionsItem>
|
</NDescriptionsItem>
|
||||||
<NDescriptionsItem v-if="currentMenu.menuType !== 'M'" :label="$t('page.system.menu.perms')">
|
<NDescriptionsItem v-if="!isCatalog" :label="$t('page.system.menu.perms')">
|
||||||
{{ currentMenu.perms }}
|
{{ currentMenu.perms }}
|
||||||
</NDescriptionsItem>
|
</NDescriptionsItem>
|
||||||
<NDescriptionsItem :label="$t('page.system.menu.isFrame')">
|
<NDescriptionsItem :label="$t('page.system.menu.isFrame')">
|
||||||
@ -425,7 +447,7 @@ const btnColumns: DataTableColumns<Api.System.Menu> = [
|
|||||||
<NDescriptionsItem :label="$t('page.system.menu.visible')">
|
<NDescriptionsItem :label="$t('page.system.menu.visible')">
|
||||||
<DictTag size="small" :value="currentMenu.visible" dict-code="sys_show_hide" />
|
<DictTag size="small" :value="currentMenu.visible" dict-code="sys_show_hide" />
|
||||||
</NDescriptionsItem>
|
</NDescriptionsItem>
|
||||||
<NDescriptionsItem v-if="currentMenu.menuType === 'C'" :label="$t('page.system.menu.isCache')">
|
<NDescriptionsItem v-if="isMenu" :label="$t('page.system.menu.isCache')">
|
||||||
<NTag v-if="currentMenu.isCache" class="m-1" size="small" :type="tagMap[currentMenu.isCache]">
|
<NTag v-if="currentMenu.isCache" class="m-1" size="small" :type="tagMap[currentMenu.isCache]">
|
||||||
{{ currentMenu.isCache === '0' ? $t('page.system.menu.cache') : $t('page.system.menu.noCache') }}
|
{{ currentMenu.isCache === '0' ? $t('page.system.menu.cache') : $t('page.system.menu.noCache') }}
|
||||||
</NTag>
|
</NTag>
|
||||||
@ -465,6 +487,7 @@ const btnColumns: DataTableColumns<Api.System.Menu> = [
|
|||||||
:menu-type="createType"
|
:menu-type="createType"
|
||||||
@submitted="handleSubmitted"
|
@submitted="handleSubmitted"
|
||||||
/>
|
/>
|
||||||
|
<MenuCascadeDeleteModal v-model:visible="cascadeDeleteVisible" @submitted="handleSubmitted" />
|
||||||
</TableSiderLayout>
|
</TableSiderLayout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
118
src/views/system/menu/modules/menu-cascade-delete-modal.vue
Normal file
118
src/views/system/menu/modules/menu-cascade-delete-modal.vue
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, ref, watch } from 'vue';
|
||||||
|
import { useLoading } from '@sa/hooks';
|
||||||
|
import { fetchCascadeDeleteMenu } from '@/service/api/system';
|
||||||
|
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
|
||||||
|
import { $t } from '@/locales';
|
||||||
|
import MenuTree from '@/components/custom/menu-tree.vue';
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'MenuCascadeDeleteModal'
|
||||||
|
});
|
||||||
|
|
||||||
|
interface Emits {
|
||||||
|
(e: 'submitted'): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const emit = defineEmits<Emits>();
|
||||||
|
|
||||||
|
const visible = defineModel<boolean>('visible', {
|
||||||
|
default: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const menuTreeRef = ref<InstanceType<typeof MenuTree> | null>(null);
|
||||||
|
const menuOptions = ref<Api.System.MenuList>([]);
|
||||||
|
const { loading: menuLoading } = useLoading();
|
||||||
|
|
||||||
|
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||||
|
const { createRequiredRule } = useFormRules();
|
||||||
|
|
||||||
|
type Model = {
|
||||||
|
menuIds: CommonType.IdType[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const model: Model = reactive(createDefaultModel());
|
||||||
|
|
||||||
|
function createDefaultModel(): Model {
|
||||||
|
return {
|
||||||
|
menuIds: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
type RuleKey = Extract<keyof Model, 'menuIds'>;
|
||||||
|
|
||||||
|
const rules: Record<RuleKey, App.Global.FormRule> = {
|
||||||
|
menuIds: createRequiredRule($t('page.system.menu.form.menuIds.invalid'))
|
||||||
|
};
|
||||||
|
|
||||||
|
async function handleUpdateModelWhenEdit() {
|
||||||
|
menuOptions.value = [];
|
||||||
|
Object.assign(model, createDefaultModel());
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeDrawer() {
|
||||||
|
visible.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSubmit() {
|
||||||
|
await validate();
|
||||||
|
window.$dialog?.warning({
|
||||||
|
title: $t('page.system.menu.cascadeDelete'),
|
||||||
|
content: $t('page.system.menu.cascadeDeleteContent'),
|
||||||
|
positiveText: $t('common.delete'),
|
||||||
|
positiveButtonProps: {
|
||||||
|
type: 'error'
|
||||||
|
},
|
||||||
|
negativeText: $t('common.cancel'),
|
||||||
|
onPositiveClick: async () => {
|
||||||
|
const { error } = await fetchCascadeDeleteMenu(model.menuIds);
|
||||||
|
if (error) return;
|
||||||
|
window.$message?.success($t('common.deleteSuccess'));
|
||||||
|
closeDrawer();
|
||||||
|
emit('submitted');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(visible, () => {
|
||||||
|
if (visible.value) {
|
||||||
|
handleUpdateModelWhenEdit();
|
||||||
|
restoreValidation();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NModal
|
||||||
|
v-model:show="visible"
|
||||||
|
:title="$t('page.system.menu.cascadeDelete')"
|
||||||
|
preset="card"
|
||||||
|
:bordered="false"
|
||||||
|
display-directive="show"
|
||||||
|
class="max-w-90% w-500px"
|
||||||
|
@close="closeDrawer"
|
||||||
|
>
|
||||||
|
<NForm ref="formRef" :model="model" :rules="rules">
|
||||||
|
<NFormItem :show-label="false" path="menuIds">
|
||||||
|
<MenuTree
|
||||||
|
v-if="visible"
|
||||||
|
ref="menuTreeRef"
|
||||||
|
v-model:options="menuOptions"
|
||||||
|
v-model:loading="menuLoading"
|
||||||
|
v-model:checked-keys="model.menuIds"
|
||||||
|
:cascade="true"
|
||||||
|
:show-header="false"
|
||||||
|
:immediate="true"
|
||||||
|
/>
|
||||||
|
</NFormItem>
|
||||||
|
</NForm>
|
||||||
|
<template #footer>
|
||||||
|
<NSpace justify="end" :size="16">
|
||||||
|
<NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
|
||||||
|
<NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
|
||||||
|
</NSpace>
|
||||||
|
</template>
|
||||||
|
</NModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
@ -83,8 +83,28 @@ const rules: Record<RuleKey, App.Global.FormRule> = {
|
|||||||
component: createRequiredRule($t('page.system.menu.form.component.invalid'))
|
component: createRequiredRule($t('page.system.menu.form.component.invalid'))
|
||||||
};
|
};
|
||||||
|
|
||||||
const isBtn = computed(() => model.menuType === 'F');
|
// 是否为目录类型
|
||||||
|
const isCatalog = computed(() => model.menuType === 'M');
|
||||||
|
|
||||||
|
// 是否为菜单类型
|
||||||
const isMenu = computed(() => model.menuType === 'C');
|
const isMenu = computed(() => model.menuType === 'C');
|
||||||
|
|
||||||
|
// 是否为按钮类型
|
||||||
|
const isBtn = computed(() => model.menuType === 'F');
|
||||||
|
|
||||||
|
// 外链类型
|
||||||
|
const isExternalType = computed(() => model.isFrame === '0');
|
||||||
|
|
||||||
|
// 内部类型
|
||||||
|
const isInternalType = computed(() => model.isFrame === '1');
|
||||||
|
|
||||||
|
// iframe类型
|
||||||
|
const isIframeType = computed(() => model.isFrame === '2');
|
||||||
|
|
||||||
|
// 本地图标类型
|
||||||
|
const isLocalIcon = computed(() => iconType.value === '2');
|
||||||
|
|
||||||
|
// 本地图标
|
||||||
const localIcons = getLocalMenuIcons();
|
const localIcons = getLocalMenuIcons();
|
||||||
const localIconOptions = localIcons.map<SelectOption>(item => ({
|
const localIconOptions = localIcons.map<SelectOption>(item => ({
|
||||||
label: () => (
|
label: () => (
|
||||||
@ -102,7 +122,7 @@ function handleInitModel() {
|
|||||||
|
|
||||||
if (props.operateType === 'edit' && props.rowData) {
|
if (props.operateType === 'edit' && props.rowData) {
|
||||||
Object.assign(model, props.rowData);
|
Object.assign(model, props.rowData);
|
||||||
if (isMenu.value && model.isFrame === '1') {
|
if (isMenu.value && isInternalType.value) {
|
||||||
model.component = model.component?.slice(0, -6);
|
model.component = model.component?.slice(0, -6);
|
||||||
}
|
}
|
||||||
iconType.value = model.icon?.startsWith('local-icon-') ? '2' : '1';
|
iconType.value = model.icon?.startsWith('local-icon-') ? '2' : '1';
|
||||||
@ -118,6 +138,44 @@ function closeDrawer() {
|
|||||||
visible.value = false;
|
visible.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理路径
|
||||||
|
function processPath(path: string | null | undefined): string {
|
||||||
|
return path?.startsWith('/') ? path.substring(1) : path || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理组件
|
||||||
|
function processComponent(component: string | null | undefined): string {
|
||||||
|
if (isCatalog.value && isInternalType.value) {
|
||||||
|
return 'Layout';
|
||||||
|
}
|
||||||
|
if (isIframeType.value || isExternalType.value) {
|
||||||
|
return 'FrameView';
|
||||||
|
}
|
||||||
|
if (isMenu.value && isInternalType.value) {
|
||||||
|
return component?.endsWith('/index') ? component : `${component || ''}/index`;
|
||||||
|
}
|
||||||
|
return component || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function processQueryParam(queryParam: string | null | undefined): string {
|
||||||
|
// 外链类型不需要查询参数
|
||||||
|
if (isExternalType.value) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 内部链接类型,处理动态参数
|
||||||
|
if (isInternalType.value && queryList.value.length) {
|
||||||
|
return JSON.stringify(Object.fromEntries(queryList.value.map(({ key, value }) => [key, value])));
|
||||||
|
}
|
||||||
|
|
||||||
|
// iframe类型,直接使用原始参数
|
||||||
|
if (isIframeType.value) {
|
||||||
|
return queryParam || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
await validate();
|
await validate();
|
||||||
|
|
||||||
@ -133,77 +191,36 @@ async function handleSubmit() {
|
|||||||
visible: menuVisible,
|
visible: menuVisible,
|
||||||
status,
|
status,
|
||||||
perms,
|
perms,
|
||||||
remark
|
remark,
|
||||||
|
component,
|
||||||
|
queryParam
|
||||||
} = model;
|
} = model;
|
||||||
|
|
||||||
let queryParam = model.queryParam;
|
const payload = {
|
||||||
if (isFrame === '0') {
|
menuName,
|
||||||
queryParam = '';
|
path: processPath(model.path),
|
||||||
} else if (isFrame === '1' && queryList.value.length) {
|
parentId,
|
||||||
const queryObj: { [key: string]: string } = {};
|
orderNum,
|
||||||
queryList.value.forEach(item => (queryObj[item.key] = item.value));
|
queryParam: processQueryParam(queryParam),
|
||||||
queryParam = JSON.stringify(queryObj);
|
isFrame,
|
||||||
}
|
isCache,
|
||||||
|
menuType,
|
||||||
const path = model.path?.startsWith('/') ? model.path?.substring(1) : model.path;
|
visible: menuVisible,
|
||||||
|
status,
|
||||||
let component = model.component;
|
perms,
|
||||||
if (isFrame === '1' && menuType === 'M') {
|
icon,
|
||||||
component = 'Layout';
|
component: processComponent(component),
|
||||||
} else if (isFrame === '2') {
|
remark
|
||||||
component = 'FrameView';
|
};
|
||||||
} else if (isMenu.value && model.isFrame === '1') {
|
|
||||||
component = component?.endsWith('/index') ? component : `${component}/index`;
|
const { error } =
|
||||||
}
|
props.operateType === 'add' ? await fetchCreateMenu(payload) : await fetchUpdateMenu({ ...payload, menuId });
|
||||||
|
|
||||||
// request
|
if (error) {
|
||||||
if (props.operateType === 'add') {
|
return;
|
||||||
const { error } = await fetchCreateMenu({
|
|
||||||
menuName,
|
|
||||||
path,
|
|
||||||
parentId,
|
|
||||||
orderNum,
|
|
||||||
queryParam,
|
|
||||||
isFrame,
|
|
||||||
isCache,
|
|
||||||
menuType,
|
|
||||||
visible: menuVisible,
|
|
||||||
status,
|
|
||||||
perms,
|
|
||||||
icon,
|
|
||||||
component,
|
|
||||||
remark
|
|
||||||
});
|
|
||||||
if (error) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
window.$message?.success($t('common.addSuccess'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.operateType === 'edit') {
|
|
||||||
const { error } = await fetchUpdateMenu({
|
|
||||||
menuId,
|
|
||||||
menuName,
|
|
||||||
path,
|
|
||||||
parentId,
|
|
||||||
orderNum,
|
|
||||||
queryParam,
|
|
||||||
isFrame,
|
|
||||||
isCache,
|
|
||||||
menuType,
|
|
||||||
visible: menuVisible,
|
|
||||||
status,
|
|
||||||
perms,
|
|
||||||
icon,
|
|
||||||
component,
|
|
||||||
remark
|
|
||||||
});
|
|
||||||
if (error) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
window.$message?.success($t('common.updateSuccess'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.$message?.success($t(props.operateType === 'add' ? 'common.addSuccess' : 'common.updateSuccess'));
|
||||||
closeDrawer();
|
closeDrawer();
|
||||||
emit('submitted', menuType!);
|
emit('submitted', menuType!);
|
||||||
}
|
}
|
||||||
@ -224,7 +241,7 @@ function onCreate() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NDrawer v-model:show="visible" display-directive="show" :width="800" class="max-w-90%">
|
<NDrawer v-model:show="visible" display-directive="show" :width="600" class="max-w-90%">
|
||||||
<NDrawerContent :title="drawerTitle" :native-scrollbar="false" closable>
|
<NDrawerContent :title="drawerTitle" :native-scrollbar="false" closable>
|
||||||
<NForm ref="formRef" :model="model" :rules="rules">
|
<NForm ref="formRef" :model="model" :rules="rules">
|
||||||
<NGrid responsive="screen" item-responsive>
|
<NGrid responsive="screen" item-responsive>
|
||||||
@ -238,12 +255,7 @@ function onCreate() {
|
|||||||
:placeholder="$t('page.system.menu.form.parentId.required')"
|
:placeholder="$t('page.system.menu.form.parentId.required')"
|
||||||
/>
|
/>
|
||||||
</NFormItemGi>
|
</NFormItemGi>
|
||||||
<NFormItemGi
|
<NFormItemGi v-if="!isBtn" :span="24" :label="$t('page.system.menu.menuType')" path="menuType">
|
||||||
v-if="model.menuType !== 'F'"
|
|
||||||
:span="24"
|
|
||||||
:label="$t('page.system.menu.menuType')"
|
|
||||||
path="menuType"
|
|
||||||
>
|
|
||||||
<NRadioGroup v-model:value="model.menuType">
|
<NRadioGroup v-model:value="model.menuType">
|
||||||
<NRadioButton
|
<NRadioButton
|
||||||
v-for="item in menuTypeOptions.filter(item => item.value !== 'F')"
|
v-for="item in menuTypeOptions.filter(item => item.value !== 'F')"
|
||||||
@ -253,22 +265,30 @@ function onCreate() {
|
|||||||
/>
|
/>
|
||||||
</NRadioGroup>
|
</NRadioGroup>
|
||||||
</NFormItemGi>
|
</NFormItemGi>
|
||||||
<NFormItemGi :span="24" :label="$t('page.system.menu.menuName')" path="menuName">
|
<NFormItemGi span="24" :label="$t('page.system.menu.menuName')" path="menuName">
|
||||||
<NInput v-model:value="model.menuName" :placeholder="$t('page.system.menu.form.menuName.required')" />
|
<NInput v-model:value="model.menuName" :placeholder="$t('page.system.menu.form.menuName.required')" />
|
||||||
</NFormItemGi>
|
</NFormItemGi>
|
||||||
<NFormItemGi v-if="!isBtn" span="24" :label="$t('page.system.menu.iconType')">
|
<NFormItemGi v-if="!isBtn" span="12" :label="$t('page.system.menu.iconType')">
|
||||||
<NRadioGroup v-model:value="iconType">
|
<NRadioGroup v-model:value="iconType">
|
||||||
<NRadio v-for="item in menuIconTypeOptions" :key="item.value" :value="item.value" :label="item.label" />
|
<NRadio v-for="item in menuIconTypeOptions" :key="item.value" :value="item.value" :label="item.label" />
|
||||||
</NRadioGroup>
|
</NRadioGroup>
|
||||||
</NFormItemGi>
|
</NFormItemGi>
|
||||||
<NFormItemGi v-if="!isBtn" span="24" path="icon">
|
<NFormItemGi v-if="!isBtn" span="12" path="icon">
|
||||||
<template #label>
|
<template #label>
|
||||||
<div class="flex-center">
|
<div class="flex-center">
|
||||||
<FormTip :content="$t('page.system.menu.iconifyTip')" />
|
<FormTip :content="$t('page.system.menu.iconifyTip')" />
|
||||||
<span class="pl-3px">{{ $t('page.system.menu.icon') }}</span>
|
<span class="pl-3px">{{ $t('page.system.menu.icon') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="iconType === '1'">
|
<template v-if="isLocalIcon">
|
||||||
|
<NSelect
|
||||||
|
v-model:value="model.icon"
|
||||||
|
:placeholder="$t('page.system.menu.placeholder.localIconPlaceholder')"
|
||||||
|
filterable
|
||||||
|
:options="localIconOptions"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
<NInput
|
<NInput
|
||||||
v-model:value="model.icon"
|
v-model:value="model.icon"
|
||||||
:placeholder="$t('page.system.menu.placeholder.iconifyIconPlaceholder')"
|
:placeholder="$t('page.system.menu.placeholder.iconifyIconPlaceholder')"
|
||||||
@ -279,27 +299,51 @@ function onCreate() {
|
|||||||
</template>
|
</template>
|
||||||
</NInput>
|
</NInput>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="iconType === '2'">
|
</NFormItemGi>
|
||||||
<NSelect
|
<NFormItemGi v-if="!isBtn" :span="12" path="isFrame">
|
||||||
v-model:value="model.icon"
|
<template #label>
|
||||||
:placeholder="$t('page.system.menu.placeholder.localIconPlaceholder')"
|
<div class="flex-center">
|
||||||
filterable
|
<FormTip :content="$t('page.system.menu.isFrameTip')" />
|
||||||
:options="localIconOptions"
|
<span>{{ $t('page.system.menu.isFrame') }}</span>
|
||||||
/>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<NRadioGroup v-model:value="model.isFrame">
|
||||||
|
<NSpace>
|
||||||
|
<NRadio
|
||||||
|
v-for="option in menuIsFrameOptions"
|
||||||
|
:key="option.value"
|
||||||
|
:value="option.value"
|
||||||
|
:label="option.label"
|
||||||
|
/>
|
||||||
|
</NSpace>
|
||||||
|
</NRadioGroup>
|
||||||
|
</NFormItemGi>
|
||||||
|
<NFormItemGi v-if="isMenu" :span="12" path="isCache">
|
||||||
|
<template #label>
|
||||||
|
<div class="flex-center">
|
||||||
|
<FormTip :content="$t('page.system.menu.isCacheTip')" />
|
||||||
|
<span>{{ $t('page.system.menu.isCache') }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<NRadioGroup v-model:value="model.isCache">
|
||||||
|
<NSpace>
|
||||||
|
<NRadio value="0" label="是" />
|
||||||
|
<NRadio value="1" label="否" />
|
||||||
|
</NSpace>
|
||||||
|
</NRadioGroup>
|
||||||
</NFormItemGi>
|
</NFormItemGi>
|
||||||
<NFormItemGi v-if="!isBtn" :span="24" path="path">
|
<NFormItemGi v-if="!isBtn" :span="24" path="path">
|
||||||
<template #label>
|
<template #label>
|
||||||
<div class="flex-center">
|
<div class="flex-center">
|
||||||
<FormTip :content="$t('page.system.menu.pathTip')" />
|
<FormTip :content="$t('page.system.menu.pathTip')" />
|
||||||
<span>
|
<span>
|
||||||
{{ model.isFrame !== '0' ? $t('page.system.menu.path') : $t('page.system.menu.externalPath') }}
|
{{ !isExternalType ? $t('page.system.menu.path') : $t('page.system.menu.externalPath') }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<NInput v-model:value="model.path" :placeholder="$t('page.system.menu.form.path.required')" />
|
<NInput v-model:value="model.path" :placeholder="$t('page.system.menu.form.path.required')" />
|
||||||
</NFormItemGi>
|
</NFormItemGi>
|
||||||
<NFormItemGi v-if="isMenu && model.isFrame === '1'" :span="24" path="component">
|
<NFormItemGi v-if="isMenu && isInternalType" :span="24" path="component">
|
||||||
<template #label>
|
<template #label>
|
||||||
<div class="flex-center">
|
<div class="flex-center">
|
||||||
<FormTip :content="$t('page.system.menu.componentTip')" />
|
<FormTip :content="$t('page.system.menu.componentTip')" />
|
||||||
@ -313,13 +357,13 @@ function onCreate() {
|
|||||||
</NInputGroup>
|
</NInputGroup>
|
||||||
</NFormItemGi>
|
</NFormItemGi>
|
||||||
<NFormItemGi
|
<NFormItemGi
|
||||||
v-if="isMenu && model.isFrame !== '0'"
|
v-if="isMenu && !isExternalType"
|
||||||
span="24"
|
span="24"
|
||||||
:show-feedback="!queryList.length"
|
:show-feedback="!queryList.length"
|
||||||
:label="model.isFrame !== '2' ? $t('page.system.menu.query') : $t('page.system.menu.iframeQuery')"
|
:label="isInternalType ? $t('page.system.menu.query') : $t('page.system.menu.iframeQuery')"
|
||||||
>
|
>
|
||||||
<NDynamicInput
|
<NDynamicInput
|
||||||
v-if="model.isFrame !== '2'"
|
v-if="isInternalType"
|
||||||
v-model:value="queryList"
|
v-model:value="queryList"
|
||||||
item-style="margin-bottom: 0"
|
item-style="margin-bottom: 0"
|
||||||
:on-create="onCreate"
|
:on-create="onCreate"
|
||||||
@ -369,38 +413,7 @@ function onCreate() {
|
|||||||
</template>
|
</template>
|
||||||
<NInput v-model:value="model.perms" :placeholder="$t('page.system.menu.form.perms.required')" />
|
<NInput v-model:value="model.perms" :placeholder="$t('page.system.menu.form.perms.required')" />
|
||||||
</NFormItemGi>
|
</NFormItemGi>
|
||||||
<NFormItemGi v-if="!isBtn" :span="12" path="isFrame">
|
|
||||||
<template #label>
|
|
||||||
<div class="flex-center">
|
|
||||||
<FormTip :content="$t('page.system.menu.isFrameTip')" />
|
|
||||||
<span>{{ $t('page.system.menu.isFrame') }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<NRadioGroup v-model:value="model.isFrame">
|
|
||||||
<NSpace>
|
|
||||||
<NRadio
|
|
||||||
v-for="option in menuIsFrameOptions"
|
|
||||||
:key="option.value"
|
|
||||||
:value="option.value"
|
|
||||||
:label="option.label"
|
|
||||||
/>
|
|
||||||
</NSpace>
|
|
||||||
</NRadioGroup>
|
|
||||||
</NFormItemGi>
|
|
||||||
<NFormItemGi v-if="isMenu" :span="12" path="isCache">
|
|
||||||
<template #label>
|
|
||||||
<div class="flex-center">
|
|
||||||
<FormTip :content="$t('page.system.menu.isCacheTip')" />
|
|
||||||
<span>{{ $t('page.system.menu.isCache') }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<NRadioGroup v-model:value="model.isCache">
|
|
||||||
<NSpace>
|
|
||||||
<NRadio value="0" label="是" />
|
|
||||||
<NRadio value="1" label="否" />
|
|
||||||
</NSpace>
|
|
||||||
</NRadioGroup>
|
|
||||||
</NFormItemGi>
|
|
||||||
<NFormItemGi v-if="!isBtn" :span="12" :label="$t('page.system.menu.visible')" path="visible">
|
<NFormItemGi v-if="!isBtn" :span="12" :label="$t('page.system.menu.visible')" path="visible">
|
||||||
<template #label>
|
<template #label>
|
||||||
<div class="flex-center">
|
<div class="flex-center">
|
||||||
|
@ -34,7 +34,7 @@ function closeDrawer() {
|
|||||||
|
|
||||||
function handleClose() {
|
function handleClose() {
|
||||||
closeDrawer();
|
closeDrawer();
|
||||||
if (fileUploadRef.value?.refreshList) {
|
if (fileUploadRef.value?.needRelaodData) {
|
||||||
emit('close');
|
emit('close');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -135,7 +135,7 @@ const {
|
|||||||
text
|
text
|
||||||
type="primary"
|
type="primary"
|
||||||
icon="material-symbols:database"
|
icon="material-symbols:database"
|
||||||
tooltipContent="数据范权限"
|
tooltipContent="数据范围权限"
|
||||||
onClick={() => handleDataScope(row)}
|
onClick={() => handleDataScope(row)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from 'vue';
|
import { h, ref, watch } from 'vue';
|
||||||
import type { UploadFileInfo } from 'naive-ui';
|
import type { UploadFileInfo } from 'naive-ui';
|
||||||
import { getToken } from '@/store/modules/auth/shared';
|
import { getToken } from '@/store/modules/auth/shared';
|
||||||
import { useDownload } from '@/hooks/business/download';
|
import { useDownload } from '@/hooks/business/download';
|
||||||
@ -77,7 +77,7 @@ function handleError(options: { file: UploadFileInfo; event?: ProgressEvent }) {
|
|||||||
const responseText = event?.target?.responseText;
|
const responseText = event?.target?.responseText;
|
||||||
const msg = JSON.parse(responseText).msg;
|
const msg = JSON.parse(responseText).msg;
|
||||||
message.value = msg;
|
message.value = msg;
|
||||||
window.$message?.error(msg || $t('common.importFail'));
|
window.$message?.error(() => h('div', { innerHTML: msg || $t('common.importFail') }));
|
||||||
success.value = false;
|
success.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user