mirror of
https://github.com/vbenjs/vue-vben-admin.git
synced 2025-08-27 15:41:32 +08:00
chore: add some notes
This commit is contained in:
31
src/App.vue
31
src/App.vue
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<ConfigProvider :locale="zhCN" :transform-cell-text="transformCellText" v-bind="lockOn">
|
||||
<ConfigProvider v-bind="lockEvent" :locale="zhCN" :transform-cell-text="transformCellText">
|
||||
<router-view />
|
||||
</ConfigProvider>
|
||||
</template>
|
||||
@@ -13,9 +13,8 @@
|
||||
import moment from 'moment';
|
||||
import 'moment/dist/locale/zh-cn';
|
||||
|
||||
import { useConfigProvider, useInitAppConfigStore } from './useApp';
|
||||
import { getConfigProvider, initAppConfigStore } from '/@/setup/Application';
|
||||
import { useLockPage } from '/@/hooks/web/useLockPage';
|
||||
import { useSetting } from '/@/hooks/core/useSetting';
|
||||
|
||||
moment.locale('zh-cn');
|
||||
|
||||
@@ -23,26 +22,22 @@
|
||||
name: 'App',
|
||||
components: { ConfigProvider },
|
||||
setup() {
|
||||
// Initialize application settings
|
||||
useInitAppConfigStore();
|
||||
// Initialize breakpoint monitoring
|
||||
createBreakpointListen();
|
||||
// Get system configuration
|
||||
const { projectSetting } = useSetting();
|
||||
// Get ConfigProvider configuration
|
||||
const { transformCellText } = useConfigProvider();
|
||||
// Initialize vuex internal system configuration
|
||||
initAppConfigStore();
|
||||
|
||||
let lockOn = {};
|
||||
if (projectSetting.lockTime) {
|
||||
// Monitor the mouse or keyboard time, used to recalculate the lock screen time
|
||||
const { on } = useLockPage();
|
||||
lockOn = on;
|
||||
}
|
||||
// Create a global breakpoint monitor
|
||||
createBreakpointListen();
|
||||
|
||||
// Get ConfigProvider configuration
|
||||
const { transformCellText } = getConfigProvider();
|
||||
|
||||
// Create a lock screen monitor
|
||||
const lockEvent = useLockPage();
|
||||
|
||||
return {
|
||||
transformCellText,
|
||||
zhCN,
|
||||
lockOn,
|
||||
lockEvent,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
@@ -5,7 +5,7 @@ enum Api {
|
||||
ACCOUNT_INFO = '/account/getAccountInfo',
|
||||
}
|
||||
|
||||
// 获取个人中心--基础设置内容
|
||||
// Get personal center-basic settings
|
||||
export function accountInfoApi() {
|
||||
return defHttp.request<GetAccountInfoModel>({
|
||||
url: Api.ACCOUNT_INFO,
|
||||
|
@@ -7,7 +7,7 @@ enum Api {
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 根据id获取用户菜单
|
||||
* @description: Get user menu based on id
|
||||
*/
|
||||
export function getMenuListById(params: getMenuListByIdParams) {
|
||||
return defHttp.request<getMenuListByIdParamsResultModel>({
|
||||
|
@@ -11,13 +11,13 @@ export interface RouteItem {
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 获取菜单接口
|
||||
* @description: Get menu interface
|
||||
*/
|
||||
export interface getMenuListByIdParams {
|
||||
id: number | string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 获取菜单返回值
|
||||
* @description: Get menu return value
|
||||
*/
|
||||
export type getMenuListByIdParamsResultModel = RouteItem[];
|
||||
|
@@ -7,7 +7,7 @@ enum Api {
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 上传接口
|
||||
* @description: Upload interface
|
||||
*/
|
||||
export function uploadApi(
|
||||
params: UploadFileParams,
|
||||
|
@@ -1,2 +1,8 @@
|
||||
import type { App } from 'vue';
|
||||
import Authority from './src/index.vue';
|
||||
export default Authority;
|
||||
|
||||
export default (app: App): void => {
|
||||
app.component(Authority.name, Authority);
|
||||
};
|
||||
|
||||
export { Authority };
|
||||
|
@@ -1,17 +1,28 @@
|
||||
<!--
|
||||
* @Author: Vben
|
||||
* @Description:Access control component for fine-grained access control.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { PropType } from 'vue';
|
||||
import { defineComponent, computed, unref } from 'vue';
|
||||
|
||||
import { PermissionModeEnum } from '/@/enums/appEnum';
|
||||
import { RoleEnum } from '/@/enums/roleEnum';
|
||||
|
||||
import { usePermission } from '/@/hooks/web/usePermission';
|
||||
import { appStore } from '/@/store/modules/app';
|
||||
|
||||
import { getSlot } from '/@/utils/helper/tsxHelper';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Authority',
|
||||
props: {
|
||||
// 指定角色可见
|
||||
/**
|
||||
* Specified role is visible
|
||||
* When the permission mode is the role mode, the value value can pass the role value.
|
||||
* When the permission mode is background, the value value can pass the code permission value
|
||||
* @default ''
|
||||
*/
|
||||
value: {
|
||||
type: [Number, Array, String] as PropType<RoleEnum | RoleEnum[] | string | string[]>,
|
||||
default: '',
|
||||
@@ -23,7 +34,7 @@
|
||||
});
|
||||
|
||||
/**
|
||||
* 渲染角色按钮
|
||||
* Render role button
|
||||
*/
|
||||
function renderRoleAuth() {
|
||||
const { value } = props;
|
||||
@@ -34,10 +45,8 @@
|
||||
return hasPermission(value) ? getSlot(slots) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染编码按钮
|
||||
* 这里只判断是否包含,具体实现可以根据项目自行写逻辑
|
||||
*/
|
||||
// Render coding button
|
||||
// Here only judge whether it is included, the specific implementation can be written according to the project logic
|
||||
function renderCodeAuth() {
|
||||
const { value } = props;
|
||||
if (!value) {
|
||||
@@ -49,12 +58,12 @@
|
||||
|
||||
return () => {
|
||||
const mode = unref(getModeRef);
|
||||
// 基于角色渲染
|
||||
// Role-based value control
|
||||
if (mode === PermissionModeEnum.ROLE) {
|
||||
return renderRoleAuth();
|
||||
}
|
||||
|
||||
// 基于后台编码渲染
|
||||
// Based on background role permission control
|
||||
if (mode === PermissionModeEnum.BACK) {
|
||||
return renderCodeAuth();
|
||||
}
|
||||
|
@@ -1,3 +1,7 @@
|
||||
<!--
|
||||
* @Author: Vben
|
||||
* @Description: Arrow component with animation
|
||||
-->
|
||||
<template>
|
||||
<span :class="getClass">
|
||||
<RightOutlined />
|
||||
|
@@ -71,9 +71,6 @@
|
||||
return props.absolute ? props.position : {};
|
||||
});
|
||||
|
||||
/**
|
||||
* @description: 渲染内容
|
||||
*/
|
||||
const renderTitle = () => {
|
||||
const list = props.text;
|
||||
if (isString(list)) {
|
||||
@@ -89,6 +86,7 @@
|
||||
|
||||
return () => {
|
||||
return h(
|
||||
// @ts-ignores
|
||||
Tooltip,
|
||||
{
|
||||
title: h(
|
||||
|
@@ -12,33 +12,19 @@
|
||||
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import { Button } from 'ant-design-vue';
|
||||
// import { extendSlots } from '/@/utils/helper/tsxHelper';
|
||||
// import { useThrottle } from '/@/hooks/core/useThrottle';
|
||||
// import { isFunction } from '/@/utils/is';
|
||||
import Icon from '/@/components/Icon';
|
||||
export default defineComponent({
|
||||
name: 'AButton',
|
||||
inheritAttrs: false,
|
||||
components: { Button, Icon },
|
||||
props: {
|
||||
// 按钮类型
|
||||
type: {
|
||||
type: String as PropType<'primary' | 'default' | 'danger' | 'dashed' | 'link'>,
|
||||
default: 'default',
|
||||
},
|
||||
// 节流防抖类型 throttle debounce
|
||||
// throttle: {
|
||||
// type: String as PropType<'throttle' | 'debounce'>,
|
||||
// default: 'throttle',
|
||||
// },
|
||||
color: {
|
||||
type: String as PropType<'error' | 'warning' | 'success' | ''>,
|
||||
},
|
||||
// // 防抖节流时间
|
||||
// throttleTime: {
|
||||
// type: Number as PropType<number>,
|
||||
// default: 50,
|
||||
// },
|
||||
loading: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false,
|
||||
@@ -58,42 +44,15 @@
|
||||
const getIsCircleBtn = computed(() => {
|
||||
return attrs.shape === 'circle';
|
||||
});
|
||||
// const getListeners = computed(() => {
|
||||
// const { throttle, throttleTime = 0 } = props;
|
||||
// // 是否开启节流防抖
|
||||
// const throttleType = throttle!.toLowerCase();
|
||||
// const isDebounce = throttleType === 'debounce';
|
||||
// const openThrottle = ['throttle', 'debounce'].includes(throttleType) && throttleTime > 0;
|
||||
// if (!openThrottle) {
|
||||
// return {
|
||||
// ...attrs,
|
||||
// };
|
||||
// }
|
||||
|
||||
// const on: {
|
||||
// onClick?: Fn;
|
||||
// } = {};
|
||||
|
||||
// if (attrs.onClick && isFunction(attrs.onClick) && openThrottle) {
|
||||
// const [handler] = useThrottle(attrs.onClick as any, throttleTime!, {
|
||||
// debounce: isDebounce,
|
||||
// immediate: false,
|
||||
// });
|
||||
// on.onClick = handler;
|
||||
// }
|
||||
|
||||
// return {
|
||||
// ...attrs,
|
||||
// ...on,
|
||||
// };
|
||||
// });
|
||||
|
||||
const getColor = computed(() => {
|
||||
const res: string[] = [];
|
||||
const { color, disabled } = props;
|
||||
color && res.push(`ant-btn-${color}`);
|
||||
disabled && res.push('is-disabled');
|
||||
return res;
|
||||
return [
|
||||
{
|
||||
[`ant-btn-${color}`]: !!color,
|
||||
[`is-disabled`]: disabled,
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
const getBindValue = computed((): any => {
|
||||
|
@@ -22,7 +22,8 @@
|
||||
import { defineComponent, reactive, onMounted, ref, toRef, toRefs } from 'vue';
|
||||
|
||||
import { Skeleton } from 'ant-design-vue';
|
||||
import { useTimeoutFn, useIntersectionObserver } from '@vueuse/core';
|
||||
import { useTimeoutFn } from '@vueuse/core';
|
||||
import { useIntersectionObserver } from '/@/hooks/event/useIntersectionObserver';
|
||||
interface State {
|
||||
isInit: boolean;
|
||||
loading: boolean;
|
||||
|
@@ -43,23 +43,22 @@
|
||||
},
|
||||
name: 'CollapseContainer',
|
||||
props: {
|
||||
// 标题
|
||||
title: {
|
||||
type: String as PropType<string>,
|
||||
default: '',
|
||||
},
|
||||
// 是否可以展开
|
||||
// Can it be expanded
|
||||
canExpan: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true,
|
||||
},
|
||||
// 标题右侧温馨提醒
|
||||
// Warm reminder on the right side of the title
|
||||
helpMessage: {
|
||||
type: [Array, String] as PropType<string[] | string>,
|
||||
default: '',
|
||||
},
|
||||
// 展开收缩的时候是否触发window.resize,
|
||||
// 可以适应表格和表单,当表单收缩起来,表格触发resize 自适应高度
|
||||
// Whether to trigger window.resize when expanding and contracting,
|
||||
// Can adapt to tables and forms, when the form shrinks, the form triggers resize to adapt to the height
|
||||
triggerWindowResize: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false,
|
||||
@@ -68,12 +67,12 @@
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false,
|
||||
},
|
||||
// 延时加载
|
||||
// Delayed loading
|
||||
lazy: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false,
|
||||
},
|
||||
// 延时加载时间
|
||||
// Delayed loading time
|
||||
lazyTime: {
|
||||
type: Number as PropType<number>,
|
||||
default: 0,
|
||||
@@ -82,14 +81,14 @@
|
||||
setup(props) {
|
||||
const showRef = ref(true);
|
||||
/**
|
||||
* @description: 处理开展事件
|
||||
* @description: Handling development events
|
||||
*/
|
||||
function handleExpand() {
|
||||
const hasShow = !unref(showRef);
|
||||
showRef.value = hasShow;
|
||||
|
||||
if (props.triggerWindowResize) {
|
||||
// 这里200毫秒是因为展开有动画,
|
||||
// 200 milliseconds here is because the expansion has animation,
|
||||
useTimeoutFn(triggerWindowResize, 200);
|
||||
}
|
||||
}
|
||||
|
@@ -9,25 +9,23 @@ export const props = {
|
||||
type: Object as PropType<Event>,
|
||||
default: null,
|
||||
},
|
||||
// 样式
|
||||
styles: {
|
||||
type: Object as PropType<any>,
|
||||
default: null,
|
||||
},
|
||||
showIcon: {
|
||||
// 是否显示icon
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true,
|
||||
},
|
||||
axis: {
|
||||
// 鼠标右键点击的位置
|
||||
// The position of the right mouse button click
|
||||
type: Object as PropType<Axis>,
|
||||
default() {
|
||||
return { x: 0, y: 0 };
|
||||
},
|
||||
},
|
||||
items: {
|
||||
// 最重要的列表,没有的话直接不显示
|
||||
// The most important list, if not, will not be displayed
|
||||
type: Array as PropType<ContextMenuItem[]>,
|
||||
default() {
|
||||
return [];
|
||||
|
@@ -1,2 +1,2 @@
|
||||
// 对vue-count-to进行改造成支持vue3版本
|
||||
// Transform vue-count-to to support vue3 version
|
||||
export { default as CountTo } from './src/index.vue';
|
||||
|
@@ -14,9 +14,8 @@ export default defineComponent({
|
||||
props: descProps,
|
||||
emits: ['register'],
|
||||
setup(props, { attrs, slots, emit }) {
|
||||
// props来自设置
|
||||
const propsRef = ref<Partial<DescOptions> | null>(null);
|
||||
// 自定义title组件:获得title
|
||||
// Custom title component: get title
|
||||
const getMergeProps = computed(() => {
|
||||
return {
|
||||
...props,
|
||||
@@ -34,19 +33,19 @@ export default defineComponent({
|
||||
});
|
||||
|
||||
/**
|
||||
* @description: 是否使用标题
|
||||
* @description: Whether to use title
|
||||
*/
|
||||
const useWrapper = computed(() => {
|
||||
return !!unref(getMergeProps).title;
|
||||
});
|
||||
|
||||
/**
|
||||
* @description: 获取配置Collapse
|
||||
* @description: Get configuration Collapse
|
||||
*/
|
||||
const getCollapseOptions = computed(
|
||||
(): CollapseContainerOptions => {
|
||||
return {
|
||||
// 默认不能展开
|
||||
// Cannot be expanded by default
|
||||
canExpand: false,
|
||||
...unref(getProps).collapseOptions,
|
||||
};
|
||||
@@ -57,7 +56,7 @@ export default defineComponent({
|
||||
* @description:设置desc
|
||||
*/
|
||||
function setDescProps(descProps: Partial<DescOptions>): void {
|
||||
// 保留上一次的setDrawerProps
|
||||
// Keep the last setDrawerProps
|
||||
const mergeProps = deepMerge(unref(propsRef) || {}, descProps);
|
||||
propsRef.value = cloneDeep(mergeProps);
|
||||
}
|
||||
@@ -68,7 +67,7 @@ export default defineComponent({
|
||||
|
||||
emit('register', methods);
|
||||
|
||||
// 防止换行
|
||||
// Prevent line breaks
|
||||
function renderLabel({ label, labelMinWidth, labelStyle }: DescItem) {
|
||||
if (!labelStyle && !labelMinWidth) {
|
||||
return label;
|
||||
@@ -101,7 +100,6 @@ export default defineComponent({
|
||||
|
||||
const width = contentMinWidth;
|
||||
return (
|
||||
// @ts-ignore
|
||||
<Descriptions.Item label={renderLabel(item)} key={field} span={span}>
|
||||
{() =>
|
||||
contentMinWidth ? (
|
||||
@@ -131,7 +129,7 @@ export default defineComponent({
|
||||
|
||||
const renderContainer = () => {
|
||||
const content = props.useCollapse ? renderDesc() : <div>{renderDesc()}</div>;
|
||||
// 减少dom层级
|
||||
// Reduce the dom level
|
||||
return props.useCollapse ? (
|
||||
<CollapseContainer
|
||||
title={unref(getMergeProps).title}
|
||||
|
@@ -2,7 +2,6 @@ import type { VNode } from 'vue';
|
||||
import type { CollapseContainerOptions } from '/@/components/Container/index';
|
||||
|
||||
export interface DescItem {
|
||||
// 最小宽度
|
||||
labelMinWidth?: number;
|
||||
|
||||
contentMinWidth?: number;
|
||||
@@ -11,7 +10,7 @@ export interface DescItem {
|
||||
|
||||
field: string;
|
||||
label: any;
|
||||
// 和并列
|
||||
// Merge column
|
||||
span?: number;
|
||||
show?: (...arg: any) => boolean;
|
||||
// render
|
||||
@@ -19,10 +18,10 @@ export interface DescItem {
|
||||
}
|
||||
|
||||
export interface DescOptions {
|
||||
// 是否包含collapse组件
|
||||
// Whether to include the collapse component
|
||||
useCollapse?: boolean;
|
||||
/**
|
||||
* item配置
|
||||
* item configuration
|
||||
* @type DescItem
|
||||
*/
|
||||
schema: DescItem[];
|
||||
@@ -32,7 +31,7 @@ export interface DescOptions {
|
||||
*/
|
||||
data: any;
|
||||
/**
|
||||
* 内置的CollapseContainer组件配置
|
||||
* Built-in CollapseContainer component configuration
|
||||
* @type CollapseContainerOptions
|
||||
*/
|
||||
collapseOptions?: CollapseContainerOptions;
|
||||
|
@@ -71,7 +71,7 @@ export default defineComponent({
|
||||
}
|
||||
);
|
||||
|
||||
// 底部按钮自定义实现,
|
||||
// Custom implementation of the bottom button,
|
||||
const getFooterHeight = computed(() => {
|
||||
const { footerHeight, showFooter }: DrawerProps = unref(getProps);
|
||||
if (showFooter && footerHeight) {
|
||||
@@ -80,7 +80,7 @@ export default defineComponent({
|
||||
return `0px`;
|
||||
});
|
||||
|
||||
// 取消事件
|
||||
// Cancel event
|
||||
async function onClose(e: any) {
|
||||
const { closeFunc } = unref(getProps);
|
||||
emit('close', e);
|
||||
@@ -93,7 +93,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
function setDrawerProps(props: Partial<DrawerProps>): void {
|
||||
// 保留上一次的setDrawerProps
|
||||
// Keep the last setDrawerProps
|
||||
propsRef.value = deepMerge(unref(propsRef) || {}, props);
|
||||
if (Reflect.has(props, 'visible')) {
|
||||
visibleRef.value = !!props.visible;
|
||||
|
@@ -1,9 +1,8 @@
|
||||
import type { PropType } from 'vue';
|
||||
// import {DrawerProps} from './types'
|
||||
export const footerProps = {
|
||||
confirmLoading: Boolean as PropType<boolean>,
|
||||
/**
|
||||
* @description: 显示关闭按钮
|
||||
* @description: Show close button
|
||||
*/
|
||||
showCancelBtn: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
@@ -15,7 +14,7 @@ export const footerProps = {
|
||||
default: '关闭',
|
||||
},
|
||||
/**
|
||||
* @description: 显示确认按钮
|
||||
* @description: Show confirmation button
|
||||
*/
|
||||
showOkBtn: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
|
@@ -73,7 +73,7 @@ export interface DrawerProps extends DrawerFooterProps {
|
||||
showDetailBack?: boolean;
|
||||
visible?: boolean;
|
||||
/**
|
||||
* 内置的ScrollContainer组件配置
|
||||
* Built-in ScrollContainer component configuration
|
||||
* @type ScrollContainerOptions
|
||||
*/
|
||||
scrollOptions?: ScrollContainerOptions;
|
||||
|
@@ -22,7 +22,7 @@ import { isFunction } from '/@/utils/is';
|
||||
|
||||
const dataTransferRef = reactive<any>({});
|
||||
/**
|
||||
* @description: 适用于将drawer独立出去,外面调用
|
||||
* @description: Applicable to separate drawer and call outside
|
||||
*/
|
||||
export function useDrawer(): UseDrawerReturnType {
|
||||
if (!getCurrentInstance()) {
|
||||
|
@@ -1,7 +1,5 @@
|
||||
import type { PropType } from 'vue';
|
||||
/**
|
||||
* @description: 基础表格参数配置
|
||||
*/
|
||||
|
||||
export const dropdownProps = {
|
||||
/**
|
||||
* the trigger mode which executes the drop-down action
|
||||
@@ -14,52 +12,6 @@ export const dropdownProps = {
|
||||
return ['contextmenu'];
|
||||
},
|
||||
},
|
||||
|
||||
// /**
|
||||
// * the dropdown menu
|
||||
// * @type () => Menu
|
||||
// */
|
||||
// overlay: {
|
||||
// type: null,
|
||||
// },
|
||||
|
||||
// /**
|
||||
// * Class name of the dropdown root element
|
||||
// * @type string
|
||||
// */
|
||||
// overlayClassName: String,
|
||||
|
||||
// /**
|
||||
// * Style of the dropdown root element
|
||||
// * @type object
|
||||
// */
|
||||
// overlayStyle: Object,
|
||||
|
||||
// /**
|
||||
// * whether the dropdown menu is visible
|
||||
// * @type boolean
|
||||
// */
|
||||
// visible: Boolean,
|
||||
|
||||
// /**
|
||||
// * whether the dropdown menu is disabled
|
||||
// * @type boolean
|
||||
// */
|
||||
// disabled: Boolean,
|
||||
|
||||
// /**
|
||||
// * to set the ontainer of the dropdown menu. The default is to create a div element in body, you can reset it to the scrolling area and make a relative reposition.
|
||||
// * @default () => document.body
|
||||
// * @type Function
|
||||
// */
|
||||
// getPopupContainer: Function,
|
||||
|
||||
// /**
|
||||
// * placement of pop menu: bottomLeft bottomCenter bottomRight topLeft topCenter topRight
|
||||
// * @default 'bottomLeft'
|
||||
// * @type string
|
||||
// */
|
||||
// placement: String,
|
||||
};
|
||||
export const basicDropdownProps = Object.assign({}, dropdownProps, {
|
||||
dropMenuList: {
|
||||
|
@@ -27,7 +27,7 @@
|
||||
<script lang="ts">
|
||||
import type { FormActionType, FormProps, FormSchema } from './types/form';
|
||||
import type { AdvanceState } from './types/hooks';
|
||||
import type { Ref } from 'vue';
|
||||
import type { Ref, WatchStopHandle } from 'vue';
|
||||
import type { ValidateFields } from 'ant-design-vue/lib/form/interface';
|
||||
|
||||
import { defineComponent, reactive, ref, computed, unref, toRef, onMounted, watch } from 'vue';
|
||||
@@ -66,6 +66,7 @@
|
||||
});
|
||||
|
||||
const defaultValueRef = ref<any>({});
|
||||
const isInitedDefaultRef = ref(false);
|
||||
const propsRef = ref<Partial<FormProps>>({});
|
||||
const schemaRef = ref<Nullable<FormSchema[]>>(null);
|
||||
const formElRef = ref<Nullable<FormActionType>>(null);
|
||||
@@ -164,16 +165,19 @@
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
const stopWatch: WatchStopHandle = watch(
|
||||
() => getSchema.value,
|
||||
() => {
|
||||
initDefault();
|
||||
(schema) => {
|
||||
if (unref(isInitedDefaultRef)) {
|
||||
return stopWatch();
|
||||
}
|
||||
if (schema && schema.length) {
|
||||
initDefault();
|
||||
isInitedDefaultRef.value = true;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* @description:设置表单
|
||||
*/
|
||||
function setProps(formProps: Partial<FormProps>): void {
|
||||
const mergeProps = deepMerge(unref(propsRef) || {}, formProps);
|
||||
propsRef.value = mergeProps;
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { Component } from 'vue';
|
||||
/**
|
||||
* 组件列表,在这里注册才可以在表单使用
|
||||
* Component list, register here to use it in the form
|
||||
*/
|
||||
import {
|
||||
Input,
|
||||
|
@@ -114,7 +114,7 @@ export default function ({
|
||||
) {
|
||||
advanceState.hideAdvanceBtn = false;
|
||||
|
||||
// 大于3行默认收起
|
||||
// More than 3 lines collapsed by default
|
||||
} else if (!advanceState.isLoad) {
|
||||
advanceState.isLoad = true;
|
||||
advanceState.isAdvanced = !advanceState.isAdvanced;
|
||||
@@ -124,7 +124,7 @@ export default function ({
|
||||
if (itemColSum > BASIC_COL_LEN) {
|
||||
return { isAdvanced: advanceState.isAdvanced, itemColSum };
|
||||
} else {
|
||||
// 第一行始终显示
|
||||
// The first line is always displayed
|
||||
return { isAdvanced: true, itemColSum };
|
||||
}
|
||||
}
|
||||
|
@@ -62,7 +62,7 @@ export function useFormAction({
|
||||
Object.keys(values).forEach((key) => {
|
||||
const element = values[key];
|
||||
if (element !== undefined && element !== null && fields.includes(key)) {
|
||||
// 时间
|
||||
// time type
|
||||
if (itemIsDateType(key)) {
|
||||
if (Array.isArray(element)) {
|
||||
const arr: any[] = [];
|
||||
@@ -84,7 +84,7 @@ export function useFormAction({
|
||||
// }
|
||||
}
|
||||
/**
|
||||
* @description: 根据字段名删除
|
||||
* @description: Delete based on field name
|
||||
*/
|
||||
function removeSchemaByFiled(fields: string | string[]): void {
|
||||
const schemaList: FormSchema[] = cloneDeep(unref(getSchema));
|
||||
@@ -102,7 +102,7 @@ export function useFormAction({
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 根据字段名删除
|
||||
* @description: Delete based on field name
|
||||
*/
|
||||
function _removeSchemaByFiled(field: string, schemaList: FormSchema[]): void {
|
||||
if (isString(field)) {
|
||||
@@ -114,7 +114,7 @@ export function useFormAction({
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 往某个字段后面插入,如果没有插入最后一个
|
||||
* @description: Insert after a certain field, if not insert the last
|
||||
*/
|
||||
function appendSchemaByField(schema: FormSchema, prefixField?: string) {
|
||||
const schemaList: FormSchema[] = cloneDeep(unref(getSchema));
|
||||
@@ -169,7 +169,7 @@ export function useFormAction({
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 是否是时间
|
||||
* @description: Is it time
|
||||
*/
|
||||
function itemIsDateType(key: string) {
|
||||
return unref(getSchema).some((item) => {
|
||||
@@ -193,7 +193,7 @@ export function useFormAction({
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 表单提交
|
||||
* @description: Form submission
|
||||
*/
|
||||
async function handleSubmit(e?: Event): Promise<void> {
|
||||
e && e.preventDefault();
|
||||
|
@@ -18,7 +18,7 @@ export function useFormValues({
|
||||
getSchema,
|
||||
formModel,
|
||||
}: UseFormValuesContext) {
|
||||
// 处理表单值
|
||||
// Processing form values
|
||||
function handleFormValues(values: any) {
|
||||
if (!isObject(values)) {
|
||||
return {};
|
||||
@@ -37,7 +37,7 @@ export function useFormValues({
|
||||
if (isArray(value) && value[0]._isAMomentObject && value[1]._isAMomentObject) {
|
||||
value = value.map((item) => transformDateFunc(item));
|
||||
}
|
||||
// 去除空格
|
||||
// Remove spaces
|
||||
if (isString(value)) {
|
||||
value = value.trim();
|
||||
}
|
||||
@@ -47,7 +47,7 @@ export function useFormValues({
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 处理时间区间参数
|
||||
* @description: Processing time interval parameters
|
||||
*/
|
||||
function handleRangeTimeValue(values: any) {
|
||||
const fieldMapToTime = unref(fieldMapToTimeRef);
|
||||
|
@@ -31,7 +31,7 @@ export function useItemLabelWidth(schemaItemRef: Ref<FormSchema>, propsRef: Ref<
|
||||
wrapperCol: globWrapperCol,
|
||||
} = unref(propsRef) as any;
|
||||
|
||||
// 如果全局有设置labelWidth, 则所有item使用
|
||||
// If labelWidth is set globally, all items use
|
||||
if ((!globalLabelWidth && !labelWidth && !globalLabelCol) || disabledLabelWidth) {
|
||||
return { labelCol, wrapperCol };
|
||||
}
|
||||
|
@@ -42,57 +42,57 @@ export type UseFormReturnType = [RegisterFn, FormActionType];
|
||||
|
||||
export interface FormProps {
|
||||
// layout?: 'vertical' | 'inline' | 'horizontal';
|
||||
// 表单值
|
||||
// Form value
|
||||
model?: any;
|
||||
// 整个表单所有项宽度
|
||||
// The width of all items in the entire form
|
||||
labelWidth?: number | string;
|
||||
// 重置时提交
|
||||
// Submit form on reset
|
||||
submitOnReset?: boolean;
|
||||
// 整个表单通用Col配置
|
||||
// Col configuration for the entire form
|
||||
labelCol?: Partial<ColEx>;
|
||||
// 整个表单通用Col配置
|
||||
// Col configuration for the entire form
|
||||
wrapperCol?: Partial<ColEx>;
|
||||
|
||||
// 通用col配置
|
||||
// General col configuration
|
||||
baseColProps?: Partial<ColEx>;
|
||||
|
||||
// 表单配置规则
|
||||
// Form configuration rules
|
||||
schemas?: FormSchema[];
|
||||
// 用于合并到动态控制表单项的 函数values
|
||||
// Function values used to merge into dynamic control form items
|
||||
mergeDynamicData?: any;
|
||||
// 紧凑模式,用于搜索表单
|
||||
// Compact mode for search forms
|
||||
compact?: boolean;
|
||||
// 空白行span
|
||||
// Blank line span
|
||||
emptySpan?: number | Partial<ColEx>;
|
||||
// 表单内部组件大小
|
||||
// Internal component size of the form
|
||||
size?: 'default' | 'small' | 'large';
|
||||
// 是否禁用
|
||||
// Whether to disable
|
||||
disabled?: boolean;
|
||||
// 时间区间字段映射成多个
|
||||
// Time interval fields are mapped into multiple
|
||||
fieldMapToTime?: FieldMapToTime;
|
||||
// 自动设置placeholder
|
||||
// Placeholder is set automatically
|
||||
autoSetPlaceHolder?: boolean;
|
||||
// 校验信息是否加入label
|
||||
// Check whether the information is added to the label
|
||||
rulesMessageJoinLabel?: boolean;
|
||||
// 是否显示收起展开按钮
|
||||
// Whether to show collapse and expand buttons
|
||||
showAdvancedButton?: boolean;
|
||||
// 超过指定行数自动收起
|
||||
// Automatically collapse over the specified number of rows
|
||||
autoAdvancedLine?: number;
|
||||
// 是否显示操作按钮
|
||||
// Whether to show the operation button
|
||||
showActionButtonGroup?: boolean;
|
||||
|
||||
// 重置按钮配置
|
||||
// Reset button configuration
|
||||
resetButtonOptions?: Partial<ButtonProps>;
|
||||
|
||||
// 确认按钮配置
|
||||
// Confirm button configuration
|
||||
submitButtonOptions?: Partial<ButtonProps>;
|
||||
|
||||
// 操作列配置
|
||||
// Operation column configuration
|
||||
actionColOptions?: Partial<ColEx>;
|
||||
|
||||
// 显示重置按钮
|
||||
// Show reset button
|
||||
showResetButton?: boolean;
|
||||
// 显示确认按钮
|
||||
// Show confirmation button
|
||||
showSubmitButton?: boolean;
|
||||
|
||||
resetFunc?: () => Promise<void>;
|
||||
@@ -101,27 +101,27 @@ export interface FormProps {
|
||||
colon?: boolean;
|
||||
}
|
||||
export interface FormSchema {
|
||||
// 字段名
|
||||
// Field name
|
||||
field: string;
|
||||
// 内部值更改触发的事件名,默认 change
|
||||
// Event name triggered by internal value change, default change
|
||||
changeEvent?: string;
|
||||
// v-model绑定的变量名 默认 value
|
||||
// Variable name bound to v-model Default value
|
||||
valueField?: string;
|
||||
// 标签名
|
||||
// Label name
|
||||
label: string;
|
||||
// 辅助文本
|
||||
// Auxiliary text
|
||||
subLabel?: string;
|
||||
// 文本右侧帮助文本
|
||||
// Help text on the right side of the text
|
||||
helpMessage?: string | string[];
|
||||
// BaseHelp组件props
|
||||
// BaseHelp component props
|
||||
helpComponentProps?: Partial<HelpComponentProps>;
|
||||
// label宽度,有传的话 itemProps配置的 labelCol 和WrapperCol会失效
|
||||
// Label width, if it is passed, the labelCol and WrapperCol configured by itemProps will be invalid
|
||||
labelWidth?: string | number;
|
||||
// 禁用调有formModel全局设置的labelWidth,自己手动设置 labelCol和wrapperCol
|
||||
// Disable the adjustment of labelWidth with global settings of formModel, and manually set labelCol and wrapperCol by yourself
|
||||
disabledLabelWidth?: boolean;
|
||||
// 组件
|
||||
// render component
|
||||
component: ComponentType;
|
||||
// 组件参数
|
||||
// Component parameters
|
||||
componentProps?:
|
||||
| ((opt: {
|
||||
schema: FormSchema;
|
||||
@@ -130,35 +130,35 @@ export interface FormSchema {
|
||||
formModel: any;
|
||||
}) => any)
|
||||
| object;
|
||||
// 必填
|
||||
// Required
|
||||
required?: boolean;
|
||||
|
||||
// 校验规则
|
||||
// Validation rules
|
||||
rules?: Rule[];
|
||||
// 校验信息是否加入label
|
||||
// Check whether the information is added to the label
|
||||
rulesMessageJoinLabel?: boolean;
|
||||
|
||||
// 参考formModelItem
|
||||
// Reference formModelItem
|
||||
itemProps?: Partial<FormItem>;
|
||||
|
||||
// formModelItem外层的col配置
|
||||
// col configuration outside formModelItem
|
||||
colProps?: Partial<ColEx>;
|
||||
|
||||
// 默认值
|
||||
defaultValue?: any;
|
||||
isAdvanced?: boolean;
|
||||
|
||||
// 配合详情组件
|
||||
// Matching details components
|
||||
span?: number;
|
||||
|
||||
ifShow?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean);
|
||||
|
||||
show?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean);
|
||||
|
||||
// 渲染form-item标签内的内容
|
||||
// Render the content in the form-item tag
|
||||
render?: (renderCallbackParams: RenderCallbackParams) => VNode | VNode[] | string;
|
||||
|
||||
// 渲染 col内容,需要外层包裹 form-item
|
||||
// Rendering col content requires outer wrapper form-item
|
||||
renderColContent?: (renderCallbackParams: RenderCallbackParams) => VNode | VNode[] | string;
|
||||
|
||||
renderComponentContent?:
|
||||
@@ -167,10 +167,10 @@ export interface FormSchema {
|
||||
| VNode[]
|
||||
| string;
|
||||
|
||||
// 自定义slot, 在 from-item内
|
||||
// Custom slot, in from-item
|
||||
slot?: string;
|
||||
|
||||
// 自定义slot,类似renderColContent
|
||||
// Custom slot, similar to renderColContent
|
||||
colSlot?: string;
|
||||
|
||||
dynamicDisabled?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean);
|
||||
@@ -179,16 +179,16 @@ export interface FormSchema {
|
||||
}
|
||||
export interface HelpComponentProps {
|
||||
maxWidth: string;
|
||||
// 是否显示序号
|
||||
// Whether to display the serial number
|
||||
showIndex: boolean;
|
||||
// 文本列表
|
||||
// Text list
|
||||
text: any;
|
||||
// 颜色
|
||||
// colour
|
||||
color: string;
|
||||
// 字体大小
|
||||
// font size
|
||||
fontSize: string;
|
||||
icon: string;
|
||||
absolute: boolean;
|
||||
// 定位
|
||||
// Positioning
|
||||
position: any;
|
||||
}
|
||||
|
@@ -6,7 +6,8 @@ import { Menu } from 'ant-design-vue';
|
||||
import SearchInput from './SearchInput.vue';
|
||||
import MenuContent from './MenuContent';
|
||||
|
||||
import { MenuModeEnum, MenuThemeEnum, MenuTypeEnum } from '/@/enums/menuEnum';
|
||||
import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum';
|
||||
import { ThemeEnum } from '/@/enums/appEnum';
|
||||
|
||||
import { menuStore } from '/@/store/modules/menu';
|
||||
import { appStore } from '/@/store/modules/app';
|
||||
@@ -254,7 +255,7 @@ export default defineComponent({
|
||||
{getSlot(slots, 'header')}
|
||||
<SearchInput
|
||||
class={!props.search ? 'hidden' : ''}
|
||||
theme={props.theme as MenuThemeEnum}
|
||||
theme={props.theme as ThemeEnum}
|
||||
onChange={handleInputChange}
|
||||
onClick={handleInputClick}
|
||||
collapsed={getCollapsedState}
|
||||
|
@@ -11,7 +11,7 @@
|
||||
<script lang="ts">
|
||||
import type { PropType } from 'vue';
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import { MenuThemeEnum } from '/@/enums/menuEnum';
|
||||
import { ThemeEnum } from '/@/enums/appEnum';
|
||||
|
||||
// hook
|
||||
import { useDebounce } from '/@/hooks/core/useDebounce';
|
||||
@@ -25,7 +25,7 @@
|
||||
default: true,
|
||||
},
|
||||
theme: {
|
||||
type: String as PropType<MenuThemeEnum>,
|
||||
type: String as PropType<ThemeEnum>,
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
|
@@ -1,7 +1,8 @@
|
||||
import type { Menu } from '/@/router/types';
|
||||
import type { PropType } from 'vue';
|
||||
|
||||
import { MenuModeEnum, MenuTypeEnum, MenuThemeEnum } from '/@/enums/menuEnum';
|
||||
import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum';
|
||||
import { ThemeEnum } from '/@/enums/appEnum';
|
||||
export const basicProps = {
|
||||
items: {
|
||||
type: Array as PropType<Menu[]>,
|
||||
@@ -40,7 +41,7 @@ export const basicProps = {
|
||||
},
|
||||
theme: {
|
||||
type: String as PropType<string>,
|
||||
default: MenuThemeEnum.DARK,
|
||||
default: ThemeEnum.DARK,
|
||||
},
|
||||
showLogo: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
|
3
src/components/Menu/src/types.d.ts
vendored
3
src/components/Menu/src/types.d.ts
vendored
@@ -1,3 +1,4 @@
|
||||
import { ThemeEnum } from '/@/enums/appEnum';
|
||||
export interface MenuState {
|
||||
// 默认选中的列表
|
||||
defaultSelectedKeys: string[];
|
||||
@@ -6,7 +7,7 @@ export interface MenuState {
|
||||
mode: MenuModeEnum;
|
||||
|
||||
// 主题
|
||||
theme: ComputedRef<MenuThemeEnum> | MenuThemeEnum;
|
||||
theme: ComputedRef<ThemeEnum> | ThemeEnum;
|
||||
|
||||
// 缩进
|
||||
inlineIndent?: number;
|
||||
|
@@ -208,8 +208,8 @@
|
||||
padding: 16px;
|
||||
|
||||
.ant-form {
|
||||
padding: 12px 12px 4px 12px;
|
||||
margin-bottom: 12px;
|
||||
padding: 20px 20px 4px 12px;
|
||||
margin-bottom: 18px;
|
||||
background: #fff;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
@@ -33,7 +33,7 @@ import {
|
||||
Result,
|
||||
Empty,
|
||||
} from 'ant-design-vue';
|
||||
import { getApp } from '/@/useApp';
|
||||
import { getApp } from '/@/setup/Application';
|
||||
|
||||
const compList = [Icon, Button, AntButton.Group, AppFooter];
|
||||
|
||||
|
@@ -16,4 +16,4 @@
|
||||
@page-loading-z-index: 10000;
|
||||
|
||||
// left-menu
|
||||
@app-menu-item-height: 46px;
|
||||
@app-menu-item-height: 44px;
|
||||
|
@@ -15,6 +15,13 @@ export enum ThemeModeEnum {
|
||||
SEMI_DARK = 'semi-dark-mode',
|
||||
}
|
||||
|
||||
// menu theme enum
|
||||
export enum ThemeEnum {
|
||||
DARK = 'dark',
|
||||
|
||||
LIGHT = 'light',
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限模式
|
||||
*/
|
||||
|
@@ -1 +0,0 @@
|
||||
export const MENU_DRAG_STATE = 'MENU_DRAG_STATE';
|
@@ -10,13 +10,6 @@ export enum MenuTypeEnum {
|
||||
TOP_MENU = 'top-menu',
|
||||
}
|
||||
|
||||
// menu theme enum
|
||||
export enum MenuThemeEnum {
|
||||
DARK = 'dark',
|
||||
|
||||
LIGHT = 'light',
|
||||
}
|
||||
|
||||
// 折叠触发器位置
|
||||
export enum TriggerEnum {
|
||||
// 不显示
|
||||
|
@@ -1,4 +0,0 @@
|
||||
export enum PaginationEnum {
|
||||
// Default number of pages
|
||||
DEFAULT_PAGE_SIZE = 20,
|
||||
}
|
@@ -1,6 +0,0 @@
|
||||
export enum ResultEnum {
|
||||
SUCCESS = 0,
|
||||
ERROR = 1,
|
||||
TIMEOUT = 401,
|
||||
TYPE = 'success',
|
||||
}
|
18
src/hooks/core/onMountedOrActivated.ts
Normal file
18
src/hooks/core/onMountedOrActivated.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { nextTick, onMounted, onActivated } from 'vue';
|
||||
|
||||
export function onMountedOrActivated(hook: Fn) {
|
||||
let mounted: boolean;
|
||||
|
||||
onMounted(() => {
|
||||
hook();
|
||||
nextTick(() => {
|
||||
mounted = true;
|
||||
});
|
||||
});
|
||||
|
||||
onActivated(() => {
|
||||
if (mounted) {
|
||||
hook();
|
||||
}
|
||||
});
|
||||
}
|
9
src/hooks/core/useExpose.ts
Normal file
9
src/hooks/core/useExpose.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { getCurrentInstance } from 'vue';
|
||||
|
||||
// expose public api
|
||||
export function useExpose(apis: Record<string, any>) {
|
||||
const instance = getCurrentInstance();
|
||||
if (instance) {
|
||||
Object.assign(instance.proxy, apis);
|
||||
}
|
||||
}
|
47
src/hooks/core/useModel.ts
Normal file
47
src/hooks/core/useModel.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { toRef, Ref, reactive, customRef, SetupContext, watch, UnwrapRef } from 'vue';
|
||||
|
||||
export type ModelProps<U> = Readonly<
|
||||
{ [props: string]: any } & {
|
||||
modelValue?: U;
|
||||
}
|
||||
>;
|
||||
|
||||
export function useModel<T>(
|
||||
props: ModelProps<T>,
|
||||
context: SetupContext,
|
||||
callback?: (val: T | undefined, internalState: { value: UnwrapRef<T | undefined> }) => any
|
||||
) {
|
||||
const outerModel: Ref<T | undefined> = toRef(props, 'modelValue');
|
||||
const internalState = reactive({
|
||||
value: props.modelValue,
|
||||
});
|
||||
|
||||
const internalModel = customRef<UnwrapRef<T> | undefined>((track, trigger) => {
|
||||
return {
|
||||
get() {
|
||||
track();
|
||||
return internalState.value;
|
||||
},
|
||||
set(newVal) {
|
||||
if (internalState.value === newVal) return;
|
||||
internalState.value = newVal;
|
||||
context.emit('update:modelValue', newVal);
|
||||
trigger();
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
watch(outerModel, (val, oldVal) => {
|
||||
if (val === oldVal || val === internalState.value) return;
|
||||
if (callback) {
|
||||
callback(val, internalState);
|
||||
return;
|
||||
}
|
||||
internalState.value = val as UnwrapRef<T> | undefined;
|
||||
});
|
||||
|
||||
return {
|
||||
internalState,
|
||||
internalModel,
|
||||
};
|
||||
}
|
15
src/hooks/core/useRefs.ts
Normal file
15
src/hooks/core/useRefs.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { ref, onBeforeUpdate } from 'vue';
|
||||
|
||||
export function useRefs() {
|
||||
const refs = ref([] as Element[]);
|
||||
|
||||
onBeforeUpdate(() => {
|
||||
refs.value = [];
|
||||
});
|
||||
|
||||
const setRefs = (index: number) => (el: Element) => {
|
||||
refs.value[index] = el;
|
||||
};
|
||||
|
||||
return [refs, setRefs];
|
||||
}
|
@@ -27,7 +27,7 @@ export function throttle<T extends unknown[]>(
|
||||
}
|
||||
let { immediate = false } = options;
|
||||
const { once = false, debounce = false } = options;
|
||||
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
||||
let timeoutId: Nullable<TimeoutHandle>;
|
||||
// Has it been cancelled
|
||||
let cancelled: boolean | null = false;
|
||||
/**
|
||||
@@ -36,7 +36,7 @@ export function throttle<T extends unknown[]>(
|
||||
function clearTimer() {
|
||||
if (timeoutId) {
|
||||
window.clearTimeout(timeoutId);
|
||||
timeoutId = undefined;
|
||||
timeoutId = null;
|
||||
}
|
||||
}
|
||||
/** cancel exec */
|
||||
@@ -63,7 +63,7 @@ export function throttle<T extends unknown[]>(
|
||||
const callNow = !timeoutId;
|
||||
if (callNow) {
|
||||
exec();
|
||||
timeoutId = undefined;
|
||||
timeoutId = null;
|
||||
}
|
||||
} else {
|
||||
debounce && clearTimer();
|
||||
|
20
src/hooks/core/useToggle.ts
Normal file
20
src/hooks/core/useToggle.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { ref, watch, Ref, SetupContext } from 'vue';
|
||||
|
||||
export function useToggle(internalModel: Ref<unknown>, { emit }: SetupContext) {
|
||||
const isActive = ref(!!internalModel.value);
|
||||
const isToggled = ref(false);
|
||||
watch(internalModel, (val) => {
|
||||
isActive.value = !!val;
|
||||
});
|
||||
watch(isActive, (value) => {
|
||||
!!value !== !!internalModel.value && emit('onUpdate:modelValue', value);
|
||||
});
|
||||
function toggleIt() {
|
||||
isToggled.value = !isToggled.value;
|
||||
}
|
||||
return {
|
||||
isActive,
|
||||
toggleIt,
|
||||
isToggled,
|
||||
};
|
||||
}
|
48
src/hooks/event/useIntersectionObserver.ts
Normal file
48
src/hooks/event/useIntersectionObserver.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { Ref, watchEffect, ref } from 'vue';
|
||||
|
||||
interface IntersectionObserverProps {
|
||||
target: Ref<Element | null | undefined>;
|
||||
root?: Ref<Element | null | undefined>;
|
||||
onIntersect: IntersectionObserverCallback;
|
||||
rootMargin?: string;
|
||||
threshold?: number;
|
||||
}
|
||||
|
||||
export function useIntersectionObserver({
|
||||
target,
|
||||
root,
|
||||
onIntersect,
|
||||
rootMargin = '0px',
|
||||
threshold = 0.1,
|
||||
}: IntersectionObserverProps) {
|
||||
let cleanup = () => {};
|
||||
const observer: Ref<Nullable<IntersectionObserver>> = ref(null);
|
||||
const stopEffect = watchEffect(() => {
|
||||
cleanup();
|
||||
|
||||
observer.value = new IntersectionObserver(onIntersect, {
|
||||
root: root ? root.value : null,
|
||||
rootMargin,
|
||||
threshold,
|
||||
});
|
||||
|
||||
const current = target.value;
|
||||
|
||||
current && observer.value.observe(current);
|
||||
|
||||
cleanup = () => {
|
||||
if (observer.value) {
|
||||
observer.value.disconnect();
|
||||
target.value && observer.value.unobserve(target.value);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
observer,
|
||||
stop: () => {
|
||||
cleanup();
|
||||
stopEffect();
|
||||
},
|
||||
};
|
||||
}
|
@@ -1,10 +1,15 @@
|
||||
import { ref, Ref, unref } from 'vue';
|
||||
import { useEventListener } from '/@/hooks/event/useEventListener';
|
||||
import { isServer } from '/@/utils/is';
|
||||
export function useClickOutside<T extends HTMLElement>(
|
||||
containerRef: Ref<T>,
|
||||
onClickOutside: (e: MouseEvent | TouchEvent) => void
|
||||
onClickOutside: (e: MouseEvent | TouchEvent) => void,
|
||||
eventName = 'click'
|
||||
) {
|
||||
if (isServer) return;
|
||||
|
||||
const isTouchRef = ref(false);
|
||||
|
||||
useEventListener({
|
||||
el: document,
|
||||
name: 'touchend',
|
||||
@@ -13,7 +18,7 @@ export function useClickOutside<T extends HTMLElement>(
|
||||
});
|
||||
useEventListener({
|
||||
el: document,
|
||||
name: 'click',
|
||||
name: eventName,
|
||||
listener: handler,
|
||||
options: true,
|
||||
});
|
||||
|
13
src/hooks/web/useHeight.ts
Normal file
13
src/hooks/web/useHeight.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Ref, ref, onMounted, nextTick } from 'vue';
|
||||
import { useRect } from '/@/hooks/web/useRect';
|
||||
export const useHeight = (element: Element | Ref<Element>) => {
|
||||
const height = ref();
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
height.value = useRect(element).height;
|
||||
});
|
||||
});
|
||||
|
||||
return height;
|
||||
};
|
@@ -1,4 +1,4 @@
|
||||
import { onUnmounted, watchEffect } from 'vue';
|
||||
import { computed, onUnmounted, watchEffect } from 'vue';
|
||||
import { useThrottle } from '/@/hooks/core/useThrottle';
|
||||
|
||||
import { appStore } from '/@/store/modules/app';
|
||||
@@ -7,10 +7,11 @@ import { userStore } from '/@/store/modules/user';
|
||||
export function useLockPage() {
|
||||
let timeId: ReturnType<typeof setTimeout>;
|
||||
|
||||
function clear() {
|
||||
function clear(): void {
|
||||
window.clearTimeout(timeId);
|
||||
}
|
||||
function resetCalcLockTimeout() {
|
||||
|
||||
function resetCalcLockTimeout(): void {
|
||||
// not login
|
||||
if (!userStore.getTokenState) {
|
||||
clear();
|
||||
@@ -28,31 +29,37 @@ export function useLockPage() {
|
||||
}, lockTime * 60 * 1000);
|
||||
}
|
||||
|
||||
function lockPage() {
|
||||
function lockPage(): void {
|
||||
appStore.commitLockInfoState({
|
||||
isLock: true,
|
||||
pwd: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
watchEffect((onClean) => {
|
||||
if (userStore.getTokenState) {
|
||||
resetCalcLockTimeout();
|
||||
} else {
|
||||
clear();
|
||||
}
|
||||
onClean(() => {
|
||||
clear();
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
clear();
|
||||
});
|
||||
|
||||
const [keyupFn] = useThrottle(resetCalcLockTimeout, 2000);
|
||||
|
||||
return {
|
||||
registerGlobOnKeyup: keyupFn,
|
||||
registerGlobOnMouseMove: keyupFn,
|
||||
on: {
|
||||
onKeyup: keyupFn,
|
||||
onMousemove: keyupFn,
|
||||
},
|
||||
};
|
||||
return computed(() => {
|
||||
const openLockPage = appStore.getProjectConfig.lockTime;
|
||||
if (openLockPage) {
|
||||
return { onKeyup: keyupFn, onMousemove: keyupFn };
|
||||
} else {
|
||||
clear();
|
||||
return {};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@@ -1,18 +1,24 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import { appStore } from '/@/store/modules/app';
|
||||
import { permissionStore } from '/@/store/modules/permission';
|
||||
import { useTabs } from './useTabs';
|
||||
import { RoleEnum } from '/@/enums/roleEnum';
|
||||
import router, { resetRouter } from '/@/router';
|
||||
import { userStore } from '/@/store/modules/user';
|
||||
import { isArray } from '/@/utils/is';
|
||||
import { RootRoute } from '/@/router/routes';
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
import { PermissionModeEnum } from '/@/enums/appEnum';
|
||||
import { intersection } from 'lodash-es';
|
||||
|
||||
import { useTabs } from './useTabs';
|
||||
|
||||
import router, { resetRouter } from '/@/router';
|
||||
import { RootRoute } from '/@/router/routes';
|
||||
|
||||
import { PermissionModeEnum } from '/@/enums/appEnum';
|
||||
import { RoleEnum } from '/@/enums/roleEnum';
|
||||
|
||||
import { intersection } from 'lodash-es';
|
||||
import { isArray } from '/@/utils/is';
|
||||
|
||||
// User permissions related operations
|
||||
export function usePermission() {
|
||||
/**
|
||||
* 更换权限模式
|
||||
* Change permission mode
|
||||
*/
|
||||
async function togglePermissionMode() {
|
||||
appStore.commitProjectConfigState({
|
||||
@@ -25,6 +31,10 @@ export function usePermission() {
|
||||
// location.reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset and regain authority resource information
|
||||
* @param id
|
||||
*/
|
||||
async function resume(id?: string | number) {
|
||||
resetRouter();
|
||||
const routes = await permissionStore.buildRoutesAction(id);
|
||||
@@ -41,12 +51,12 @@ export function usePermission() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 角色模式下判断是否显示
|
||||
* Determine whether there is permission
|
||||
*/
|
||||
function hasPermission(value?: RoleEnum | RoleEnum[] | string | string[], def = true): boolean {
|
||||
const permMode = appStore.getProjectConfig.permissionMode;
|
||||
if (PermissionModeEnum.ROLE === permMode) {
|
||||
// !不传默认可见
|
||||
// Visible by default
|
||||
if (!value) {
|
||||
return def;
|
||||
}
|
||||
@@ -56,7 +66,7 @@ export function usePermission() {
|
||||
return (intersection(value, userStore.getRoleListState) as RoleEnum[]).length > 0;
|
||||
}
|
||||
if (PermissionModeEnum.BACK === permMode) {
|
||||
// !不传默认可见
|
||||
// Visible by default
|
||||
if (!value) {
|
||||
return def;
|
||||
}
|
||||
@@ -66,17 +76,18 @@ export function usePermission() {
|
||||
}
|
||||
return (intersection(value, allCodeList) as string[]).length > 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新角色
|
||||
* Change roles
|
||||
* @param roles
|
||||
*/
|
||||
async function changeRole(roles: RoleEnum | RoleEnum[]): Promise<void> {
|
||||
if (appStore.getProjectConfig.permissionMode !== PermissionModeEnum.ROLE) {
|
||||
throw new Error('请在配置中将PermissionModeEnum切换为ROLE模式在进行操作!');
|
||||
throw new Error(
|
||||
'Please switch PermissionModeEnum to ROLE mode in the configuration to operate!'
|
||||
);
|
||||
}
|
||||
if (!isArray(roles)) {
|
||||
roles = [roles];
|
||||
@@ -86,10 +97,10 @@ export function usePermission() {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Change menu
|
||||
*/
|
||||
async function changeMenu(id?: string | number) {
|
||||
// 这里传入id是为测试,实际可以不用传,会自动获取登录人的id
|
||||
// TODO The id passed in here is for testing. Actually, you don’t need to pass it. The id of the login person will be automatically obtained.
|
||||
resume(id);
|
||||
}
|
||||
|
||||
|
33
src/hooks/web/useRect.ts
Normal file
33
src/hooks/web/useRect.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Ref, unref } from 'vue';
|
||||
import { isWindow } from '/@/utils/is';
|
||||
|
||||
export const useRect = (elementRef: (Element | Window) | Ref<Element | Window | undefined>) => {
|
||||
const element = unref(elementRef);
|
||||
|
||||
if (isWindow(element)) {
|
||||
const width = element.innerWidth;
|
||||
const height = element.innerHeight;
|
||||
|
||||
return {
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: width,
|
||||
bottom: height,
|
||||
width,
|
||||
height,
|
||||
};
|
||||
}
|
||||
|
||||
if (element && element.getBoundingClientRect) {
|
||||
return element.getBoundingClientRect();
|
||||
}
|
||||
|
||||
return {
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
};
|
||||
};
|
@@ -35,7 +35,7 @@ interface SwitchOptions {
|
||||
}
|
||||
|
||||
interface SelectConfig {
|
||||
options?: SelectOptions;
|
||||
options?: LabelValueOptions;
|
||||
def?: any;
|
||||
disabled?: boolean;
|
||||
handler?: Fn;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { ContentEnum, RouterTransitionEnum } from '/@/enums/appEnum';
|
||||
import { MenuThemeEnum, TopMenuAlignEnum, TriggerEnum } from '/@/enums/menuEnum';
|
||||
import { ContentEnum, RouterTransitionEnum, ThemeEnum } from '/@/enums/appEnum';
|
||||
import { TopMenuAlignEnum, TriggerEnum } from '/@/enums/menuEnum';
|
||||
|
||||
export enum HandlerEnum {
|
||||
CHANGE_LAYOUT,
|
||||
@@ -40,11 +40,11 @@ export enum HandlerEnum {
|
||||
|
||||
export const themeOptions = [
|
||||
{
|
||||
value: MenuThemeEnum.LIGHT,
|
||||
value: ThemeEnum.LIGHT,
|
||||
label: '亮色',
|
||||
},
|
||||
{
|
||||
value: MenuThemeEnum.DARK,
|
||||
value: ThemeEnum.DARK,
|
||||
label: '暗色',
|
||||
},
|
||||
];
|
||||
|
24
src/main.ts
24
src/main.ts
@@ -4,34 +4,40 @@ import router, { setupRouter } from '/@/router';
|
||||
import { setupStore } from '/@/store';
|
||||
import { setupAntd } from '/@/setup/ant-design-vue';
|
||||
import { setupErrorHandle } from '/@/setup/error-handle';
|
||||
import { setupDirectives } from '/@/setup/directives';
|
||||
import { setupGlobDirectives } from '/@/setup/directives';
|
||||
|
||||
import { isDevMode, isProdMode, isUseMock } from '/@/utils/env';
|
||||
import { setupProdMockServer } from '../mock/_createProductionServer';
|
||||
import { setApp } from './useApp';
|
||||
import { setApp } from '/@/setup/Application';
|
||||
|
||||
import App from './App.vue';
|
||||
|
||||
import { isDevMode, isProdMode, isUseMock } from '/@/utils/env';
|
||||
|
||||
import '/@/design/index.less';
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
// ui
|
||||
// Configure component library
|
||||
setupAntd(app);
|
||||
// router
|
||||
|
||||
// Configure routing
|
||||
setupRouter(app);
|
||||
// store
|
||||
|
||||
// Configure vuex store
|
||||
setupStore(app);
|
||||
|
||||
// Directives
|
||||
setupDirectives(app);
|
||||
// Register global directive
|
||||
setupGlobDirectives(app);
|
||||
|
||||
// error-handle
|
||||
// Configure global error handling
|
||||
setupErrorHandle(app);
|
||||
|
||||
// Mount when the route is ready
|
||||
router.isReady().then(() => {
|
||||
app.mount('#app');
|
||||
});
|
||||
|
||||
// The development environment takes effect
|
||||
if (isDevMode()) {
|
||||
app.config.performance = true;
|
||||
window.__APP__ = app;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import type { ProjectConfig } from '/@/types/config';
|
||||
|
||||
import { MenuTypeEnum, MenuThemeEnum, MenuModeEnum, TriggerEnum } from '/@/enums/menuEnum';
|
||||
import { ContentEnum, PermissionModeEnum, RouterTransitionEnum } from '/@/enums/appEnum';
|
||||
import { MenuTypeEnum, MenuModeEnum, TriggerEnum } from '/@/enums/menuEnum';
|
||||
import { ContentEnum, PermissionModeEnum, ThemeEnum, RouterTransitionEnum } from '/@/enums/appEnum';
|
||||
import { primaryColor } from '../../build/config/lessModifyVars';
|
||||
import { isProdMode } from '/@/utils/env';
|
||||
|
||||
@@ -39,7 +39,7 @@ const setting: ProjectConfig = {
|
||||
// 是否显示顶部
|
||||
show: true,
|
||||
// theme
|
||||
theme: MenuThemeEnum.LIGHT,
|
||||
theme: ThemeEnum.LIGHT,
|
||||
// 开启锁屏功能
|
||||
useLockPage: true,
|
||||
// 显示刷新按钮
|
||||
@@ -74,7 +74,7 @@ const setting: ProjectConfig = {
|
||||
// 菜单类型
|
||||
type: MenuTypeEnum.SIDEBAR,
|
||||
// 菜单主题
|
||||
theme: MenuThemeEnum.DARK,
|
||||
theme: ThemeEnum.DARK,
|
||||
// 分割菜单
|
||||
split: false,
|
||||
// 顶部菜单布局
|
||||
|
@@ -1,4 +1,7 @@
|
||||
// Application related functions
|
||||
/**
|
||||
* Application configuration
|
||||
*/
|
||||
|
||||
import type { ProjectConfig } from '/@/types/config';
|
||||
import type { App } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
@@ -18,7 +21,9 @@ import {
|
||||
|
||||
import { appStore } from '/@/store/modules/app';
|
||||
|
||||
// Used to share global app instances
|
||||
let app: App;
|
||||
|
||||
export function setApp(_app: App): void {
|
||||
app = _app;
|
||||
}
|
||||
@@ -27,7 +32,7 @@ export function getApp(): App {
|
||||
return app;
|
||||
}
|
||||
|
||||
// TODO 主题切换
|
||||
// TODO Theme switching
|
||||
export function useThemeMode(mode: ThemeModeEnum) {
|
||||
const modeRef = ref(mode);
|
||||
const html = document.documentElement;
|
||||
@@ -43,7 +48,7 @@ export function useThemeMode(mode: ThemeModeEnum) {
|
||||
}
|
||||
|
||||
// Initial project configuration
|
||||
export function useInitAppConfigStore() {
|
||||
export function initAppConfigStore() {
|
||||
let projCfg: ProjectConfig = getLocal(PROJ_CFG_KEY) as ProjectConfig;
|
||||
if (!projCfg) {
|
||||
projCfg = projectSetting;
|
||||
@@ -67,8 +72,8 @@ export function useInitAppConfigStore() {
|
||||
appStore.commitProjectConfigState(projCfg);
|
||||
}
|
||||
|
||||
// Config Provider
|
||||
export function useConfigProvider() {
|
||||
// antdv Config Provider
|
||||
export function getConfigProvider() {
|
||||
function transformCellText({ text }: { text: string }) {
|
||||
if (isNull(text) || isUnDef(text)) {
|
||||
return ' - ';
|
@@ -1,5 +1,5 @@
|
||||
// Load on demand
|
||||
|
||||
// This module only introduces components globally before login
|
||||
import type { App } from 'vue';
|
||||
|
||||
import {
|
||||
|
@@ -1,14 +0,0 @@
|
||||
@import (reference) '../../design/index.less';
|
||||
|
||||
.app-svg-loading {
|
||||
position: relative;
|
||||
width: auto;
|
||||
|
||||
&__tip {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
font-size: 13px;
|
||||
color: #303133;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
@@ -1,11 +1,16 @@
|
||||
import { Spin } from 'ant-design-vue';
|
||||
import svgImg from '/@/assets/images/loading.svg';
|
||||
|
||||
import './spin.less';
|
||||
Spin.setDefaultIndicator({
|
||||
indicator: () => {
|
||||
return (
|
||||
<div class="app-svg-loading">
|
||||
<div
|
||||
class="app-svg-loading"
|
||||
style={{
|
||||
position: 'relative',
|
||||
width: 'auto',
|
||||
}}
|
||||
>
|
||||
<img src={svgImg} alt="" height="32" width="32" class="g-loading" />
|
||||
</div>
|
||||
);
|
||||
|
86
src/setup/application.ts
Normal file
86
src/setup/application.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Application configuration
|
||||
*/
|
||||
|
||||
import type { ProjectConfig } from '/@/types/config';
|
||||
import type { App } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { ThemeModeEnum } from '/@/enums/appEnum';
|
||||
import { PROJ_CFG_KEY } from '/@/enums/cacheEnum';
|
||||
|
||||
import projectSetting from '/@/settings/projectSetting';
|
||||
import { getLocal } from '/@/utils/helper/persistent';
|
||||
import { isUnDef, isNull } from '/@/utils/is';
|
||||
import {
|
||||
updateGrayMode,
|
||||
updateColorWeak,
|
||||
updateHeaderBgColor,
|
||||
updateSidebarBgColor,
|
||||
} from '/@/setup/theme';
|
||||
|
||||
import { appStore } from '/@/store/modules/app';
|
||||
|
||||
// Used to share global app instances
|
||||
let app: App;
|
||||
|
||||
export function setApp(_app: App): void {
|
||||
app = _app;
|
||||
}
|
||||
|
||||
export function getApp(): App {
|
||||
return app;
|
||||
}
|
||||
|
||||
// TODO Theme switching
|
||||
export function useThemeMode(mode: ThemeModeEnum) {
|
||||
const modeRef = ref(mode);
|
||||
const html = document.documentElement;
|
||||
const clsList = html.classList;
|
||||
|
||||
const change = () => {
|
||||
clsList.contains(mode) ? clsList.remove(mode) : clsList.add(mode);
|
||||
};
|
||||
return {
|
||||
runChangeThemeMode: change,
|
||||
mode: computed(() => modeRef.value),
|
||||
};
|
||||
}
|
||||
|
||||
// Initial project configuration
|
||||
export function initAppConfigStore() {
|
||||
let projCfg: ProjectConfig = getLocal(PROJ_CFG_KEY) as ProjectConfig;
|
||||
if (!projCfg) {
|
||||
projCfg = projectSetting;
|
||||
}
|
||||
const { colorWeak, grayMode, headerBgColor, menuBgColor } = projCfg;
|
||||
try {
|
||||
// if (
|
||||
// themeColor !== primaryColor &&
|
||||
// themeColor &&
|
||||
// process.env.VUE_APP_USE_THEME_REPLACER !== 'TRUE'
|
||||
// ) {
|
||||
// updateTheme(themeColor);
|
||||
// }
|
||||
headerBgColor && updateHeaderBgColor(headerBgColor);
|
||||
menuBgColor && updateSidebarBgColor(menuBgColor);
|
||||
grayMode && updateGrayMode(grayMode);
|
||||
colorWeak && updateColorWeak(colorWeak);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
appStore.commitProjectConfigState(projCfg);
|
||||
}
|
||||
|
||||
// antdv Config Provider
|
||||
export function getConfigProvider() {
|
||||
function transformCellText({ text }: { text: string }) {
|
||||
if (isNull(text) || isUnDef(text)) {
|
||||
return ' - ';
|
||||
}
|
||||
return text;
|
||||
}
|
||||
return {
|
||||
transformCellText,
|
||||
};
|
||||
}
|
@@ -1,5 +1,9 @@
|
||||
/**
|
||||
* Configure and register global directives
|
||||
*/
|
||||
import type { App } from 'vue';
|
||||
import { setupPermissionDirective } from './permission';
|
||||
export function setupDirectives(app: App) {
|
||||
|
||||
export function setupGlobDirectives(app: App) {
|
||||
setupPermissionDirective(app);
|
||||
}
|
||||
|
@@ -1,5 +1,11 @@
|
||||
/**
|
||||
* Global authority directive
|
||||
* Used for fine-grained control of component permissions
|
||||
* @Example v-auth="RoleEnum.TEST"
|
||||
*/
|
||||
import type { App, Directive, DirectiveBinding } from 'vue';
|
||||
|
||||
import { appStore } from '/@/store/modules/app';
|
||||
import type { App } from 'vue';
|
||||
import { usePermission } from '/@/hooks/web/usePermission';
|
||||
import { PermissionModeEnum } from '/@/enums/appEnum';
|
||||
const { hasPermission } = usePermission();
|
||||
@@ -13,18 +19,28 @@ function isAuth(el: Element, binding: any) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isBackMode() {
|
||||
return appStore.getProjectConfig.permissionMode === PermissionModeEnum.BACK;
|
||||
}
|
||||
|
||||
const mounted = (el: Element, binding: DirectiveBinding<any>) => {
|
||||
if (isBackMode()) return;
|
||||
isAuth(el, binding);
|
||||
};
|
||||
|
||||
const updated = (el: Element, binding: DirectiveBinding<any>) => {
|
||||
if (!isBackMode()) return;
|
||||
isAuth(el, binding);
|
||||
};
|
||||
|
||||
const authDirective: Directive = {
|
||||
mounted,
|
||||
updated,
|
||||
};
|
||||
|
||||
export function setupPermissionDirective(app: App) {
|
||||
app.directive('auth', {
|
||||
mounted(el: Element, binding) {
|
||||
if (isBackMode()) return;
|
||||
isAuth(el, binding);
|
||||
},
|
||||
updated(el: Element, binding) {
|
||||
if (!isBackMode()) return;
|
||||
isAuth(el, binding);
|
||||
},
|
||||
});
|
||||
app.directive('auth', authDirective);
|
||||
}
|
||||
|
||||
export default authDirective;
|
||||
|
@@ -1,11 +1,16 @@
|
||||
/**
|
||||
* Prevent repeated clicks
|
||||
* @Example v-repeat-click="()=>{}"
|
||||
*/
|
||||
import { on, once } from '/@/utils/domUtils';
|
||||
import type { Directive, DirectiveBinding } from 'vue';
|
||||
|
||||
export default {
|
||||
beforeMount(el: Element, binding: any) {
|
||||
let interval: ReturnType<typeof setInterval> | null = null;
|
||||
const repeatDirective: Directive = {
|
||||
beforeMount(el: Element, binding: DirectiveBinding<any>) {
|
||||
let interval: Nullable<IntervalHandle> = null;
|
||||
let startTime = 0;
|
||||
const handler = () => binding.value && binding.value();
|
||||
const clear = () => {
|
||||
const handler = (): void => binding.value && binding.value();
|
||||
const clear = (): void => {
|
||||
if (Date.now() - startTime < 100) {
|
||||
handler();
|
||||
}
|
||||
@@ -13,7 +18,7 @@ export default {
|
||||
interval = null;
|
||||
};
|
||||
|
||||
on(el, 'mousedown', (e) => {
|
||||
on(el, 'mousedown', (e: MouseEvent): void => {
|
||||
if ((e as any).button !== 0) return;
|
||||
startTime = Date.now();
|
||||
once(document as any, 'mouseup', clear);
|
||||
@@ -22,3 +27,5 @@ export default {
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default repeatDirective;
|
||||
|
@@ -1,19 +1,28 @@
|
||||
/**
|
||||
* Used to configure the global error handling function, which can monitor vue errors, script errors, static resource errors and Promise errors
|
||||
*/
|
||||
|
||||
import { errorStore, ErrorInfo } from '/@/store/modules/error';
|
||||
import { useSetting } from '/@/hooks/core/useSetting';
|
||||
import { ErrorTypeEnum } from '/@/enums/exceptionEnum';
|
||||
import { App } from 'vue';
|
||||
|
||||
/**
|
||||
* Handling error stack information
|
||||
* @param error
|
||||
*/
|
||||
function processStackMsg(error: Error) {
|
||||
if (!error.stack) {
|
||||
return '';
|
||||
}
|
||||
let stack = error.stack
|
||||
.replace(/\n/gi, '') // 去掉换行,节省传输内容大小
|
||||
.replace(/\bat\b/gi, '@') // chrome中是at,ff中是@
|
||||
.split('@') // 以@分割信息
|
||||
.slice(0, 9) // 最大堆栈长度(Error.stackTraceLimit = 10),所以只取前10条
|
||||
.map((v) => v.replace(/^\s*|\s*$/g, '')) // 去除多余空格
|
||||
.join('~') // 手动添加分隔符,便于后期展示
|
||||
.replace(/\?[^:]+/gi, ''); // 去除js文件链接的多余参数(?x=1之类)
|
||||
.replace(/\n/gi, '') // Remove line breaks to save the size of the transmitted content
|
||||
.replace(/\bat\b/gi, '@') // At in chrome, @ in ff
|
||||
.split('@') // Split information with @
|
||||
.slice(0, 9) // The maximum stack length (Error.stackTraceLimit = 10), so only take the first 10
|
||||
.map((v) => v.replace(/^\s*|\s*$/g, '')) // Remove extra spaces
|
||||
.join('~') // Manually add separators for later display
|
||||
.replace(/\?[^:]+/gi, ''); // Remove redundant parameters of js file links (?x=1 and the like)
|
||||
const msg = error.toString();
|
||||
if (stack.indexOf(msg) < 0) {
|
||||
stack = msg + '@' + stack;
|
||||
@@ -21,6 +30,10 @@ function processStackMsg(error: Error) {
|
||||
return stack;
|
||||
}
|
||||
|
||||
/**
|
||||
* get comp name
|
||||
* @param vm
|
||||
*/
|
||||
function formatComponentName(vm: any) {
|
||||
if (vm.$root === vm) {
|
||||
return {
|
||||
@@ -43,6 +56,10 @@ function formatComponentName(vm: any) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure Vue error handling function
|
||||
*/
|
||||
|
||||
function vueErrorHandler(err: Error, vm: any, info: string) {
|
||||
const { name, path } = formatComponentName(vm);
|
||||
errorStore.commitErrorInfoState({
|
||||
@@ -56,6 +73,9 @@ function vueErrorHandler(err: Error, vm: any, info: string) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure script error handling function
|
||||
*/
|
||||
export function scriptErrorHandler(
|
||||
event: Event | string,
|
||||
source?: string,
|
||||
@@ -86,6 +106,9 @@ export function scriptErrorHandler(
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure Promise error handling function
|
||||
*/
|
||||
function registerPromiseErrorHandler() {
|
||||
window.addEventListener(
|
||||
'unhandledrejection',
|
||||
@@ -104,8 +127,11 @@ function registerPromiseErrorHandler() {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure monitoring resource loading error handling function
|
||||
*/
|
||||
function registerResourceErrorHandler() {
|
||||
// 监控资源加载错误(img,script,css,以及jsonp)
|
||||
// Monitoring resource loading error(img,script,css,and jsonp)
|
||||
window.addEventListener(
|
||||
'error',
|
||||
function (e: Event) {
|
||||
@@ -129,19 +155,23 @@ function registerResourceErrorHandler() {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure global error handling
|
||||
* @param app
|
||||
*/
|
||||
export function setupErrorHandle(app: App) {
|
||||
const { projectSetting } = useSetting();
|
||||
const { useErrorHandle } = projectSetting;
|
||||
if (!useErrorHandle) {
|
||||
return;
|
||||
}
|
||||
// Vue异常监控;
|
||||
if (!useErrorHandle) return;
|
||||
// Vue exception monitoring;
|
||||
app.config.errorHandler = vueErrorHandler;
|
||||
// js错误
|
||||
|
||||
// script error
|
||||
window.onerror = scriptErrorHandler;
|
||||
// promise 异常
|
||||
|
||||
// promise exception
|
||||
registerPromiseErrorHandler();
|
||||
|
||||
// 静态资源异常
|
||||
// Static resource exception
|
||||
registerResourceErrorHandler();
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { isHexColor, colorIsDark, lighten, darken } from '/@/utils/color';
|
||||
import { appStore } from '/@/store/modules/app';
|
||||
import { MenuThemeEnum } from '/@/enums/menuEnum';
|
||||
import { ThemeEnum } from '/@/enums/appEnum';
|
||||
|
||||
const HEADER_BG_COLOR_VAR = '--header-bg-color';
|
||||
const HEADER_BG_HOVER_COLOR_VAR = '--header-bg-hover-color';
|
||||
@@ -22,14 +22,26 @@ function toggleClass(flag: boolean, clsName: string) {
|
||||
document.body.className = flag ? `${className} ${clsName} ` : className;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the status of the project's color weakness mode
|
||||
* @param gray
|
||||
*/
|
||||
export const updateColorWeak = (colorWeak: boolean) => {
|
||||
toggleClass(colorWeak, 'color-weak');
|
||||
};
|
||||
|
||||
/**
|
||||
* Change project gray mode status
|
||||
* @param gray
|
||||
*/
|
||||
export const updateGrayMode = (gray: boolean) => {
|
||||
toggleClass(gray, 'gray-mode');
|
||||
};
|
||||
|
||||
/**
|
||||
* Change the background color of the top header
|
||||
* @param color
|
||||
*/
|
||||
export function updateHeaderBgColor(color: string) {
|
||||
if (!isHexColor(color)) return;
|
||||
// bg color
|
||||
@@ -40,15 +52,20 @@ export function updateHeaderBgColor(color: string) {
|
||||
setCssVar(HEADER_BG_HOVER_COLOR_VAR, hoverColor);
|
||||
setCssVar(HEADER_MENU_ACTIVE_BG_COLOR_VAR, hoverColor);
|
||||
|
||||
// Determine the depth of the color value and automatically switch the theme
|
||||
const isDark = colorIsDark(color);
|
||||
|
||||
appStore.commitProjectConfigState({
|
||||
headerSetting: {
|
||||
theme: isDark ? MenuThemeEnum.DARK : MenuThemeEnum.LIGHT,
|
||||
theme: isDark ? ThemeEnum.DARK : ThemeEnum.LIGHT,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the background color of the left menu
|
||||
* @param color bg color
|
||||
*/
|
||||
export function updateSidebarBgColor(color: string) {
|
||||
if (!isHexColor(color)) return;
|
||||
|
||||
@@ -58,11 +75,12 @@ export function updateSidebarBgColor(color: string) {
|
||||
setCssVar(SIDER_LIGHTEN_2_BG_COLOR, lighten(color, 8));
|
||||
|
||||
// only #ffffff is light
|
||||
// Only when the background color is #fff, the theme of the menu will be changed to light
|
||||
const isLight = ['#fff', '#ffffff'].includes(color.toLowerCase());
|
||||
|
||||
appStore.commitProjectConfigState({
|
||||
menuSetting: {
|
||||
theme: isLight ? MenuThemeEnum.LIGHT : MenuThemeEnum.DARK,
|
||||
theme: isLight ? ThemeEnum.LIGHT : ThemeEnum.DARK,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@@ -1,19 +1,16 @@
|
||||
import type { App } from 'vue';
|
||||
import {
|
||||
createStore,
|
||||
// createLogger, Plugin
|
||||
} from 'vuex';
|
||||
import { createStore, createLogger, Plugin } from 'vuex';
|
||||
import { config } from 'vuex-module-decorators';
|
||||
import { isDevMode } from '/@/utils/env';
|
||||
|
||||
config.rawError = true;
|
||||
const isDev = isDevMode();
|
||||
// const plugins: Plugin<any>[] = isDev ? [createLogger()] : [];
|
||||
const plugins: Plugin<any>[] = isDev ? [createLogger()] : [];
|
||||
|
||||
const store = createStore({
|
||||
modules: {},
|
||||
// modules: {},
|
||||
strict: isDev,
|
||||
// plugins,
|
||||
plugins,
|
||||
});
|
||||
|
||||
export function setupStore(app: App<Element>) {
|
||||
|
8
src/types/config.d.ts
vendored
8
src/types/config.d.ts
vendored
@@ -1,6 +1,6 @@
|
||||
// 左侧菜单, 顶部菜单
|
||||
import { MenuTypeEnum, MenuModeEnum, MenuThemeEnum, TriggerEnum } from '/@/enums/menuEnum';
|
||||
import { ContentEnum, PermissionModeEnum, RouterTransitionEnum } from '/@/enums/appEnum';
|
||||
import { MenuTypeEnum, MenuModeEnum, TriggerEnum } from '/@/enums/menuEnum';
|
||||
import { ContentEnum, PermissionModeEnum, ThemeEnum, RouterTransitionEnum } from '/@/enums/appEnum';
|
||||
|
||||
export interface MessageSetting {
|
||||
title: string;
|
||||
@@ -20,7 +20,7 @@ export interface MenuSetting {
|
||||
menuWidth: number;
|
||||
mode: MenuModeEnum;
|
||||
type: MenuTypeEnum;
|
||||
theme: MenuThemeEnum;
|
||||
theme: ThemeEnum;
|
||||
topMenuAlign: 'start' | 'center' | 'end';
|
||||
collapsedShowSearch: boolean;
|
||||
trigger: TriggerEnum;
|
||||
@@ -41,7 +41,7 @@ export interface MultiTabsSetting {
|
||||
export interface HeaderSetting {
|
||||
fixed: boolean;
|
||||
show: boolean;
|
||||
theme: MenuThemeEnum;
|
||||
theme: ThemeEnum;
|
||||
// 显示刷新按钮
|
||||
showRedo: boolean;
|
||||
// 显示全屏按钮
|
||||
|
6
src/types/event.d.ts
vendored
6
src/types/event.d.ts
vendored
@@ -1,9 +1,7 @@
|
||||
/**
|
||||
* @description: 输入框事件
|
||||
*/
|
||||
declare interface ChangeEvent extends Event {
|
||||
target: HTMLInputElement;
|
||||
}
|
||||
interface WheelEvent {
|
||||
|
||||
declare interface WheelEvent {
|
||||
path?: EventTarget[];
|
||||
}
|
||||
|
15
src/types/global.d.ts
vendored
15
src/types/global.d.ts
vendored
@@ -6,7 +6,6 @@ declare interface PromiseFn<T = any, R = T> {
|
||||
(...arg: T[]): Promise<R>;
|
||||
}
|
||||
|
||||
// 任意对象
|
||||
declare interface IObj<T = any> {
|
||||
[key: string]: T;
|
||||
[key: number]: T;
|
||||
@@ -34,19 +33,11 @@ declare type Indexable<T = any> = {
|
||||
|
||||
declare type Hash<T> = Indexable<T>;
|
||||
|
||||
// declare type DeepPartial<T> = {
|
||||
// [P in keyof T]?: T[P] extends (infer U)[]
|
||||
// ? RecursivePartial<U>[]
|
||||
// : T[P] extends object
|
||||
// ? RecursivePartial<T[P]>
|
||||
// : T[P];
|
||||
// };
|
||||
|
||||
declare type DeepPartial<T> = {
|
||||
[P in keyof T]?: DeepPartial<T[P]>;
|
||||
};
|
||||
|
||||
declare type SelectOptions = {
|
||||
declare type LabelValueOptions = {
|
||||
label: string;
|
||||
value: any;
|
||||
}[];
|
||||
@@ -54,3 +45,7 @@ declare type SelectOptions = {
|
||||
declare type EmitType = (event: string, ...args: any[]) => void;
|
||||
|
||||
declare type TargetContext = '_self' | '_blank';
|
||||
|
||||
declare type TimeoutHandle = ReturnType<typeof setTimeout>;
|
||||
|
||||
declare type IntervalHandle = ReturnType<typeof setInterval>;
|
||||
|
1
src/types/window.d.ts
vendored
1
src/types/window.d.ts
vendored
@@ -2,6 +2,7 @@ import type { App } from 'vue';
|
||||
|
||||
declare global {
|
||||
declare interface Window {
|
||||
// Global vue app instance
|
||||
__APP__: App<Element>;
|
||||
}
|
||||
}
|
||||
|
@@ -1,9 +1,8 @@
|
||||
import { userStore } from '/@/store/modules/user';
|
||||
// import { permissionStore } from '@/store/modules/permission';
|
||||
// import { RoleEnum } from '@/enums/roleEnum';
|
||||
|
||||
/**
|
||||
* @description: 获取token
|
||||
* @description: Get token
|
||||
* @return jwt token
|
||||
*/
|
||||
export function getToken(): string {
|
||||
return userStore.getTokenState;
|
||||
|
@@ -4,6 +4,6 @@
|
||||
export function triggerWindowResize() {
|
||||
const event = document.createEvent('HTMLEvents');
|
||||
event.initEvent('resize', true, true);
|
||||
(event as any).eventType = 'message';
|
||||
(event as ChangeEvent).eventType = 'message';
|
||||
window.dispatchEvent(event);
|
||||
}
|
||||
|
@@ -50,10 +50,6 @@ export function isArray(val: unknown): val is Array<any> {
|
||||
return val && Array.isArray(val);
|
||||
}
|
||||
|
||||
export const isClient = () => {
|
||||
return typeof window !== 'undefined';
|
||||
};
|
||||
|
||||
export const isWindow = (val: any): val is Window => {
|
||||
return typeof window !== 'undefined' && is(val, 'Window');
|
||||
};
|
||||
@@ -64,6 +60,8 @@ export const isElement = (val: unknown): val is Element => {
|
||||
|
||||
export const isServer = typeof window === 'undefined';
|
||||
|
||||
export const isClient = typeof window !== 'undefined';
|
||||
|
||||
export function isImageDom(o: Element) {
|
||||
return o && ['IMAGE', 'IMG'].includes(o.tagName);
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
const projectName = import.meta.env.VITE_GLOB_APP_TITLE;
|
||||
|
||||
export function warn(message: string) {
|
||||
console.warn(`[${projectName} warn]:${message}`);
|
||||
}
|
||||
|
@@ -9,9 +9,6 @@
|
||||
export default defineComponent({
|
||||
name: 'Welcome',
|
||||
components: { House },
|
||||
setup() {
|
||||
return {};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { FormSchema } from '/@/components/Form';
|
||||
|
||||
const basicOptions: SelectOptions = [
|
||||
const basicOptions: LabelValueOptions = [
|
||||
{
|
||||
label: '付晓晓',
|
||||
value: '1',
|
||||
@@ -11,7 +11,7 @@ const basicOptions: SelectOptions = [
|
||||
},
|
||||
];
|
||||
|
||||
const storeTypeOptions: SelectOptions = [
|
||||
const storeTypeOptions: LabelValueOptions = [
|
||||
{
|
||||
label: '私密',
|
||||
value: '1',
|
||||
|
@@ -58,7 +58,7 @@
|
||||
import { Alert, Divider } from 'ant-design-vue';
|
||||
import CurrentPermissionMode from '../CurrentPermissionMode.vue';
|
||||
import { usePermission } from '/@/hooks/web/usePermission';
|
||||
import Authority from '/@/components/Authority';
|
||||
import { Authority } from '/@/components/Authority';
|
||||
import { getPermCodeByUserId } from '/@/api/sys/user';
|
||||
import { permissionStore } from '/@/store/modules/permission';
|
||||
import { PermissionModeEnum } from '/@/enums/appEnum';
|
||||
|
@@ -65,7 +65,7 @@
|
||||
import { userStore } from '/@/store/modules/user';
|
||||
import { RoleEnum } from '/@/enums/roleEnum';
|
||||
import { usePermission } from '/@/hooks/web/usePermission';
|
||||
import Authority from '/@/components/Authority';
|
||||
import { Authority } from '/@/components/Authority';
|
||||
|
||||
export default defineComponent({
|
||||
components: { Alert, CurrentPermissionMode, Divider, Authority },
|
||||
|
Reference in New Issue
Block a user