Pre Merge pull request !84 from xingyu/dev
commit
d0c499b539
|
|
@ -2,5 +2,5 @@ ports:
|
||||||
- port: 5555
|
- port: 5555
|
||||||
onOpen: open-preview
|
onOpen: open-preview
|
||||||
tasks:
|
tasks:
|
||||||
- init: corepack enable && pnpm install
|
- init: npm i -g corepack && pnpm install
|
||||||
command: pnpm run dev:play
|
command: pnpm run dev:play
|
||||||
|
|
|
||||||
|
|
@ -223,16 +223,5 @@
|
||||||
"commentTranslate.multiLineMerge": true,
|
"commentTranslate.multiLineMerge": true,
|
||||||
"vue.server.hybridMode": true,
|
"vue.server.hybridMode": true,
|
||||||
"typescript.tsdk": "node_modules/typescript/lib",
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
"oxc.enable": false,
|
"oxc.enable": false
|
||||||
"cSpell.words": [
|
|
||||||
"archiver",
|
|
||||||
"axios",
|
|
||||||
"dotenv",
|
|
||||||
"isequal",
|
|
||||||
"jspm",
|
|
||||||
"napi",
|
|
||||||
"nolebase",
|
|
||||||
"rollup",
|
|
||||||
"vitest"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,10 @@ VITE_APP_TITLE=芋道管理系统
|
||||||
|
|
||||||
# 应用命名空间,用于缓存、store等功能的前缀,确保隔离
|
# 应用命名空间,用于缓存、store等功能的前缀,确保隔离
|
||||||
VITE_APP_NAMESPACE=yudao-vben-antd
|
VITE_APP_NAMESPACE=yudao-vben-antd
|
||||||
|
|
||||||
|
# 对store进行加密的密钥,在将store持久化到localStorage时会使用该密钥进行加密
|
||||||
|
VITE_APP_STORE_SECURE_KEY=please-replace-me-with-your-own-key
|
||||||
|
|
||||||
# 是否开启模拟数据
|
# 是否开启模拟数据
|
||||||
VITE_NITRO_MOCK=false
|
VITE_NITRO_MOCK=false
|
||||||
|
|
||||||
|
|
@ -16,4 +20,4 @@ VITE_APP_CAPTCHA_ENABLE=false
|
||||||
VITE_APP_DOCALERT_ENABLE=true
|
VITE_APP_DOCALERT_ENABLE=true
|
||||||
|
|
||||||
# 百度统计
|
# 百度统计
|
||||||
VITE_APP_BAIDU_CODE = e98f2eab6ceb8688bc6d8fc5332ff093
|
VITE_APP_BAIDU_CODE = e98f2eab6ceb8688bc6d8fc5332ff093
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vben/web-antd",
|
"name": "@vben/web-antd",
|
||||||
"version": "5.5.4",
|
"version": "5.5.5",
|
||||||
"homepage": "https://vben.pro",
|
"homepage": "https://vben.pro",
|
||||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
@ -26,6 +26,8 @@
|
||||||
"#/*": "./src/*"
|
"#/*": "./src/*"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@form-create/ant-design-vue": "catalog:",
|
||||||
|
"@form-create/antd-designer": "catalog:",
|
||||||
"@tinymce/tinymce-vue": "catalog:",
|
"@tinymce/tinymce-vue": "catalog:",
|
||||||
"@vben/access": "workspace:*",
|
"@vben/access": "workspace:*",
|
||||||
"@vben/common-ui": "workspace:*",
|
"@vben/common-ui": "workspace:*",
|
||||||
|
|
@ -49,7 +51,9 @@
|
||||||
"highlight.js": "catalog:",
|
"highlight.js": "catalog:",
|
||||||
"pinia": "catalog:",
|
"pinia": "catalog:",
|
||||||
"vue": "catalog:",
|
"vue": "catalog:",
|
||||||
"vue-router": "catalog:"
|
"vue-dompurify-html": "catalog:",
|
||||||
|
"vue-router": "catalog:",
|
||||||
|
"vxe-table": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/crypto-js": "catalog:"
|
"@types/crypto-js": "catalog:"
|
||||||
|
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
|
|
@ -76,8 +76,8 @@ const withDefaultPlaceholder = <T extends Component>(
|
||||||
componentProps: Recordable<any> = {},
|
componentProps: Recordable<any> = {},
|
||||||
) => {
|
) => {
|
||||||
return defineComponent({
|
return defineComponent({
|
||||||
inheritAttrs: false,
|
|
||||||
name: component.name,
|
name: component.name,
|
||||||
|
inheritAttrs: false,
|
||||||
setup: (props: any, { attrs, expose, slots }) => {
|
setup: (props: any, { attrs, expose, slots }) => {
|
||||||
const placeholder =
|
const placeholder =
|
||||||
props?.placeholder ||
|
props?.placeholder ||
|
||||||
|
|
@ -142,20 +142,34 @@ async function initComponentAdapter() {
|
||||||
// 如果你的组件体积比较大,可以使用异步加载
|
// 如果你的组件体积比较大,可以使用异步加载
|
||||||
// Button: () =>
|
// Button: () =>
|
||||||
// import('xxx').then((res) => res.Button),
|
// import('xxx').then((res) => res.Button),
|
||||||
ApiSelect: withDefaultPlaceholder(ApiComponent, 'select', {
|
ApiSelect: withDefaultPlaceholder(
|
||||||
component: Select,
|
{
|
||||||
loadingSlot: 'suffixIcon',
|
...ApiComponent,
|
||||||
visibleEvent: 'onDropdownVisibleChange',
|
name: 'ApiSelect',
|
||||||
modelPropName: 'value',
|
},
|
||||||
}),
|
'select',
|
||||||
ApiTreeSelect: withDefaultPlaceholder(ApiComponent, 'select', {
|
{
|
||||||
component: TreeSelect,
|
component: Select,
|
||||||
fieldNames: { label: 'label', value: 'value', children: 'children' },
|
loadingSlot: 'suffixIcon',
|
||||||
loadingSlot: 'suffixIcon',
|
visibleEvent: 'onDropdownVisibleChange',
|
||||||
modelPropName: 'value',
|
modelPropName: 'value',
|
||||||
optionsPropName: 'treeData',
|
},
|
||||||
visibleEvent: 'onVisibleChange',
|
),
|
||||||
}),
|
ApiTreeSelect: withDefaultPlaceholder(
|
||||||
|
{
|
||||||
|
...ApiComponent,
|
||||||
|
name: 'ApiTreeSelect',
|
||||||
|
},
|
||||||
|
'select',
|
||||||
|
{
|
||||||
|
component: TreeSelect,
|
||||||
|
fieldNames: { label: 'label', value: 'value', children: 'children' },
|
||||||
|
loadingSlot: 'suffixIcon',
|
||||||
|
modelPropName: 'value',
|
||||||
|
optionsPropName: 'treeData',
|
||||||
|
visibleEvent: 'onVisibleChange',
|
||||||
|
},
|
||||||
|
),
|
||||||
AutoComplete,
|
AutoComplete,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
CheckboxGroup,
|
CheckboxGroup,
|
||||||
|
|
|
||||||
|
|
@ -119,39 +119,39 @@ export async function checkCaptcha(data: any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获取登录验证码 */
|
/** 获取登录验证码 */
|
||||||
export const sendSmsCode = (data: AuthApi.SmsCodeParams) => {
|
export async function sendSmsCode(data: AuthApi.SmsCodeParams) {
|
||||||
return requestClient.post('/system/auth/send-sms-code', data);
|
return requestClient.post('/system/auth/send-sms-code', data);
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 短信验证码登录 */
|
/** 短信验证码登录 */
|
||||||
export const smsLogin = (data: AuthApi.SmsLoginParams) => {
|
export async function smsLogin(data: AuthApi.SmsLoginParams) {
|
||||||
return requestClient.post('/system/auth/sms-login', data);
|
return requestClient.post('/system/auth/sms-login', data);
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 注册 */
|
/** 注册 */
|
||||||
export const register = (data: AuthApi.RegisterParams) => {
|
export async function register(data: AuthApi.RegisterParams) {
|
||||||
return requestClient.post('/system/auth/register', data);
|
return requestClient.post('/system/auth/register', data);
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 通过短信重置密码 */
|
/** 通过短信重置密码 */
|
||||||
export const smsResetPassword = (data: AuthApi.ResetPasswordParams) => {
|
export async function smsResetPassword(data: AuthApi.ResetPasswordParams) {
|
||||||
return requestClient.post('/system/auth/reset-password', data);
|
return requestClient.post('/system/auth/reset-password', data);
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 社交授权的跳转 */
|
/** 社交授权的跳转 */
|
||||||
export const socialAuthRedirect = (type: number, redirectUri: string) => {
|
export async function socialAuthRedirect(type: number, redirectUri: string) {
|
||||||
return requestClient.get('/system/auth/social-auth-redirect', {
|
return requestClient.get('/system/auth/social-auth-redirect', {
|
||||||
params: {
|
params: {
|
||||||
type,
|
type,
|
||||||
redirectUri,
|
redirectUri,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 社交快捷登录 */
|
/** 社交快捷登录 */
|
||||||
export const socialLogin = (data: AuthApi.SocialLoginParams) => {
|
export async function socialLogin(data: AuthApi.SocialLoginParams) {
|
||||||
return requestClient.post<AuthApi.LoginResult>(
|
return requestClient.post<AuthApi.LoginResult>(
|
||||||
'/system/auth/social-login',
|
'/system/auth/social-login',
|
||||||
data,
|
data,
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ export namespace InfraFileConfigApi {
|
||||||
bucket?: string;
|
bucket?: string;
|
||||||
accessKey?: string;
|
accessKey?: string;
|
||||||
accessSecret?: string;
|
accessSecret?: string;
|
||||||
|
pathStyle?: boolean;
|
||||||
domain: string;
|
domain: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { createApp, watchEffect } from 'vue';
|
import { createApp, watchEffect } from 'vue';
|
||||||
|
import VueDOMPurifyHTML from 'vue-dompurify-html';
|
||||||
|
|
||||||
import { registerAccessDirective } from '@vben/access';
|
import { registerAccessDirective } from '@vben/access';
|
||||||
import { registerLoadingDirective } from '@vben/common-ui/es/loading';
|
import { registerLoadingDirective } from '@vben/common-ui/es/loading';
|
||||||
|
|
@ -10,11 +11,14 @@ import '@vben/styles/antd';
|
||||||
import { useTitle } from '@vueuse/core';
|
import { useTitle } from '@vueuse/core';
|
||||||
|
|
||||||
import { $t, setupI18n } from '#/locales';
|
import { $t, setupI18n } from '#/locales';
|
||||||
|
import { setupFormCreate } from '#/plugins/form-create';
|
||||||
|
|
||||||
import { initComponentAdapter } from './adapter/component';
|
import { initComponentAdapter } from './adapter/component';
|
||||||
import App from './app.vue';
|
import App from './app.vue';
|
||||||
import { router } from './router';
|
import { router } from './router';
|
||||||
|
|
||||||
|
import 'vxe-table/styles/cssvar.scss'; // TODO @puhui999:这个必须导入哇?我看 use-vxe-grid.vue 已经导入了
|
||||||
|
|
||||||
async function bootstrap(namespace: string) {
|
async function bootstrap(namespace: string) {
|
||||||
// 初始化组件适配器
|
// 初始化组件适配器
|
||||||
await initComponentAdapter();
|
await initComponentAdapter();
|
||||||
|
|
@ -39,7 +43,7 @@ async function bootstrap(namespace: string) {
|
||||||
// 国际化 i18n 配置
|
// 国际化 i18n 配置
|
||||||
await setupI18n(app);
|
await setupI18n(app);
|
||||||
|
|
||||||
// 配置 pinia-tore
|
// 配置 pinia-store
|
||||||
await initStores(app, { namespace });
|
await initStores(app, { namespace });
|
||||||
|
|
||||||
// 安装权限指令
|
// 安装权限指令
|
||||||
|
|
@ -52,6 +56,13 @@ async function bootstrap(namespace: string) {
|
||||||
// 配置路由及路由守卫
|
// 配置路由及路由守卫
|
||||||
app.use(router);
|
app.use(router);
|
||||||
|
|
||||||
|
// formCreate
|
||||||
|
setupFormCreate(app);
|
||||||
|
|
||||||
|
// vue-dompurify-html
|
||||||
|
// TODO @dhb52:VueDOMPurifyHTML 是不是不用引入哈?
|
||||||
|
app.use(VueDOMPurifyHTML);
|
||||||
|
|
||||||
// 配置Motion插件
|
// 配置Motion插件
|
||||||
const { MotionPlugin } = await import('@vben/plugins/motion');
|
const { MotionPlugin } = await import('@vben/plugins/motion');
|
||||||
app.use(MotionPlugin);
|
app.use(MotionPlugin);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
<!--
|
||||||
|
参考自 https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/components/ContentWrap/src/ContentWrap.vue
|
||||||
|
保证和 yudao-ui-admin-vue3 功能的一致性
|
||||||
|
-->
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { CSSProperties } from 'vue';
|
||||||
|
|
||||||
|
import { Card } from 'ant-design-vue';
|
||||||
|
|
||||||
|
defineOptions({ name: 'ContentWrap' });
|
||||||
|
|
||||||
|
withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
bodyStyle?: CSSProperties;
|
||||||
|
title?: string;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
bodyStyle: () => ({ padding: '10px' }),
|
||||||
|
title: '',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
// TODO @puhui999:这个功能,和 vue3 貌似没对全哇?
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Card :body-style="bodyStyle" :title="title" class="mb-4">
|
||||||
|
<slot></slot>
|
||||||
|
</Card>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export { default as ContentWrap } from './content-wrap.vue';
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
<script lang="tsx">
|
||||||
|
import type { DescriptionsProps } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import type { PropType } from 'vue';
|
||||||
|
|
||||||
|
import type { DescriptionItemSchema, DescriptionsOptions } from './typing';
|
||||||
|
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
|
import { Descriptions, DescriptionsItem } from 'ant-design-vue';
|
||||||
|
|
||||||
|
/** 对 Descriptions 进行二次封装 */
|
||||||
|
const Description = defineComponent({
|
||||||
|
name: 'Descriptions',
|
||||||
|
props: {
|
||||||
|
data: {
|
||||||
|
type: Object as PropType<Record<string, any>>,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
type: Array as PropType<DescriptionItemSchema[]>,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
// Descriptions 原生 props
|
||||||
|
componentProps: {
|
||||||
|
type: Object as PropType<DescriptionsProps>,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
setup(props: DescriptionsOptions) {
|
||||||
|
// TODO @puhui999:每个 field 的 slot 的考虑
|
||||||
|
// TODO @puhui999:from 5.0:extra: () => getSlot(slots, 'extra')
|
||||||
|
/** 过滤掉不需要展示的 */
|
||||||
|
const shouldShowItem = (item: DescriptionItemSchema) => {
|
||||||
|
if (item.hidden === undefined) return true;
|
||||||
|
return typeof item.hidden === 'function'
|
||||||
|
? !item.hidden(props.data)
|
||||||
|
: !item.hidden;
|
||||||
|
};
|
||||||
|
/** 渲染内容 */
|
||||||
|
const renderContent = (item: DescriptionItemSchema) => {
|
||||||
|
if (item.content) {
|
||||||
|
return typeof item.content === 'function'
|
||||||
|
? item.content(props.data)
|
||||||
|
: item.content;
|
||||||
|
}
|
||||||
|
return item.field ? props.data?.[item.field] : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
return () => (
|
||||||
|
<Descriptions
|
||||||
|
{...props}
|
||||||
|
bordered={props.componentProps?.bordered}
|
||||||
|
colon={props.componentProps?.colon}
|
||||||
|
column={props.componentProps?.column}
|
||||||
|
extra={props.componentProps?.extra}
|
||||||
|
layout={props.componentProps?.layout}
|
||||||
|
size={props.componentProps?.size}
|
||||||
|
title={props.componentProps?.title}
|
||||||
|
>
|
||||||
|
{props.schema?.filter(shouldShowItem).map((item) => (
|
||||||
|
<DescriptionsItem
|
||||||
|
contentStyle={item.contentStyle}
|
||||||
|
key={item.field || String(item.label)}
|
||||||
|
label={item.label}
|
||||||
|
labelStyle={item.labelStyle}
|
||||||
|
span={item.span}
|
||||||
|
>
|
||||||
|
{renderContent(item)}
|
||||||
|
</DescriptionsItem>
|
||||||
|
))}
|
||||||
|
</Descriptions>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO @puhui999:from 5.0:emits: ['register'] 事件
|
||||||
|
export default Description;
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
export { default as Description } from './description.vue';
|
||||||
|
export * from './typing';
|
||||||
|
export { useDescription } from './use-description';
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
import type { DescriptionsProps } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import type { CSSProperties, VNode } from 'vue';
|
||||||
|
|
||||||
|
// TODO @puhui999:【content】这个纠结下;1)vben2.0 是 render;https://doc.vvbin.cn/components/desc.html#usage 2)
|
||||||
|
// TODO @puhui999:vben2.0 还有 sapn【done】、labelMinWidth、contentMinWidth
|
||||||
|
// TODO @puhui999:【hidden】这个纠结下;1)vben2.0 是 show;
|
||||||
|
export interface DescriptionItemSchema {
|
||||||
|
label: string | VNode; // 内容的描述
|
||||||
|
field?: string; // 对应 data 中的字段名
|
||||||
|
content?: ((data: any) => string | VNode) | string | VNode; // 自定义需要展示的内容,比如说 dict-tag
|
||||||
|
span?: number; // 包含列的数量
|
||||||
|
labelStyle?: CSSProperties; // 自定义标签样式
|
||||||
|
contentStyle?: CSSProperties; // 自定义内容样式
|
||||||
|
hidden?: ((data: any) => boolean) | boolean; // 是否显示
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO @puhui999:vben2.0 还有 title【done】、bordered【done】d、useCollapse、collapseOptions
|
||||||
|
// TODO @puhui999:from 5.0:bordered 默认为 true
|
||||||
|
// TODO @puhui999:from 5.0:column 默认为 lg: 3, md: 3, sm: 2, xl: 3, xs: 1, xxl: 4
|
||||||
|
// TODO @puhui999:from 5.0:size 默认为 small;有 'default', 'middle', 'small', undefined
|
||||||
|
// TODO @puhui999:from 5.0:useCollapse 默认为 true
|
||||||
|
export interface DescriptionsOptions {
|
||||||
|
data?: Record<string, any>; // 数据
|
||||||
|
schema?: DescriptionItemSchema[]; // 描述项配置
|
||||||
|
componentProps?: DescriptionsProps; // antd Descriptions 组件参数
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
import type { DescriptionsOptions } from './typing';
|
||||||
|
|
||||||
|
import { defineComponent, h, isReactive, reactive, watch } from 'vue';
|
||||||
|
|
||||||
|
import { Description } from './index';
|
||||||
|
|
||||||
|
/** 描述列表 api 定义 */
|
||||||
|
class DescriptionApi {
|
||||||
|
private state = reactive<Record<string, any>>({});
|
||||||
|
|
||||||
|
constructor(options: DescriptionsOptions) {
|
||||||
|
this.state = { ...options };
|
||||||
|
}
|
||||||
|
|
||||||
|
getState(): DescriptionsOptions {
|
||||||
|
return this.state as DescriptionsOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO @puhui999:【setState】纠结下:1)vben2.0 是 data https://doc.vvbin.cn/components/desc.html#usage;
|
||||||
|
setState(newState: Partial<DescriptionsOptions>) {
|
||||||
|
this.state = { ...this.state, ...newState };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ExtendedDescriptionApi = DescriptionApi;
|
||||||
|
|
||||||
|
export function useDescription(options: DescriptionsOptions) {
|
||||||
|
const IS_REACTIVE = isReactive(options);
|
||||||
|
const api = new DescriptionApi(options);
|
||||||
|
// 扩展API
|
||||||
|
const extendedApi: ExtendedDescriptionApi = api as never;
|
||||||
|
const Desc = defineComponent({
|
||||||
|
name: 'UseDescription',
|
||||||
|
inheritAttrs: false,
|
||||||
|
setup(_, { attrs, slots }) {
|
||||||
|
// 合并props和attrs到state
|
||||||
|
api.setState({ ...attrs });
|
||||||
|
|
||||||
|
return () =>
|
||||||
|
h(
|
||||||
|
Description,
|
||||||
|
{
|
||||||
|
...api.getState(),
|
||||||
|
...attrs,
|
||||||
|
},
|
||||||
|
slots,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 响应式支持
|
||||||
|
if (IS_REACTIVE) {
|
||||||
|
watch(
|
||||||
|
() => options.schema,
|
||||||
|
(newSchema) => {
|
||||||
|
api.setState({ schema: newSchema });
|
||||||
|
},
|
||||||
|
{ immediate: true, deep: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => options.data,
|
||||||
|
(newData) => {
|
||||||
|
api.setState({ data: newData });
|
||||||
|
},
|
||||||
|
{ immediate: true, deep: true },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [Desc, extendedApi] as const;
|
||||||
|
}
|
||||||
|
|
@ -41,17 +41,14 @@ const dictTag = computed(() => {
|
||||||
switch (colorType) {
|
switch (colorType) {
|
||||||
case 'danger': {
|
case 'danger': {
|
||||||
colorType = 'error';
|
colorType = 'error';
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'info': {
|
case 'info': {
|
||||||
colorType = 'default';
|
colorType = 'default';
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'primary': {
|
case 'primary': {
|
||||||
colorType = 'processing';
|
colorType = 'processing';
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
<!-- 数据字典 Select 选择器 -->
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { DictSelectProps } from '../typing';
|
||||||
|
|
||||||
|
import { computed, useAttrs } from 'vue';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Checkbox,
|
||||||
|
CheckboxGroup,
|
||||||
|
Radio,
|
||||||
|
RadioGroup,
|
||||||
|
Select,
|
||||||
|
SelectOption,
|
||||||
|
} from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { getDictObj, getIntDictOptions, getStrDictOptions } from '#/utils/dict';
|
||||||
|
|
||||||
|
defineOptions({ name: 'DictSelect' });
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<DictSelectProps>(), {
|
||||||
|
valueType: 'str',
|
||||||
|
selectType: 'select',
|
||||||
|
});
|
||||||
|
|
||||||
|
const attrs = useAttrs();
|
||||||
|
|
||||||
|
// 获得字典配置
|
||||||
|
// TODO @dhb:可以使用 getDictOptions 替代么?
|
||||||
|
const getDictOptions = computed(() => {
|
||||||
|
switch (props.valueType) {
|
||||||
|
case 'bool': {
|
||||||
|
return getDictObj(props.dictType, 'bool');
|
||||||
|
}
|
||||||
|
case 'int': {
|
||||||
|
return getIntDictOptions(props.dictType);
|
||||||
|
}
|
||||||
|
case 'str': {
|
||||||
|
return getStrDictOptions(props.dictType);
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Select v-if="selectType === 'select'" class="w-1/1" v-bind="attrs">
|
||||||
|
<SelectOption
|
||||||
|
v-for="(dict, index) in getDictOptions"
|
||||||
|
:key="index"
|
||||||
|
:value="dict.value"
|
||||||
|
>
|
||||||
|
{{ dict.label }}
|
||||||
|
</SelectOption>
|
||||||
|
</Select>
|
||||||
|
<RadioGroup v-if="selectType === 'radio'" class="w-1/1" v-bind="attrs">
|
||||||
|
<Radio
|
||||||
|
v-for="(dict, index) in getDictOptions"
|
||||||
|
:key="index"
|
||||||
|
:value="dict.value"
|
||||||
|
>
|
||||||
|
{{ dict.label }}
|
||||||
|
</Radio>
|
||||||
|
</RadioGroup>
|
||||||
|
<CheckboxGroup v-if="selectType === 'checkbox'" class="w-1/1" v-bind="attrs">
|
||||||
|
<Checkbox
|
||||||
|
v-for="(dict, index) in getDictOptions"
|
||||||
|
:key="index"
|
||||||
|
:value="dict.value"
|
||||||
|
>
|
||||||
|
{{ dict.label }}
|
||||||
|
</Checkbox>
|
||||||
|
</CheckboxGroup>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,290 @@
|
||||||
|
import type { ApiSelectProps } from '#/components/form-create/typing';
|
||||||
|
|
||||||
|
import { defineComponent, onMounted, ref, useAttrs } from 'vue';
|
||||||
|
|
||||||
|
import { isEmpty } from '@vben/utils';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Checkbox,
|
||||||
|
CheckboxGroup,
|
||||||
|
Radio,
|
||||||
|
RadioGroup,
|
||||||
|
Select,
|
||||||
|
SelectOption,
|
||||||
|
} from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export const useApiSelect = (option: ApiSelectProps) => {
|
||||||
|
return defineComponent({
|
||||||
|
name: option.name,
|
||||||
|
props: {
|
||||||
|
// 选项标签
|
||||||
|
labelField: {
|
||||||
|
type: String,
|
||||||
|
default: () => option.labelField ?? 'label',
|
||||||
|
},
|
||||||
|
// 选项的值
|
||||||
|
valueField: {
|
||||||
|
type: String,
|
||||||
|
default: () => option.valueField ?? 'value',
|
||||||
|
},
|
||||||
|
// api 接口
|
||||||
|
url: {
|
||||||
|
type: String,
|
||||||
|
default: () => option.url ?? '',
|
||||||
|
},
|
||||||
|
// 请求类型
|
||||||
|
method: {
|
||||||
|
type: String,
|
||||||
|
default: 'GET',
|
||||||
|
},
|
||||||
|
// 选项解析函数
|
||||||
|
parseFunc: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
// 请求参数
|
||||||
|
data: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
// 选择器类型,下拉框 select、多选框 checkbox、单选框 radio
|
||||||
|
selectType: {
|
||||||
|
type: String,
|
||||||
|
default: 'select',
|
||||||
|
},
|
||||||
|
// 是否多选
|
||||||
|
multiple: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
// 是否远程搜索
|
||||||
|
remote: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
// 远程搜索时携带的参数
|
||||||
|
remoteField: {
|
||||||
|
type: String,
|
||||||
|
default: 'label',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const attrs = useAttrs();
|
||||||
|
const options = ref<any[]>([]); // 下拉数据
|
||||||
|
const loading = ref(false); // 是否正在从远程获取数据
|
||||||
|
const queryParam = ref<any>(); // 当前输入的值
|
||||||
|
const getOptions = async () => {
|
||||||
|
options.value = [];
|
||||||
|
// 接口选择器
|
||||||
|
if (isEmpty(props.url)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (props.method) {
|
||||||
|
case 'GET': {
|
||||||
|
let url: string = props.url;
|
||||||
|
if (props.remote && queryParam.value !== undefined) {
|
||||||
|
url = url.includes('?')
|
||||||
|
? `${url}&${props.remoteField}=${queryParam.value}`
|
||||||
|
: `${url}?${props.remoteField}=${queryParam.value}`;
|
||||||
|
}
|
||||||
|
parseOptions(await requestClient.get(url));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'POST': {
|
||||||
|
const data: any = JSON.parse(props.data);
|
||||||
|
if (props.remote) {
|
||||||
|
data[props.remoteField] = queryParam.value;
|
||||||
|
}
|
||||||
|
parseOptions(await requestClient.post(props.url, data));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function parseOptions(data: any) {
|
||||||
|
// 情况一:如果有自定义解析函数优先使用自定义解析
|
||||||
|
if (!isEmpty(props.parseFunc)) {
|
||||||
|
options.value = parseFunc()?.(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 情况二:返回的直接是一个列表
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
parseOptions0(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 情况二:返回的是分页数据,尝试读取 list
|
||||||
|
data = data.list;
|
||||||
|
if (!!data && Array.isArray(data)) {
|
||||||
|
parseOptions0(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 情况三:不是 yudao-vue-pro 标准返回
|
||||||
|
console.warn(
|
||||||
|
`接口[${props.url}] 返回结果不是 yudao-vue-pro 标准返回建议采用自定义解析函数处理`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseOptions0(data: any[]) {
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
options.value = data.map((item: any) => ({
|
||||||
|
label: parseExpression(item, props.labelField),
|
||||||
|
value: parseExpression(item, props.valueField),
|
||||||
|
}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.warn(`接口[${props.url}] 返回结果不是一个数组`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseFunc() {
|
||||||
|
let parse: any = null;
|
||||||
|
if (props.parseFunc) {
|
||||||
|
// 解析字符串函数
|
||||||
|
// eslint-disable-next-line no-new-func
|
||||||
|
parse = new Function(`return ${props.parseFunc}`)();
|
||||||
|
}
|
||||||
|
return parse;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseExpression(data: any, template: string) {
|
||||||
|
// 检测是否使用了表达式
|
||||||
|
if (!template.includes('${')) {
|
||||||
|
return data[template];
|
||||||
|
}
|
||||||
|
// 正则表达式匹配模板字符串中的 ${...}
|
||||||
|
const pattern = /\$\{([^}]*)\}/g;
|
||||||
|
// 使用replace函数配合正则表达式和回调函数来进行替换
|
||||||
|
return template.replaceAll(pattern, (_, expr) => {
|
||||||
|
// expr 是匹配到的 ${} 内的表达式(这里是属性名),从 data 中获取对应的值
|
||||||
|
const result = data[expr.trim()]; // 去除前后空白,以防用户输入带空格的属性名
|
||||||
|
if (!result) {
|
||||||
|
console.warn(
|
||||||
|
`接口选择器选项模版[${template}][${expr.trim()}] 解析值失败结果为[${result}], 请检查属性名称是否存在于接口返回值中,存在则忽略此条!!!`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const remoteMethod = async (query: any) => {
|
||||||
|
if (!query) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
queryParam.value = query;
|
||||||
|
await getOptions();
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await getOptions();
|
||||||
|
});
|
||||||
|
|
||||||
|
const buildSelect = () => {
|
||||||
|
if (props.multiple) {
|
||||||
|
// fix:多写此步是为了解决 multiple 属性问题
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
class="w-1/1"
|
||||||
|
loading={loading.value}
|
||||||
|
mode="multiple"
|
||||||
|
{...attrs}
|
||||||
|
// TODO: remote 对等实现
|
||||||
|
// remote={props.remote}
|
||||||
|
{...(props.remote && { remoteMethod })}
|
||||||
|
>
|
||||||
|
{options.value.map(
|
||||||
|
(item: { label: any; value: any }, index: any) => (
|
||||||
|
<SelectOption key={index} value={item.value}>
|
||||||
|
{item.label}
|
||||||
|
</SelectOption>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
class="w-1/1"
|
||||||
|
loading={loading.value}
|
||||||
|
{...attrs}
|
||||||
|
// TODO: @dhb52 remote 对等实现, 还是说没作用
|
||||||
|
// remote={props.remote}
|
||||||
|
{...(props.remote && { remoteMethod })}
|
||||||
|
>
|
||||||
|
{options.value.map(
|
||||||
|
(item: { label: any; value: any }, index: any) => (
|
||||||
|
<SelectOption key={index} value={item.value}>
|
||||||
|
{item.label}
|
||||||
|
</SelectOption>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const buildCheckbox = () => {
|
||||||
|
if (isEmpty(options.value)) {
|
||||||
|
options.value = [
|
||||||
|
{ label: '选项1', value: '选项1' },
|
||||||
|
{ label: '选项2', value: '选项2' },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<CheckboxGroup class="w-1/1" {...attrs}>
|
||||||
|
{options.value.map(
|
||||||
|
(item: { label: any; value: any }, index: any) => (
|
||||||
|
<Checkbox key={index} value={item.value}>
|
||||||
|
{item.label}
|
||||||
|
</Checkbox>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</CheckboxGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const buildRadio = () => {
|
||||||
|
if (isEmpty(options.value)) {
|
||||||
|
options.value = [
|
||||||
|
{ label: '选项1', value: '选项1' },
|
||||||
|
{ label: '选项2', value: '选项2' },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<RadioGroup class="w-1/1" {...attrs}>
|
||||||
|
{options.value.map(
|
||||||
|
(item: { label: any; value: any }, index: any) => (
|
||||||
|
<Radio key={index} value={item.value}>
|
||||||
|
{item.label}
|
||||||
|
</Radio>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</RadioGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
return () => (
|
||||||
|
<>
|
||||||
|
{(() => {
|
||||||
|
switch (props.selectType) {
|
||||||
|
case 'checkbox': {
|
||||||
|
return buildCheckbox();
|
||||||
|
}
|
||||||
|
case 'radio': {
|
||||||
|
return buildRadio();
|
||||||
|
}
|
||||||
|
case 'select': {
|
||||||
|
return buildSelect();
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return buildSelect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})()}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
|
import ImageUpload from '#/components/upload/image-upload.vue';
|
||||||
|
|
||||||
|
export const useImagesUpload = () => {
|
||||||
|
return defineComponent({
|
||||||
|
props: {
|
||||||
|
multiple: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
maxNumber: {
|
||||||
|
type: Number,
|
||||||
|
default: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
// TODO: @dhb52 其实还是靠 props 默认参数起作用,没能从 formCreate 传递
|
||||||
|
return (props: { maxNumber?: number; multiple?: boolean }) => (
|
||||||
|
<ImageUpload maxNumber={props.maxNumber} multiple={props.multiple} />
|
||||||
|
);
|
||||||
|
},
|
||||||
|
name: 'ImagesUpload',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,182 @@
|
||||||
|
import type { Ref } from 'vue';
|
||||||
|
|
||||||
|
import type { Menu } from '#/components/form-create/typing';
|
||||||
|
|
||||||
|
import { nextTick, onMounted } from 'vue';
|
||||||
|
|
||||||
|
import { apiSelectRule } from '#/components/form-create/rules/data';
|
||||||
|
|
||||||
|
import {
|
||||||
|
useDictSelectRule,
|
||||||
|
useEditorRule,
|
||||||
|
useSelectRule,
|
||||||
|
useUploadFileRule,
|
||||||
|
useUploadImageRule,
|
||||||
|
useUploadImagesRule,
|
||||||
|
} from './rules';
|
||||||
|
|
||||||
|
export function makeRequiredRule() {
|
||||||
|
return {
|
||||||
|
type: 'Required',
|
||||||
|
field: 'formCreate$required',
|
||||||
|
title: '是否必填',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const localeProps = (
|
||||||
|
t: (msg: string) => any,
|
||||||
|
prefix: string,
|
||||||
|
rules: any[],
|
||||||
|
) => {
|
||||||
|
return rules.map((rule: { field: string; title: any }) => {
|
||||||
|
if (rule.field === 'formCreate$required') {
|
||||||
|
rule.title = t('props.required') || rule.title;
|
||||||
|
} else if (rule.field && rule.field !== '_optionType') {
|
||||||
|
rule.title = t(`components.${prefix}.${rule.field}`) || rule.title;
|
||||||
|
}
|
||||||
|
return rule;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析表单组件的 field, title 等字段(递归,如果组件包含子组件)
|
||||||
|
*
|
||||||
|
* @param rule 组件的生成规则 https://www.form-create.com/v3/guide/rule
|
||||||
|
* @param fields 解析后表单组件字段
|
||||||
|
* @param parentTitle 如果是子表单,子表单的标题,默认为空
|
||||||
|
*/
|
||||||
|
export const parseFormFields = (
|
||||||
|
rule: Record<string, any>,
|
||||||
|
fields: Array<Record<string, any>> = [],
|
||||||
|
parentTitle: string = '',
|
||||||
|
) => {
|
||||||
|
const { type, field, $required, title: tempTitle, children } = rule;
|
||||||
|
if (field && tempTitle) {
|
||||||
|
let title = tempTitle;
|
||||||
|
if (parentTitle) {
|
||||||
|
title = `${parentTitle}.${tempTitle}`;
|
||||||
|
}
|
||||||
|
let required = false;
|
||||||
|
if ($required) {
|
||||||
|
required = true;
|
||||||
|
}
|
||||||
|
fields.push({
|
||||||
|
field,
|
||||||
|
title,
|
||||||
|
type,
|
||||||
|
required,
|
||||||
|
});
|
||||||
|
// TODO 子表单 需要处理子表单字段
|
||||||
|
// if (type === 'group' && rule.props?.rule && Array.isArray(rule.props.rule)) {
|
||||||
|
// // 解析子表单的字段
|
||||||
|
// rule.props.rule.forEach((item) => {
|
||||||
|
// parseFields(item, fieldsPermission, title)
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
if (children && Array.isArray(children)) {
|
||||||
|
children.forEach((rule) => {
|
||||||
|
parseFormFields(rule, fields);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表单设计器增强 hook
|
||||||
|
* 新增
|
||||||
|
* - 文件上传
|
||||||
|
* - 单图上传
|
||||||
|
* - 多图上传
|
||||||
|
* - 字典选择器
|
||||||
|
* - 用户选择器
|
||||||
|
* - 部门选择器
|
||||||
|
* - 富文本
|
||||||
|
*/
|
||||||
|
export const useFormCreateDesigner = async (designer: Ref) => {
|
||||||
|
const editorRule = useEditorRule();
|
||||||
|
const uploadFileRule = useUploadFileRule();
|
||||||
|
const uploadImageRule = useUploadImageRule();
|
||||||
|
const uploadImagesRule = useUploadImagesRule();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建表单组件
|
||||||
|
*/
|
||||||
|
const buildFormComponents = () => {
|
||||||
|
// 移除自带的上传组件规则,使用 uploadFileRule、uploadImgRule、uploadImgsRule 替代
|
||||||
|
designer.value?.removeMenuItem('upload');
|
||||||
|
// 移除自带的富文本组件规则,使用 editorRule 替代
|
||||||
|
designer.value?.removeMenuItem('fc-editor');
|
||||||
|
const components = [
|
||||||
|
editorRule,
|
||||||
|
uploadFileRule,
|
||||||
|
uploadImageRule,
|
||||||
|
uploadImagesRule,
|
||||||
|
];
|
||||||
|
components.forEach((component) => {
|
||||||
|
// 插入组件规则
|
||||||
|
designer.value?.addComponent(component);
|
||||||
|
// 插入拖拽按钮到 `main` 分类下
|
||||||
|
designer.value?.appendMenuItem('main', {
|
||||||
|
icon: component.icon,
|
||||||
|
name: component.name,
|
||||||
|
label: component.label,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const userSelectRule = useSelectRule({
|
||||||
|
name: 'UserSelect',
|
||||||
|
label: '用户选择器',
|
||||||
|
icon: 'icon-eye',
|
||||||
|
});
|
||||||
|
const deptSelectRule = useSelectRule({
|
||||||
|
name: 'DeptSelect',
|
||||||
|
label: '部门选择器',
|
||||||
|
icon: 'icon-tree',
|
||||||
|
});
|
||||||
|
const dictSelectRule = useDictSelectRule();
|
||||||
|
const apiSelectRule0 = useSelectRule({
|
||||||
|
name: 'ApiSelect',
|
||||||
|
label: '接口选择器',
|
||||||
|
icon: 'icon-json',
|
||||||
|
props: [...apiSelectRule],
|
||||||
|
event: ['click', 'change', 'visibleChange', 'clear', 'blur', 'focus'],
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建系统字段菜单
|
||||||
|
*/
|
||||||
|
const buildSystemMenu = () => {
|
||||||
|
// 移除自带的下拉选择器组件,使用 currencySelectRule 替代
|
||||||
|
// designer.value?.removeMenuItem('select')
|
||||||
|
// designer.value?.removeMenuItem('radio')
|
||||||
|
// designer.value?.removeMenuItem('checkbox')
|
||||||
|
const components = [
|
||||||
|
userSelectRule,
|
||||||
|
deptSelectRule,
|
||||||
|
dictSelectRule,
|
||||||
|
apiSelectRule0,
|
||||||
|
];
|
||||||
|
const menu: Menu = {
|
||||||
|
name: 'system',
|
||||||
|
title: '系统字段',
|
||||||
|
list: components.map((component) => {
|
||||||
|
// 插入组件规则
|
||||||
|
designer.value?.addComponent(component);
|
||||||
|
// 插入拖拽按钮到 `system` 分类下
|
||||||
|
return {
|
||||||
|
icon: component.icon,
|
||||||
|
name: component.name,
|
||||||
|
label: component.label,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
designer.value?.addMenu(menu);
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await nextTick();
|
||||||
|
buildFormComponents();
|
||||||
|
buildSystemMenu();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
export { useApiSelect } from './components/use-api-select';
|
||||||
|
|
||||||
|
export { useFormCreateDesigner } from './helpers';
|
||||||
|
|
@ -0,0 +1,182 @@
|
||||||
|
/* eslint-disable no-template-curly-in-string */
|
||||||
|
const selectRule = [
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
field: 'selectType',
|
||||||
|
title: '选择器类型',
|
||||||
|
value: 'select',
|
||||||
|
options: [
|
||||||
|
{ label: '下拉框', value: 'select' },
|
||||||
|
{ label: '单选框', value: 'radio' },
|
||||||
|
{ label: '多选框', value: 'checkbox' },
|
||||||
|
],
|
||||||
|
// 参考 https://www.form-create.com/v3/guide/control 组件联动,单选框和多选框不需要多选属性
|
||||||
|
control: [
|
||||||
|
{
|
||||||
|
value: 'select',
|
||||||
|
condition: '==',
|
||||||
|
method: 'hidden',
|
||||||
|
rule: [
|
||||||
|
'multiple',
|
||||||
|
'clearable',
|
||||||
|
'collapseTags',
|
||||||
|
'multipleLimit',
|
||||||
|
'allowCreate',
|
||||||
|
'filterable',
|
||||||
|
'noMatchText',
|
||||||
|
'remote',
|
||||||
|
'remoteMethod',
|
||||||
|
'reserveKeyword',
|
||||||
|
'defaultFirstOption',
|
||||||
|
'automaticDropdown',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'filterable',
|
||||||
|
title: '是否可搜索',
|
||||||
|
},
|
||||||
|
{ type: 'switch', field: 'multiple', title: '是否多选' },
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'disabled',
|
||||||
|
title: '是否禁用',
|
||||||
|
},
|
||||||
|
{ type: 'switch', field: 'clearable', title: '是否可以清空选项' },
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'collapseTags',
|
||||||
|
title: '多选时是否将选中值按文字的形式展示',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'inputNumber',
|
||||||
|
field: 'multipleLimit',
|
||||||
|
title: '多选时用户最多可以选择的项目数,为 0 则不限制',
|
||||||
|
props: { min: 0 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
field: 'autocomplete',
|
||||||
|
title: 'autocomplete 属性',
|
||||||
|
},
|
||||||
|
{ type: 'input', field: 'placeholder', title: '占位符' },
|
||||||
|
{ type: 'switch', field: 'allowCreate', title: '是否允许用户创建新条目' },
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
field: 'noMatchText',
|
||||||
|
title: '搜索条件无匹配时显示的文字',
|
||||||
|
},
|
||||||
|
{ type: 'input', field: 'noDataText', title: '选项为空时显示的文字' },
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'reserveKeyword',
|
||||||
|
title: '多选且可搜索时,是否在选中一个选项后保留当前的搜索关键词',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'defaultFirstOption',
|
||||||
|
title: '在输入框按下回车,选择第一个匹配项',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'popperAppendToBody',
|
||||||
|
title: '是否将弹出框插入至 body 元素',
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'automaticDropdown',
|
||||||
|
title: '对于不可搜索的 Select,是否在输入框获得焦点后自动弹出选项菜单',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const apiSelectRule = [
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
field: 'url',
|
||||||
|
title: 'url 地址',
|
||||||
|
props: {
|
||||||
|
placeholder: '/system/user/simple-list',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
field: 'method',
|
||||||
|
title: '请求类型',
|
||||||
|
value: 'GET',
|
||||||
|
options: [
|
||||||
|
{ label: 'GET', value: 'GET' },
|
||||||
|
{ label: 'POST', value: 'POST' },
|
||||||
|
],
|
||||||
|
control: [
|
||||||
|
{
|
||||||
|
value: 'GET',
|
||||||
|
condition: '!=',
|
||||||
|
method: 'hidden',
|
||||||
|
rule: [
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
field: 'data',
|
||||||
|
title: '请求参数 JSON 格式',
|
||||||
|
props: {
|
||||||
|
autosize: true,
|
||||||
|
type: 'textarea',
|
||||||
|
placeholder: '{"type": 1}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
field: 'labelField',
|
||||||
|
title: 'label 属性',
|
||||||
|
info: '可以使用 el 表达式:${属性},来实现复杂数据组合。如:${nickname}-${id}',
|
||||||
|
props: {
|
||||||
|
placeholder: 'nickname',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
field: 'valueField',
|
||||||
|
title: 'value 属性',
|
||||||
|
info: '可以使用 el 表达式:${属性},来实现复杂数据组合。如:${nickname}-${id}',
|
||||||
|
props: {
|
||||||
|
placeholder: 'id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
field: 'parseFunc',
|
||||||
|
title: '选项解析函数',
|
||||||
|
info: `data 为接口返回值,需要写一个匿名函数解析返回值为选择器 options 列表
|
||||||
|
(data: any)=>{ label: string; value: any }[]`,
|
||||||
|
props: {
|
||||||
|
autosize: true,
|
||||||
|
rows: { minRows: 2, maxRows: 6 },
|
||||||
|
type: 'textarea',
|
||||||
|
placeholder: `
|
||||||
|
function (data) {
|
||||||
|
console.log(data)
|
||||||
|
return data.list.map(item=> ({label: item.nickname,value: item.id}))
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'remote',
|
||||||
|
info: '是否可搜索',
|
||||||
|
title: '其中的选项是否从服务器远程加载',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
field: 'remoteField',
|
||||||
|
title: '请求参数',
|
||||||
|
info: '远程请求时请求携带的参数名称,如:name',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export { apiSelectRule, selectRule };
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
export { useDictSelectRule } from './use-dict-select';
|
||||||
|
export { useEditorRule } from './use-editor-rule';
|
||||||
|
export { useSelectRule } from './use-select-rule';
|
||||||
|
export { useUploadFileRule } from './use-upload-file-rule';
|
||||||
|
export { useUploadImageRule } from './use-upload-image-rule';
|
||||||
|
export { useUploadImagesRule } from './use-upload-images-rule';
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
|
import { buildUUID, cloneDeep } from '@vben/utils';
|
||||||
|
|
||||||
|
import * as DictDataApi from '#/api/system/dict/type';
|
||||||
|
import {
|
||||||
|
localeProps,
|
||||||
|
makeRequiredRule,
|
||||||
|
} from '#/components/form-create/helpers';
|
||||||
|
import { selectRule } from '#/components/form-create/rules/data';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典选择器规则,如果规则使用到动态数据则需要单独配置不能使用 useSelectRule
|
||||||
|
*/
|
||||||
|
export const useDictSelectRule = () => {
|
||||||
|
const label = '字典选择器';
|
||||||
|
const name = 'DictSelect';
|
||||||
|
const rules = cloneDeep(selectRule);
|
||||||
|
const dictOptions = ref<{ label: string; value: string }[]>([]); // 字典类型下拉数据
|
||||||
|
onMounted(async () => {
|
||||||
|
const data = await DictDataApi.getSimpleDictTypeList();
|
||||||
|
if (!data || data.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dictOptions.value =
|
||||||
|
data?.map((item: DictDataApi.SystemDictTypeApi.DictType) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.type,
|
||||||
|
})) ?? [];
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
icon: 'icon-descriptions',
|
||||||
|
label,
|
||||||
|
name,
|
||||||
|
rule() {
|
||||||
|
return {
|
||||||
|
type: name,
|
||||||
|
field: buildUUID(),
|
||||||
|
title: label,
|
||||||
|
info: '',
|
||||||
|
$required: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props(_: any, { t }: any) {
|
||||||
|
return localeProps(t, `${name}.props`, [
|
||||||
|
makeRequiredRule(),
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
field: 'dictType',
|
||||||
|
title: '字典类型',
|
||||||
|
value: '',
|
||||||
|
options: dictOptions.value,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
field: 'valueType',
|
||||||
|
title: '字典值类型',
|
||||||
|
value: 'str',
|
||||||
|
options: [
|
||||||
|
{ label: '数字', value: 'int' },
|
||||||
|
{ label: '字符串', value: 'str' },
|
||||||
|
{ label: '布尔值', value: 'bool' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
...rules,
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { buildUUID } from '@vben/utils';
|
||||||
|
|
||||||
|
import {
|
||||||
|
localeProps,
|
||||||
|
makeRequiredRule,
|
||||||
|
} from '#/components/form-create/helpers';
|
||||||
|
|
||||||
|
export const useEditorRule = () => {
|
||||||
|
const label = '富文本';
|
||||||
|
const name = 'Tinymce';
|
||||||
|
return {
|
||||||
|
icon: 'icon-editor',
|
||||||
|
label,
|
||||||
|
name,
|
||||||
|
rule() {
|
||||||
|
return {
|
||||||
|
type: name,
|
||||||
|
field: buildUUID(),
|
||||||
|
title: label,
|
||||||
|
info: '',
|
||||||
|
$required: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props(_: any, { t }: any) {
|
||||||
|
return localeProps(t, `${name}.props`, [
|
||||||
|
makeRequiredRule(),
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
field: 'height',
|
||||||
|
title: '高度',
|
||||||
|
},
|
||||||
|
{ type: 'switch', field: 'readonly', title: '是否只读' },
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
import type { SelectRuleOption } from '#/components/form-create/typing';
|
||||||
|
|
||||||
|
import { buildUUID, cloneDeep } from '@vben/utils';
|
||||||
|
|
||||||
|
import {
|
||||||
|
localeProps,
|
||||||
|
makeRequiredRule,
|
||||||
|
} from '#/components/form-create/helpers';
|
||||||
|
import { selectRule } from '#/components/form-create/rules/data';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用选择器规则 hook
|
||||||
|
*
|
||||||
|
* @param option 规则配置
|
||||||
|
*/
|
||||||
|
export const useSelectRule = (option: SelectRuleOption) => {
|
||||||
|
const label = option.label;
|
||||||
|
const name = option.name;
|
||||||
|
const rules = cloneDeep(selectRule);
|
||||||
|
return {
|
||||||
|
icon: option.icon,
|
||||||
|
label,
|
||||||
|
name,
|
||||||
|
event: option.event,
|
||||||
|
rule() {
|
||||||
|
return {
|
||||||
|
type: name,
|
||||||
|
field: buildUUID(),
|
||||||
|
title: label,
|
||||||
|
info: '',
|
||||||
|
$required: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props(_: any, { t }: any) {
|
||||||
|
if (!option.props) {
|
||||||
|
option.props = [];
|
||||||
|
}
|
||||||
|
return localeProps(t, `${name}.props`, [
|
||||||
|
makeRequiredRule(),
|
||||||
|
...option.props,
|
||||||
|
...rules,
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
import { buildUUID } from '@vben/utils';
|
||||||
|
|
||||||
|
import {
|
||||||
|
localeProps,
|
||||||
|
makeRequiredRule,
|
||||||
|
} from '#/components/form-create/helpers';
|
||||||
|
|
||||||
|
export const useUploadFileRule = () => {
|
||||||
|
const label = '文件上传';
|
||||||
|
const name = 'FileUpload';
|
||||||
|
return {
|
||||||
|
icon: 'icon-upload',
|
||||||
|
label,
|
||||||
|
name,
|
||||||
|
rule() {
|
||||||
|
return {
|
||||||
|
type: name,
|
||||||
|
field: buildUUID(),
|
||||||
|
title: label,
|
||||||
|
info: '',
|
||||||
|
$required: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props(_: any, { t }: any) {
|
||||||
|
return localeProps(t, `${name}.props`, [
|
||||||
|
makeRequiredRule(),
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
field: 'fileType',
|
||||||
|
title: '文件类型',
|
||||||
|
value: ['doc', 'xls', 'ppt', 'txt', 'pdf'],
|
||||||
|
options: [
|
||||||
|
{ label: 'doc', value: 'doc' },
|
||||||
|
{ label: 'xls', value: 'xls' },
|
||||||
|
{ label: 'ppt', value: 'ppt' },
|
||||||
|
{ label: 'txt', value: 'txt' },
|
||||||
|
{ label: 'pdf', value: 'pdf' },
|
||||||
|
],
|
||||||
|
props: {
|
||||||
|
multiple: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'autoUpload',
|
||||||
|
title: '是否在选取文件后立即进行上传',
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'drag',
|
||||||
|
title: '拖拽上传',
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'isShowTip',
|
||||||
|
title: '是否显示提示',
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'inputNumber',
|
||||||
|
field: 'fileSize',
|
||||||
|
title: '大小限制(MB)',
|
||||||
|
value: 5,
|
||||||
|
props: { min: 0 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'inputNumber',
|
||||||
|
field: 'limit',
|
||||||
|
title: '数量限制',
|
||||||
|
value: 5,
|
||||||
|
props: { min: 0 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'disabled',
|
||||||
|
title: '是否禁用',
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
import { buildUUID } from '@vben/utils';
|
||||||
|
|
||||||
|
import {
|
||||||
|
localeProps,
|
||||||
|
makeRequiredRule,
|
||||||
|
} from '#/components/form-create/helpers';
|
||||||
|
|
||||||
|
export const useUploadImageRule = () => {
|
||||||
|
const label = '单图上传';
|
||||||
|
const name = 'ImageUpload';
|
||||||
|
return {
|
||||||
|
icon: 'icon-image',
|
||||||
|
label,
|
||||||
|
name,
|
||||||
|
rule() {
|
||||||
|
return {
|
||||||
|
type: name,
|
||||||
|
field: buildUUID(),
|
||||||
|
title: label,
|
||||||
|
info: '',
|
||||||
|
$required: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props(_: any, { t }: any) {
|
||||||
|
return localeProps(t, `${name}.props`, [
|
||||||
|
makeRequiredRule(),
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'drag',
|
||||||
|
title: '拖拽上传',
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
field: 'fileType',
|
||||||
|
title: '图片类型限制',
|
||||||
|
value: ['image/jpeg', 'image/png', 'image/gif'],
|
||||||
|
options: [
|
||||||
|
{ label: 'image/apng', value: 'image/apng' },
|
||||||
|
{ label: 'image/bmp', value: 'image/bmp' },
|
||||||
|
{ label: 'image/gif', value: 'image/gif' },
|
||||||
|
{ label: 'image/jpeg', value: 'image/jpeg' },
|
||||||
|
{ label: 'image/pjpeg', value: 'image/pjpeg' },
|
||||||
|
{ label: 'image/svg+xml', value: 'image/svg+xml' },
|
||||||
|
{ label: 'image/tiff', value: 'image/tiff' },
|
||||||
|
{ label: 'image/webp', value: 'image/webp' },
|
||||||
|
{ label: 'image/x-icon', value: 'image/x-icon' },
|
||||||
|
],
|
||||||
|
props: {
|
||||||
|
multiple: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'inputNumber',
|
||||||
|
field: 'fileSize',
|
||||||
|
title: '大小限制(MB)',
|
||||||
|
value: 5,
|
||||||
|
props: { min: 0 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
field: 'height',
|
||||||
|
title: '组件高度',
|
||||||
|
value: '150px',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
field: 'width',
|
||||||
|
title: '组件宽度',
|
||||||
|
value: '150px',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
field: 'borderradius',
|
||||||
|
title: '组件边框圆角',
|
||||||
|
value: '8px',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'disabled',
|
||||||
|
title: '是否显示删除按钮',
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'showBtnText',
|
||||||
|
title: '是否显示按钮文字',
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
import { buildUUID } from '@vben/utils';
|
||||||
|
|
||||||
|
import {
|
||||||
|
localeProps,
|
||||||
|
makeRequiredRule,
|
||||||
|
} from '#/components/form-create/helpers';
|
||||||
|
|
||||||
|
export const useUploadImagesRule = () => {
|
||||||
|
const label = '多图上传';
|
||||||
|
const name = 'ImagesUpload';
|
||||||
|
return {
|
||||||
|
icon: 'icon-image',
|
||||||
|
label,
|
||||||
|
name,
|
||||||
|
rule() {
|
||||||
|
return {
|
||||||
|
type: name,
|
||||||
|
field: buildUUID(),
|
||||||
|
title: label,
|
||||||
|
info: '',
|
||||||
|
$required: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props(_: any, { t }: any) {
|
||||||
|
return localeProps(t, `${name}.props`, [
|
||||||
|
makeRequiredRule(),
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
field: 'drag',
|
||||||
|
title: '拖拽上传',
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
field: 'fileType',
|
||||||
|
title: '图片类型限制',
|
||||||
|
value: ['image/jpeg', 'image/png', 'image/gif'],
|
||||||
|
options: [
|
||||||
|
{ label: 'image/apng', value: 'image/apng' },
|
||||||
|
{ label: 'image/bmp', value: 'image/bmp' },
|
||||||
|
{ label: 'image/gif', value: 'image/gif' },
|
||||||
|
{ label: 'image/jpeg', value: 'image/jpeg' },
|
||||||
|
{ label: 'image/pjpeg', value: 'image/pjpeg' },
|
||||||
|
{ label: 'image/svg+xml', value: 'image/svg+xml' },
|
||||||
|
{ label: 'image/tiff', value: 'image/tiff' },
|
||||||
|
{ label: 'image/webp', value: 'image/webp' },
|
||||||
|
{ label: 'image/x-icon', value: 'image/x-icon' },
|
||||||
|
],
|
||||||
|
props: {
|
||||||
|
multiple: true,
|
||||||
|
maxNumber: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'inputNumber',
|
||||||
|
field: 'fileSize',
|
||||||
|
title: '大小限制(MB)',
|
||||||
|
value: 5,
|
||||||
|
props: { min: 0 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'inputNumber',
|
||||||
|
field: 'limit',
|
||||||
|
title: '数量限制',
|
||||||
|
value: 5,
|
||||||
|
props: { min: 0 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
field: 'height',
|
||||||
|
title: '组件高度',
|
||||||
|
value: '150px',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
field: 'width',
|
||||||
|
title: '组件宽度',
|
||||||
|
value: '150px',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
field: 'borderradius',
|
||||||
|
title: '组件边框圆角',
|
||||||
|
value: '8px',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
import type { Rule } from '@form-create/ant-design-vue';
|
||||||
|
|
||||||
|
/** 数据字典 Select 选择器组件 Props 类型 */
|
||||||
|
export interface DictSelectProps {
|
||||||
|
dictType: string; // 字典类型
|
||||||
|
valueType?: 'bool' | 'int' | 'str'; // 字典值类型 TODO @芋艿:'boolean' | 'number' | 'string';需要和 vue3 一起统一!
|
||||||
|
selectType?: 'checkbox' | 'radio' | 'select'; // 选择器类型,下拉框 select、多选框 checkbox、单选框 radio
|
||||||
|
formCreateInject?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 左侧拖拽按钮 */
|
||||||
|
export interface MenuItem {
|
||||||
|
label: string;
|
||||||
|
name: string;
|
||||||
|
icon: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 左侧拖拽按钮分类 */
|
||||||
|
export interface Menu {
|
||||||
|
title: string;
|
||||||
|
name: string;
|
||||||
|
list: MenuItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MenuList = Array<Menu>;
|
||||||
|
|
||||||
|
// TODO @dhb52:MenuList、Menu、MenuItem、DragRule 这几个,是不是没用到呀?
|
||||||
|
// 拖拽组件的规则
|
||||||
|
export interface DragRule {
|
||||||
|
icon: string;
|
||||||
|
name: string;
|
||||||
|
label: string;
|
||||||
|
children?: string;
|
||||||
|
inside?: true;
|
||||||
|
drag?: string | true;
|
||||||
|
dragBtn?: false;
|
||||||
|
mask?: false;
|
||||||
|
|
||||||
|
rule(): Rule;
|
||||||
|
|
||||||
|
props(v: any, v1: any): Rule[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 通用 API 下拉组件 Props 类型 */
|
||||||
|
export interface ApiSelectProps {
|
||||||
|
name: string; // 组件名称
|
||||||
|
labelField?: string; // 选项标签
|
||||||
|
valueField?: string; // 选项的值
|
||||||
|
url?: string; // url 接口
|
||||||
|
isDict?: boolean; // 是否字典选择器
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 选择组件规则配置类型 */
|
||||||
|
export interface SelectRuleOption {
|
||||||
|
label: string; // label 名称
|
||||||
|
name: string; // 组件名称
|
||||||
|
icon: string; // 组件图标
|
||||||
|
props?: any[]; // 组件规则
|
||||||
|
event?: any[]; // 事件配置
|
||||||
|
}
|
||||||
|
|
@ -33,7 +33,7 @@ import {
|
||||||
|
|
||||||
type InitOptions = IPropTypes['init'];
|
type InitOptions = IPropTypes['init'];
|
||||||
|
|
||||||
defineOptions({ inheritAttrs: false });
|
defineOptions({ name: 'Tinymce', inheritAttrs: false });
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
options: {
|
options: {
|
||||||
|
|
@ -157,7 +157,6 @@ const initOptions = computed((): InitOptions => {
|
||||||
const { httpRequest } = useUpload();
|
const { httpRequest } = useUpload();
|
||||||
httpRequest(file)
|
httpRequest(file)
|
||||||
.then((url) => {
|
.then((url) => {
|
||||||
console.log('tinymce 上传图片成功:', url);
|
|
||||||
resolve(url);
|
resolve(url);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ import type { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface
|
||||||
|
|
||||||
import type { AxiosResponse } from '@vben/request';
|
import type { AxiosResponse } from '@vben/request';
|
||||||
|
|
||||||
|
import type { UploadListType } from './typing';
|
||||||
|
|
||||||
import type { AxiosProgressEvent } from '#/api/infra/file';
|
import type { AxiosProgressEvent } from '#/api/infra/file';
|
||||||
|
|
||||||
import { ref, toRefs, watch } from 'vue';
|
import { ref, toRefs, watch } from 'vue';
|
||||||
|
|
@ -30,7 +32,7 @@ const props = withDefaults(
|
||||||
) => Promise<AxiosResponse<any>>;
|
) => Promise<AxiosResponse<any>>;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
helpText?: string;
|
helpText?: string;
|
||||||
listType?: ListType;
|
listType?: UploadListType;
|
||||||
// 最大数量的文件,Infinity不限制
|
// 最大数量的文件,Infinity不限制
|
||||||
maxNumber?: number;
|
maxNumber?: number;
|
||||||
// 文件最大多少MB
|
// 文件最大多少MB
|
||||||
|
|
@ -58,7 +60,6 @@ const props = withDefaults(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
const emit = defineEmits(['change', 'update:value', 'delete']);
|
const emit = defineEmits(['change', 'update:value', 'delete']);
|
||||||
type ListType = 'picture' | 'picture-card' | 'text';
|
|
||||||
const { accept, helpText, maxNumber, maxSize } = toRefs(props);
|
const { accept, helpText, maxNumber, maxSize } = toRefs(props);
|
||||||
const isInnerOperate = ref<boolean>(false);
|
const isInnerOperate = ref<boolean>(false);
|
||||||
const { getStringAccept } = useUploadType({
|
const { getStringAccept } = useUploadType({
|
||||||
|
|
|
||||||
|
|
@ -4,3 +4,5 @@ export enum UploadResultStatus {
|
||||||
SUCCESS = 'success',
|
SUCCESS = 'success',
|
||||||
UPLOADING = 'uploading',
|
UPLOADING = 'uploading',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type UploadListType = 'picture' | 'picture-card' | 'text';
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import type { NotificationItem } from '@vben/layouts';
|
||||||
|
|
||||||
import { computed, onMounted, ref, watch } from 'vue';
|
import { computed, onMounted, ref, watch } from 'vue';
|
||||||
|
|
||||||
import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
|
import { AuthenticationLoginExpiredModal, useVbenModal } from '@vben/common-ui';
|
||||||
import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
|
import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
|
||||||
import { useWatermark } from '@vben/hooks';
|
import { useWatermark } from '@vben/hooks';
|
||||||
import {
|
import {
|
||||||
|
|
@ -33,6 +33,8 @@ import { router } from '#/router';
|
||||||
import { useAuthStore } from '#/store';
|
import { useAuthStore } from '#/store';
|
||||||
import LoginForm from '#/views/_core/authentication/login.vue';
|
import LoginForm from '#/views/_core/authentication/login.vue';
|
||||||
|
|
||||||
|
import Help from './components/help.vue';
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
const accessStore = useAccessStore();
|
const accessStore = useAccessStore();
|
||||||
|
|
@ -42,6 +44,10 @@ const notifications = ref<NotificationItem[]>([]);
|
||||||
const unreadCount = ref(0);
|
const unreadCount = ref(0);
|
||||||
const showDot = computed(() => unreadCount.value > 0);
|
const showDot = computed(() => unreadCount.value > 0);
|
||||||
|
|
||||||
|
const [HelpModal, helpModalApi] = useVbenModal({
|
||||||
|
connectedComponent: Help,
|
||||||
|
});
|
||||||
|
|
||||||
const menus = computed(() => [
|
const menus = computed(() => [
|
||||||
{
|
{
|
||||||
handler: () => {
|
handler: () => {
|
||||||
|
|
@ -70,9 +76,7 @@ const menus = computed(() => [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
handler: () => {
|
handler: () => {
|
||||||
openWindow(`${VBEN_GITHUB_URL}/issues`, {
|
helpModalApi.open();
|
||||||
target: '_blank',
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
icon: CircleHelp,
|
icon: CircleHelp,
|
||||||
text: $t('ui.widgets.qa'),
|
text: $t('ui.widgets.qa'),
|
||||||
|
|
@ -210,4 +214,5 @@ watch(
|
||||||
<LockScreen :avatar @to-login="handleLogout" />
|
<LockScreen :avatar @to-login="handleLogout" />
|
||||||
</template>
|
</template>
|
||||||
</BasicLayout>
|
</BasicLayout>
|
||||||
|
<HelpModal />
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { useVbenModal, VbenButton, VbenButtonGroup } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Image, Tag } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
draggable: true,
|
||||||
|
overlayBlur: 5,
|
||||||
|
footer: false,
|
||||||
|
onCancel() {
|
||||||
|
modalApi.close();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function openWindow(url: string) {
|
||||||
|
window.open(url, '_blank');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Modal class="w-[40%]" :title="$t('ui.widgets.qa')">
|
||||||
|
<div class="mt-2 flex flex-col">
|
||||||
|
<div class="mt-2 flex flex-row">
|
||||||
|
<VbenButtonGroup class="basis-1/3" :gap="2" border size="large">
|
||||||
|
<p class="p-2">项目地址:</p>
|
||||||
|
<VbenButton
|
||||||
|
variant="link"
|
||||||
|
@click="
|
||||||
|
openWindow('https://gitee.com/yudaocode/yudao-ui-admin-vben')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Gitee
|
||||||
|
</VbenButton>
|
||||||
|
<VbenButton
|
||||||
|
variant="link"
|
||||||
|
@click="
|
||||||
|
openWindow('https://github.com/yudaocode/yudao-ui-admin-vben')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Github
|
||||||
|
</VbenButton>
|
||||||
|
</VbenButtonGroup>
|
||||||
|
|
||||||
|
<VbenButtonGroup class="basis-1/3" :gap="2" border size="large">
|
||||||
|
<p class="p-2">issues:</p>
|
||||||
|
<VbenButton
|
||||||
|
variant="link"
|
||||||
|
@click="
|
||||||
|
openWindow(
|
||||||
|
'https://gitee.com/yudaocode/yudao-ui-admin-vben/issues',
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Gitee
|
||||||
|
</VbenButton>
|
||||||
|
<VbenButton
|
||||||
|
variant="link"
|
||||||
|
@click="
|
||||||
|
openWindow(
|
||||||
|
'https://github.com/yudaocode/yudao-ui-admin-vben/issues',
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Github
|
||||||
|
</VbenButton>
|
||||||
|
</VbenButtonGroup>
|
||||||
|
|
||||||
|
<VbenButtonGroup class="basis-1/3" :gap="2" border size="large">
|
||||||
|
<p class="p-2">开发文档:</p>
|
||||||
|
<VbenButton
|
||||||
|
variant="link"
|
||||||
|
@click="openWindow('https://doc.iocoder.cn/quick-start/')"
|
||||||
|
>
|
||||||
|
项目文档
|
||||||
|
</VbenButton>
|
||||||
|
<VbenButton variant="link" @click="openWindow('https://antdv.com/')">
|
||||||
|
antdv 文档
|
||||||
|
</VbenButton>
|
||||||
|
</VbenButtonGroup>
|
||||||
|
</div>
|
||||||
|
<p class="mt-2 flex justify-center">
|
||||||
|
<span>
|
||||||
|
<Image src="../../../public/wx-xingyu.png" alt="数舵科技" />
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p class="mt-2 flex justify-center pt-4 text-sm italic">
|
||||||
|
本项目采用<Tag color="blue">MIT</Tag>开源协议,个人与企业可100%
|
||||||
|
免费使用。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
import type { App } from 'vue';
|
||||||
|
|
||||||
|
// import install from '@form-create/ant-design-vue/auto-import';
|
||||||
|
import FcDesigner from '@form-create/antd-designer';
|
||||||
|
import Antd from 'ant-design-vue';
|
||||||
|
|
||||||
|
// ======================= 自定义组件 =======================
|
||||||
|
import { useApiSelect } from '#/components/form-create';
|
||||||
|
import DictSelect from '#/components/form-create/components/dict-select.vue';
|
||||||
|
import { useImagesUpload } from '#/components/form-create/components/use-images-upload';
|
||||||
|
import { Tinymce } from '#/components/tinymce';
|
||||||
|
import { FileUpload, ImageUpload } from '#/components/upload';
|
||||||
|
|
||||||
|
const UserSelect = useApiSelect({
|
||||||
|
name: 'UserSelect',
|
||||||
|
labelField: 'nickname',
|
||||||
|
valueField: 'id',
|
||||||
|
url: '/system/user/simple-list',
|
||||||
|
});
|
||||||
|
const DeptSelect = useApiSelect({
|
||||||
|
name: 'DeptSelect',
|
||||||
|
labelField: 'name',
|
||||||
|
valueField: 'id',
|
||||||
|
url: '/system/dept/simple-list',
|
||||||
|
});
|
||||||
|
const ApiSelect = useApiSelect({
|
||||||
|
name: 'ApiSelect',
|
||||||
|
});
|
||||||
|
const ImagesUpload = useImagesUpload();
|
||||||
|
|
||||||
|
const components = [
|
||||||
|
ImageUpload,
|
||||||
|
ImagesUpload,
|
||||||
|
FileUpload,
|
||||||
|
Tinymce,
|
||||||
|
DictSelect,
|
||||||
|
UserSelect,
|
||||||
|
DeptSelect,
|
||||||
|
ApiSelect,
|
||||||
|
];
|
||||||
|
|
||||||
|
// TODO: @dhb52 按需导入,而不是app.use(Antd);
|
||||||
|
// 参考 http://www.form-create.com/v3/ant-design-vue/auto-import.html 文档
|
||||||
|
export const setupFormCreate = (app: App) => {
|
||||||
|
components.forEach((component) => {
|
||||||
|
app.component(component.name as string, component);
|
||||||
|
});
|
||||||
|
app.use(Antd);
|
||||||
|
app.use(FcDesigner);
|
||||||
|
app.use(FcDesigner.formCreate);
|
||||||
|
};
|
||||||
|
|
@ -7,8 +7,8 @@ import {
|
||||||
import { resetStaticRoutes } from '@vben/utils';
|
import { resetStaticRoutes } from '@vben/utils';
|
||||||
|
|
||||||
import { createRouterGuard } from './guard';
|
import { createRouterGuard } from './guard';
|
||||||
import { setupBaiduTongJi } from './tongji';
|
|
||||||
import { routes } from './routes';
|
import { routes } from './routes';
|
||||||
|
import { setupBaiduTongJi } from './tongji';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @zh_CN 创建vue-router实例
|
* @zh_CN 创建vue-router实例
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,8 @@ const coreRoutes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
name: 'SocialLogin',
|
name: 'SocialLogin',
|
||||||
path: 'social-login',
|
path: 'social-login',
|
||||||
component: () => import('#/views/_core/authentication/social-login.vue'),
|
component: () =>
|
||||||
|
import('#/views/_core/authentication/social-login.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
title: $t('page.auth.login'),
|
title: $t('page.auth.login'),
|
||||||
},
|
},
|
||||||
|
|
@ -104,7 +105,7 @@ const coreRoutes: RouteRecordRaw[] = [
|
||||||
meta: {
|
meta: {
|
||||||
title: $t('page.auth.login'),
|
title: $t('page.auth.login'),
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -44,4 +44,4 @@ const componentKeys: string[] = Object.keys(
|
||||||
const path = v.replace('../../views/', '/');
|
const path = v.replace('../../views/', '/');
|
||||||
return path.endsWith('.vue') ? path.slice(0, -4) : path;
|
return path.endsWith('.vue') ? path.slice(0, -4) : path;
|
||||||
});
|
});
|
||||||
export { accessRoutes, coreRouteNames, routes, componentKeys };
|
export { accessRoutes, componentKeys, coreRouteNames, routes };
|
||||||
|
|
|
||||||
|
|
@ -13,4 +13,4 @@ const routes: RouteRecordRaw[] = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default routes;
|
export default routes;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,28 @@ import { isObject } from '@vben/utils';
|
||||||
|
|
||||||
import { useDictStore } from '#/store';
|
import { useDictStore } from '#/store';
|
||||||
|
|
||||||
const dictStore = useDictStore();
|
// TODO @dhb52:top-level 调用 导致:"getActivePinia()" was called but there was no active Pinia
|
||||||
|
// 先临时移入到方法中
|
||||||
|
// const dictStore = useDictStore();
|
||||||
|
|
||||||
|
// TODO @dhb: antd 组件的 color 类型
|
||||||
|
type ColorType = 'error' | 'info' | 'success' | 'warning';
|
||||||
|
|
||||||
|
export interface DictDataType {
|
||||||
|
dictType: string;
|
||||||
|
label: string;
|
||||||
|
value: boolean | number | string;
|
||||||
|
colorType: ColorType;
|
||||||
|
cssClass: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NumberDictDataType extends DictDataType {
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StringDictDataType extends DictDataType {
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取字典标签
|
* 获取字典标签
|
||||||
|
|
@ -16,6 +37,7 @@ const dictStore = useDictStore();
|
||||||
* @returns 字典标签
|
* @returns 字典标签
|
||||||
*/
|
*/
|
||||||
function getDictLabel(dictType: string, value: any) {
|
function getDictLabel(dictType: string, value: any) {
|
||||||
|
const dictStore = useDictStore();
|
||||||
const dictObj = dictStore.getDictData(dictType, value);
|
const dictObj = dictStore.getDictData(dictType, value);
|
||||||
return isObject(dictObj) ? dictObj.label : '';
|
return isObject(dictObj) ? dictObj.label : '';
|
||||||
}
|
}
|
||||||
|
|
@ -28,6 +50,7 @@ function getDictLabel(dictType: string, value: any) {
|
||||||
* @returns 字典对象
|
* @returns 字典对象
|
||||||
*/
|
*/
|
||||||
function getDictObj(dictType: string, value: any) {
|
function getDictObj(dictType: string, value: any) {
|
||||||
|
const dictStore = useDictStore();
|
||||||
const dictObj = dictStore.getDictData(dictType, value);
|
const dictObj = dictStore.getDictData(dictType, value);
|
||||||
return isObject(dictObj) ? dictObj : null;
|
return isObject(dictObj) ? dictObj : null;
|
||||||
}
|
}
|
||||||
|
|
@ -36,12 +59,15 @@ function getDictObj(dictType: string, value: any) {
|
||||||
* 获取字典数组 用于select radio 等
|
* 获取字典数组 用于select radio 等
|
||||||
*
|
*
|
||||||
* @param dictType 字典类型
|
* @param dictType 字典类型
|
||||||
|
* @param valueType 字典值类型,默认 string 类型
|
||||||
* @returns 字典数组
|
* @returns 字典数组
|
||||||
*/
|
*/
|
||||||
|
// TODO @puhui999:貌似可以定义一个类型?不使用 any[]
|
||||||
function getDictOptions(
|
function getDictOptions(
|
||||||
dictType: string,
|
dictType: string,
|
||||||
valueType: 'boolean' | 'number' | 'string' = 'string',
|
valueType: 'boolean' | 'number' | 'string' = 'string',
|
||||||
) {
|
): any[] {
|
||||||
|
const dictStore = useDictStore();
|
||||||
const dictOpts = dictStore.getDictOptions(dictType);
|
const dictOpts = dictStore.getDictOptions(dictType);
|
||||||
const dictOptions: DefaultOptionType = [];
|
const dictOptions: DefaultOptionType = [];
|
||||||
if (dictOpts.length > 0) {
|
if (dictOpts.length > 0) {
|
||||||
|
|
@ -71,6 +97,51 @@ function getDictOptions(
|
||||||
return dictOptions.length > 0 ? dictOptions : [];
|
return dictOptions.length > 0 ? dictOptions : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO @dhb52:下面的一系列方法,看看能不能复用 getDictOptions 方法
|
||||||
|
export const getIntDictOptions = (dictType: string): NumberDictDataType[] => {
|
||||||
|
// 获得通用的 DictDataType 列表
|
||||||
|
const dictOptions = getDictOptions(dictType) as DictDataType[];
|
||||||
|
// 转换成 number 类型的 NumberDictDataType 类型
|
||||||
|
// why 需要特殊转换:避免 IDEA 在 v-for="dict in getIntDictOptions(...)" 时,el-option 的 key 会告警
|
||||||
|
const dictOption: NumberDictDataType[] = [];
|
||||||
|
dictOptions.forEach((dict: DictDataType) => {
|
||||||
|
dictOption.push({
|
||||||
|
...dict,
|
||||||
|
value: Number.parseInt(`${dict.value}`),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return dictOption;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO @dhb52:下面的一系列方法,看看能不能复用 getDictOptions 方法
|
||||||
|
export const getStrDictOptions = (dictType: string) => {
|
||||||
|
// 获得通用的 DictDataType 列表
|
||||||
|
const dictOptions = getDictOptions(dictType) as DictDataType[];
|
||||||
|
// 转换成 string 类型的 StringDictDataType 类型
|
||||||
|
// why 需要特殊转换:避免 IDEA 在 v-for="dict in getStrDictOptions(...)" 时,el-option 的 key 会告警
|
||||||
|
const dictOption: StringDictDataType[] = [];
|
||||||
|
dictOptions.forEach((dict: DictDataType) => {
|
||||||
|
dictOption.push({
|
||||||
|
...dict,
|
||||||
|
value: `${dict.value}`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return dictOption;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO @dhb52:下面的一系列方法,看看能不能复用 getDictOptions 方法
|
||||||
|
export const getBoolDictOptions = (dictType: string) => {
|
||||||
|
const dictOption: DictDataType[] = [];
|
||||||
|
const dictOptions = getDictOptions(dictType) as DictDataType[];
|
||||||
|
dictOptions.forEach((dict: DictDataType) => {
|
||||||
|
dictOption.push({
|
||||||
|
...dict,
|
||||||
|
value: `${dict.value}` === 'true',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return dictOption;
|
||||||
|
};
|
||||||
|
|
||||||
enum DICT_TYPE {
|
enum DICT_TYPE {
|
||||||
AI_GENERATE_MODE = 'ai_generate_mode', // AI 生成模式
|
AI_GENERATE_MODE = 'ai_generate_mode', // AI 生成模式
|
||||||
AI_IMAGE_STATUS = 'ai_image_status', // AI 图片状态
|
AI_IMAGE_STATUS = 'ai_image_status', // AI 图片状态
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ const loginRef = ref();
|
||||||
|
|
||||||
/** 获取租户列表,并默认选中 */
|
/** 获取租户列表,并默认选中 */
|
||||||
const tenantList = ref<AuthApi.TenantResult[]>([]); // 租户列表
|
const tenantList = ref<AuthApi.TenantResult[]>([]); // 租户列表
|
||||||
const fetchTenantList = async () => {
|
async function fetchTenantList() {
|
||||||
if (!tenantEnable) {
|
if (!tenantEnable) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -56,11 +56,11 @@ const fetchTenantList = async () => {
|
||||||
|
|
||||||
// 设置选中的租户编号
|
// 设置选中的租户编号
|
||||||
accessStore.setTenantId(tenantId);
|
accessStore.setTenantId(tenantId);
|
||||||
loginRef.value.getFormApi().setFieldValue('tenantId', tenantId);
|
loginRef.value.getFormApi().setFieldValue('tenantId', tenantId?.toString());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取租户列表失败:', error);
|
console.error('获取租户列表失败:', error);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 组件挂载时获取租户信息 */
|
/** 组件挂载时获取租户信息 */
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|
@ -74,19 +74,19 @@ const formSchema = computed((): VbenFormSchema[] => {
|
||||||
componentProps: {
|
componentProps: {
|
||||||
options: tenantList.value.map((item) => ({
|
options: tenantList.value.map((item) => ({
|
||||||
label: item.name,
|
label: item.name,
|
||||||
value: item.id,
|
value: item.id.toString(),
|
||||||
})),
|
})),
|
||||||
placeholder: $t('authentication.tenantTip'),
|
placeholder: $t('authentication.tenantTip'),
|
||||||
},
|
},
|
||||||
fieldName: 'tenantId',
|
fieldName: 'tenantId',
|
||||||
label: $t('authentication.tenant'),
|
label: $t('authentication.tenant'),
|
||||||
rules: z.number().positive(),
|
rules: z.string().min(1, { message: $t('authentication.tenantTip') }),
|
||||||
dependencies: {
|
dependencies: {
|
||||||
triggerFields: ['tenantId'],
|
triggerFields: ['tenantId'],
|
||||||
if: tenantEnable,
|
if: tenantEnable,
|
||||||
trigger(values) {
|
trigger(values) {
|
||||||
if (values.tenantId) {
|
if (values.tenantId) {
|
||||||
accessStore.setTenantId(values.tenantId);
|
accessStore.setTenantId(Number(values.tenantId));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ const forgetPasswordRef = ref();
|
||||||
|
|
||||||
/** 获取租户列表,并默认选中 */
|
/** 获取租户列表,并默认选中 */
|
||||||
const tenantList = ref<AuthApi.TenantResult[]>([]); // 租户列表
|
const tenantList = ref<AuthApi.TenantResult[]>([]); // 租户列表
|
||||||
const fetchTenantList = async () => {
|
async function fetchTenantList() {
|
||||||
if (!tenantEnable) {
|
if (!tenantEnable) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -55,11 +55,13 @@ const fetchTenantList = async () => {
|
||||||
|
|
||||||
// 设置选中的租户编号
|
// 设置选中的租户编号
|
||||||
accessStore.setTenantId(tenantId);
|
accessStore.setTenantId(tenantId);
|
||||||
forgetPasswordRef.value.getFormApi().setFieldValue('tenantId', tenantId);
|
forgetPasswordRef.value
|
||||||
|
.getFormApi()
|
||||||
|
.setFieldValue('tenantId', tenantId?.toString());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取租户列表失败:', error);
|
console.error('获取租户列表失败:', error);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 组件挂载时获取租户信息 */
|
/** 组件挂载时获取租户信息 */
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|
@ -73,19 +75,19 @@ const formSchema = computed((): VbenFormSchema[] => {
|
||||||
componentProps: {
|
componentProps: {
|
||||||
options: tenantList.value.map((item) => ({
|
options: tenantList.value.map((item) => ({
|
||||||
label: item.name,
|
label: item.name,
|
||||||
value: item.id,
|
value: item.id.toString(),
|
||||||
})),
|
})),
|
||||||
placeholder: $t('authentication.tenantTip'),
|
placeholder: $t('authentication.tenantTip'),
|
||||||
},
|
},
|
||||||
fieldName: 'tenantId',
|
fieldName: 'tenantId',
|
||||||
label: $t('authentication.tenant'),
|
label: $t('authentication.tenant'),
|
||||||
rules: z.number().positive(),
|
rules: z.string().min(1, { message: $t('authentication.tenantTip') }),
|
||||||
dependencies: {
|
dependencies: {
|
||||||
triggerFields: ['tenantId'],
|
triggerFields: ['tenantId'],
|
||||||
if: tenantEnable,
|
if: tenantEnable,
|
||||||
trigger(values) {
|
trigger(values) {
|
||||||
if (values.tenantId) {
|
if (values.tenantId) {
|
||||||
accessStore.setTenantId(values.tenantId);
|
accessStore.setTenantId(Number(values.tenantId));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ const captchaType = 'blockPuzzle'; // 验证码类型:'blockPuzzle' | 'clickWo
|
||||||
|
|
||||||
/** 获取租户列表,并默认选中 */
|
/** 获取租户列表,并默认选中 */
|
||||||
const tenantList = ref<AuthApi.TenantResult[]>([]); // 租户列表
|
const tenantList = ref<AuthApi.TenantResult[]>([]); // 租户列表
|
||||||
const fetchTenantList = async () => {
|
async function fetchTenantList() {
|
||||||
if (!tenantEnable) {
|
if (!tenantEnable) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -61,26 +61,25 @@ const fetchTenantList = async () => {
|
||||||
|
|
||||||
// 设置选中的租户编号
|
// 设置选中的租户编号
|
||||||
accessStore.setTenantId(tenantId);
|
accessStore.setTenantId(tenantId);
|
||||||
loginRef.value.getFormApi().setFieldValue('tenantId', tenantId);
|
loginRef.value.getFormApi().setFieldValue('tenantId', tenantId?.toString());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取租户列表失败:', error);
|
console.error('获取租户列表失败:', error);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 处理登录 */
|
/** 处理登录 */
|
||||||
const handleLogin = async (values: any) => {
|
async function handleLogin(values: any) {
|
||||||
// 如果开启验证码,则先验证验证码
|
// 如果开启验证码,则先验证验证码
|
||||||
if (captchaEnable) {
|
if (captchaEnable) {
|
||||||
verifyRef.value.show();
|
verifyRef.value.show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 无验证码,直接登录
|
// 无验证码,直接登录
|
||||||
await authStore.authLogin('username', values);
|
await authStore.authLogin('username', values);
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 验证码通过,执行登录 */
|
/** 验证码通过,执行登录 */
|
||||||
const handleVerifySuccess = async ({ captchaVerification }: any) => {
|
async function handleVerifySuccess({ captchaVerification }: any) {
|
||||||
try {
|
try {
|
||||||
await authStore.authLogin('username', {
|
await authStore.authLogin('username', {
|
||||||
...(await loginRef.value.getFormApi().getValues()),
|
...(await loginRef.value.getFormApi().getValues()),
|
||||||
|
|
@ -89,11 +88,11 @@ const handleVerifySuccess = async ({ captchaVerification }: any) => {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in handleLogin:', error);
|
console.error('Error in handleLogin:', error);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 处理第三方登录 */
|
/** 处理第三方登录 */
|
||||||
const redirect = query?.redirect;
|
const redirect = query?.redirect;
|
||||||
const handleThirdLogin = async (type: number) => {
|
async function handleThirdLogin(type: number) {
|
||||||
if (type <= 0) {
|
if (type <= 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -111,7 +110,7 @@ const handleThirdLogin = async (type: number) => {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('第三方登录处理失败:', error);
|
console.error('第三方登录处理失败:', error);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 组件挂载时获取租户信息 */
|
/** 组件挂载时获取租户信息 */
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|
@ -125,19 +124,19 @@ const formSchema = computed((): VbenFormSchema[] => {
|
||||||
componentProps: {
|
componentProps: {
|
||||||
options: tenantList.value.map((item) => ({
|
options: tenantList.value.map((item) => ({
|
||||||
label: item.name,
|
label: item.name,
|
||||||
value: item.id,
|
value: item.id.toString(),
|
||||||
})),
|
})),
|
||||||
placeholder: $t('authentication.tenantTip'),
|
placeholder: $t('authentication.tenantTip'),
|
||||||
},
|
},
|
||||||
fieldName: 'tenantId',
|
fieldName: 'tenantId',
|
||||||
label: $t('authentication.tenant'),
|
label: $t('authentication.tenant'),
|
||||||
rules: z.number().positive(),
|
rules: z.string().min(1, { message: $t('authentication.tenantTip') }),
|
||||||
dependencies: {
|
dependencies: {
|
||||||
triggerFields: ['tenantId'],
|
triggerFields: ['tenantId'],
|
||||||
if: tenantEnable,
|
if: tenantEnable,
|
||||||
trigger(values) {
|
trigger(values) {
|
||||||
if (values.tenantId) {
|
if (values.tenantId) {
|
||||||
accessStore.setTenantId(values.tenantId);
|
accessStore.setTenantId(Number(values.tenantId));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ const captchaType = 'blockPuzzle'; // 验证码类型:'blockPuzzle' | 'clickWo
|
||||||
|
|
||||||
/** 获取租户列表,并默认选中 */
|
/** 获取租户列表,并默认选中 */
|
||||||
const tenantList = ref<AuthApi.TenantResult[]>([]); // 租户列表
|
const tenantList = ref<AuthApi.TenantResult[]>([]); // 租户列表
|
||||||
const fetchTenantList = async () => {
|
async function fetchTenantList() {
|
||||||
if (!tenantEnable) {
|
if (!tenantEnable) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -60,14 +60,16 @@ const fetchTenantList = async () => {
|
||||||
|
|
||||||
// 设置选中的租户编号
|
// 设置选中的租户编号
|
||||||
accessStore.setTenantId(tenantId);
|
accessStore.setTenantId(tenantId);
|
||||||
registerRef.value.getFormApi().setFieldValue('tenantId', tenantId);
|
registerRef.value
|
||||||
|
.getFormApi()
|
||||||
|
.setFieldValue('tenantId', tenantId?.toString());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取租户列表失败:', error);
|
console.error('获取租户列表失败:', error);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 执行注册 */
|
/** 执行注册 */
|
||||||
const handleRegister = async (values: any) => {
|
async function handleRegister(values: any) {
|
||||||
// 如果开启验证码,则先验证验证码
|
// 如果开启验证码,则先验证验证码
|
||||||
if (captchaEnable) {
|
if (captchaEnable) {
|
||||||
verifyRef.value.show();
|
verifyRef.value.show();
|
||||||
|
|
@ -76,7 +78,7 @@ const handleRegister = async (values: any) => {
|
||||||
|
|
||||||
// 无验证码,直接登录
|
// 无验证码,直接登录
|
||||||
await authStore.authLogin('register', values);
|
await authStore.authLogin('register', values);
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 验证码通过,执行注册 */
|
/** 验证码通过,执行注册 */
|
||||||
const handleVerifySuccess = async ({ captchaVerification }: any) => {
|
const handleVerifySuccess = async ({ captchaVerification }: any) => {
|
||||||
|
|
@ -108,13 +110,13 @@ const formSchema = computed((): VbenFormSchema[] => {
|
||||||
},
|
},
|
||||||
fieldName: 'tenantId',
|
fieldName: 'tenantId',
|
||||||
label: $t('authentication.tenant'),
|
label: $t('authentication.tenant'),
|
||||||
rules: z.number().positive(),
|
rules: z.string().min(1, { message: $t('authentication.tenantTip') }),
|
||||||
dependencies: {
|
dependencies: {
|
||||||
triggerFields: ['tenantId'],
|
triggerFields: ['tenantId'],
|
||||||
if: tenantEnable,
|
if: tenantEnable,
|
||||||
trigger(values) {
|
trigger(values) {
|
||||||
if (values.tenantId) {
|
if (values.tenantId) {
|
||||||
accessStore.setTenantId(values.tenantId);
|
accessStore.setTenantId(Number(values.tenantId));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ const captchaType = 'blockPuzzle'; // 验证码类型:'blockPuzzle' | 'clickWo
|
||||||
|
|
||||||
/** 获取租户列表,并默认选中 */
|
/** 获取租户列表,并默认选中 */
|
||||||
const tenantList = ref<AuthApi.TenantResult[]>([]); // 租户列表
|
const tenantList = ref<AuthApi.TenantResult[]>([]); // 租户列表
|
||||||
const fetchTenantList = async () => {
|
async function fetchTenantList() {
|
||||||
if (!tenantEnable) {
|
if (!tenantEnable) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -66,14 +66,14 @@ const fetchTenantList = async () => {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取租户列表失败:', error);
|
console.error('获取租户列表失败:', error);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 尝试登录:当账号已经绑定,socialLogin 会直接获得 token */
|
/** 尝试登录:当账号已经绑定,socialLogin 会直接获得 token */
|
||||||
const socialType = Number(getUrlValue('type'));
|
const socialType = Number(getUrlValue('type'));
|
||||||
const redirect = getUrlValue('redirect');
|
const redirect = getUrlValue('redirect');
|
||||||
const socialCode = query?.code as string;
|
const socialCode = query?.code as string;
|
||||||
const socialState = query?.state as string;
|
const socialState = query?.state as string;
|
||||||
const tryLogin = async () => {
|
async function tryLogin() {
|
||||||
// 用于登录后,基于 redirect 的重定向
|
// 用于登录后,基于 redirect 的重定向
|
||||||
if (redirect) {
|
if (redirect) {
|
||||||
await router.replace({
|
await router.replace({
|
||||||
|
|
@ -90,10 +90,10 @@ const tryLogin = async () => {
|
||||||
code: socialCode,
|
code: socialCode,
|
||||||
state: socialState,
|
state: socialState,
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 处理登录 */
|
/** 处理登录 */
|
||||||
const handleLogin = async (values: any) => {
|
async function handleLogin(values: any) {
|
||||||
// 如果开启验证码,则先验证验证码
|
// 如果开启验证码,则先验证验证码
|
||||||
if (captchaEnable) {
|
if (captchaEnable) {
|
||||||
verifyRef.value.show();
|
verifyRef.value.show();
|
||||||
|
|
@ -107,10 +107,10 @@ const handleLogin = async (values: any) => {
|
||||||
socialCode,
|
socialCode,
|
||||||
socialState,
|
socialState,
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 验证码通过,执行登录 */
|
/** 验证码通过,执行登录 */
|
||||||
const handleVerifySuccess = async ({ captchaVerification }: any) => {
|
async function handleVerifySuccess({ captchaVerification }: any) {
|
||||||
try {
|
try {
|
||||||
await authStore.authLogin('username', {
|
await authStore.authLogin('username', {
|
||||||
...(await loginRef.value.getFormApi().getValues()),
|
...(await loginRef.value.getFormApi().getValues()),
|
||||||
|
|
@ -122,7 +122,7 @@ const handleVerifySuccess = async ({ captchaVerification }: any) => {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in handleLogin:', error);
|
console.error('Error in handleLogin:', error);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/** tricky: 配合 login.vue 中,redirectUri 需要对参数进行 encode,需要在回调后进行decode */
|
/** tricky: 配合 login.vue 中,redirectUri 需要对参数进行 encode,需要在回调后进行decode */
|
||||||
function getUrlValue(key: string): string {
|
function getUrlValue(key: string): string {
|
||||||
|
|
@ -144,19 +144,19 @@ const formSchema = computed((): VbenFormSchema[] => {
|
||||||
componentProps: {
|
componentProps: {
|
||||||
options: tenantList.value.map((item) => ({
|
options: tenantList.value.map((item) => ({
|
||||||
label: item.name,
|
label: item.name,
|
||||||
value: item.id,
|
value: item.id.toString(),
|
||||||
})),
|
})),
|
||||||
placeholder: $t('authentication.tenantTip'),
|
placeholder: $t('authentication.tenantTip'),
|
||||||
},
|
},
|
||||||
fieldName: 'tenantId',
|
fieldName: 'tenantId',
|
||||||
label: $t('authentication.tenant'),
|
label: $t('authentication.tenant'),
|
||||||
rules: z.number().positive(),
|
rules: z.string().min(1, { message: $t('authentication.tenantTip') }),
|
||||||
dependencies: {
|
dependencies: {
|
||||||
triggerFields: ['tenantId'],
|
triggerFields: ['tenantId'],
|
||||||
if: tenantEnable,
|
if: tenantEnable,
|
||||||
trigger(values) {
|
trigger(values) {
|
||||||
if (values.tenantId) {
|
if (values.tenantId) {
|
||||||
accessStore.setTenantId(values.tenantId);
|
accessStore.setTenantId(Number(values.tenantId));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ const queryParams = reactive({
|
||||||
const loading = ref(false); // 表单是否提交中
|
const loading = ref(false); // 表单是否提交中
|
||||||
|
|
||||||
/** 初始化授权信息 */
|
/** 初始化授权信息 */
|
||||||
const init = async () => {
|
async function init() {
|
||||||
// 防止在没有登录的情况下循环弹窗
|
// 防止在没有登录的情况下循环弹窗
|
||||||
if (query.client_id === undefined) {
|
if (query.client_id === undefined) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -75,10 +75,10 @@ const init = async () => {
|
||||||
'scopes',
|
'scopes',
|
||||||
scopes.filter((scope) => scope.value).map((scope) => scope.key),
|
scopes.filter((scope) => scope.value).map((scope) => scope.key),
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 处理授权的提交 */
|
/** 处理授权的提交 */
|
||||||
const handleSubmit = async (approved: boolean) => {
|
async function handleSubmit(approved: boolean) {
|
||||||
// 计算 checkedScopes + uncheckedScopes
|
// 计算 checkedScopes + uncheckedScopes
|
||||||
let checkedScopes: string[];
|
let checkedScopes: string[];
|
||||||
let uncheckedScopes: string[];
|
let uncheckedScopes: string[];
|
||||||
|
|
@ -107,7 +107,7 @@ const handleSubmit = async (approved: boolean) => {
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 调用授权 API 接口 */
|
/** 调用授权 API 接口 */
|
||||||
const doAuthorize = (
|
const doAuthorize = (
|
||||||
|
|
@ -127,7 +127,7 @@ const doAuthorize = (
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 格式化 scope 文本 */
|
/** 格式化 scope 文本 */
|
||||||
const formatScope = (scope: string) => {
|
function formatScope(scope: string) {
|
||||||
// 格式化 scope 授权范围,方便用户理解。
|
// 格式化 scope 授权范围,方便用户理解。
|
||||||
// 这里仅仅是一个 demo,可以考虑录入到字典数据中,例如说字典类型 "system_oauth2_scope",它的每个 scope 都是一条字典数据。
|
// 这里仅仅是一个 demo,可以考虑录入到字典数据中,例如说字典类型 "system_oauth2_scope",它的每个 scope 都是一条字典数据。
|
||||||
switch (scope) {
|
switch (scope) {
|
||||||
|
|
@ -141,7 +141,7 @@ const formatScope = (scope: string) => {
|
||||||
return scope;
|
return scope;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const formSchema = computed((): VbenFormSchema[] => {
|
const formSchema = computed((): VbenFormSchema[] => {
|
||||||
return [
|
return [
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,20 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { SystemUserProfileApi } from '#/api/system/user/profile';
|
import type { SystemUserProfileApi } from '#/api/system/user/profile';
|
||||||
|
|
||||||
import { Card, Tabs } from 'ant-design-vue';
|
|
||||||
import { Page } from '@vben/common-ui';
|
|
||||||
import ProfileUser from './modules/profile-user.vue';
|
|
||||||
import BaseInfo from './modules/base-info.vue';
|
|
||||||
import ResetPwd from './modules/reset-pwd.vue';
|
|
||||||
import UserSocial from './modules/user-social.vue';
|
|
||||||
|
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Card, Tabs } from 'ant-design-vue';
|
||||||
|
|
||||||
import { getUserProfile } from '#/api/system/user/profile';
|
import { getUserProfile } from '#/api/system/user/profile';
|
||||||
import { useAuthStore } from '#/store';
|
import { useAuthStore } from '#/store';
|
||||||
|
|
||||||
|
import BaseInfo from './modules/base-info.vue';
|
||||||
|
import ProfileUser from './modules/profile-user.vue';
|
||||||
|
import ResetPwd from './modules/reset-pwd.vue';
|
||||||
|
import UserSocial from './modules/user-social.vue';
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
const activeName = ref('basicInfo');
|
const activeName = ref('basicInfo');
|
||||||
|
|
||||||
|
|
@ -46,13 +49,13 @@ onMounted(loadProfile);
|
||||||
<Card class="ml-3 w-3/5">
|
<Card class="ml-3 w-3/5">
|
||||||
<Tabs v-model:active-key="activeName" class="-mt-4">
|
<Tabs v-model:active-key="activeName" class="-mt-4">
|
||||||
<Tabs.TabPane key="basicInfo" tab="基本设置">
|
<Tabs.TabPane key="basicInfo" tab="基本设置">
|
||||||
<BaseInfo :profile="profile" @success="refreshProfile" />
|
<BaseInfo :profile="profile" @success="refreshProfile" />
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
<Tabs.TabPane key="resetPwd" tab="密码设置">
|
<Tabs.TabPane key="resetPwd" tab="密码设置">
|
||||||
<ResetPwd />
|
<ResetPwd />
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
<Tabs.TabPane key="userSocial" tab="社交绑定" force-render>
|
<Tabs.TabPane key="userSocial" tab="社交绑定" force-render>
|
||||||
<UserSocial @update:active-name="activeName = $event" />
|
<UserSocial @update:active-name="activeName = $event" />
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
<!-- TODO @芋艿:在线设备 -->
|
<!-- TODO @芋艿:在线设备 -->
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,21 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Recordable } from '@vben/types';
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
import type { SystemUserProfileApi } from '#/api/system/user/profile';
|
import type { SystemUserProfileApi } from '#/api/system/user/profile';
|
||||||
|
|
||||||
|
import { watch } from 'vue';
|
||||||
|
|
||||||
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
import { watch } from 'vue';
|
|
||||||
import { useVbenForm, z } from '#/adapter/form';
|
import { useVbenForm, z } from '#/adapter/form';
|
||||||
import { DICT_TYPE, getDictOptions } from '#/utils/dict';
|
|
||||||
import { updateUserProfile } from '#/api/system/user/profile';
|
import { updateUserProfile } from '#/api/system/user/profile';
|
||||||
import { $t } from '@vben/locales';
|
import { DICT_TYPE, getDictOptions } from '#/utils/dict';
|
||||||
|
|
||||||
const props = defineProps<{ profile?: SystemUserProfileApi.UserProfileRespVO }>();
|
const props = defineProps<{
|
||||||
|
profile?: SystemUserProfileApi.UserProfileRespVO;
|
||||||
|
}>();
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'success'): void;
|
(e: 'success'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
@ -87,11 +92,15 @@ async function handleSubmit(values: Recordable<any>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 监听 profile 变化 */
|
/** 监听 profile 变化 */
|
||||||
watch(() => props.profile, (newProfile) => {
|
watch(
|
||||||
if (newProfile) {
|
() => props.profile,
|
||||||
formApi.setValues(newProfile);
|
(newProfile) => {
|
||||||
}
|
if (newProfile) {
|
||||||
}, { immediate: true });
|
formApi.setValues(newProfile);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,6 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||||
component: 'InputNumber',
|
component: 'InputNumber',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
min: 0,
|
min: 0,
|
||||||
class: 'w-full',
|
|
||||||
controlsPosition: 'right',
|
controlsPosition: 'right',
|
||||||
placeholder: '请输入分类排序',
|
placeholder: '请输入分类排序',
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,34 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { DocAlert } from '#/components/doc-alert';
|
|
||||||
import { Button } from 'ant-design-vue';
|
|
||||||
import { Page } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Button } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { DocAlert } from '#/components/doc-alert';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page>
|
<Page>
|
||||||
<DocAlert title="审批接入(流程表单)" url="https://doc.iocoder.cn/bpm/use-bpm-form/" />
|
<DocAlert
|
||||||
<Button danger type="link" target="_blank" href="https://github.com/yudaocode/yudao-ui-admin-vue3">
|
title="审批接入(流程表单)"
|
||||||
|
url="https://doc.iocoder.cn/bpm/use-bpm-form/"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
danger
|
||||||
|
type="link"
|
||||||
|
target="_blank"
|
||||||
|
href="https://github.com/yudaocode/yudao-ui-admin-vue3"
|
||||||
|
>
|
||||||
该功能支持 Vue3 + element-plus 版本!
|
该功能支持 Vue3 + element-plus 版本!
|
||||||
</Button>
|
</Button>
|
||||||
<br />
|
<br />
|
||||||
<Button type="link" target="_blank" href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/form/index">
|
<Button
|
||||||
可参考 https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/form/index 代码,pull request 贡献给我们!
|
type="link"
|
||||||
|
target="_blank"
|
||||||
|
href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/form/index"
|
||||||
|
>
|
||||||
|
可参考
|
||||||
|
https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/form/index
|
||||||
|
代码,pull request 贡献给我们!
|
||||||
</Button>
|
</Button>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,31 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { DocAlert } from '#/components/doc-alert';
|
|
||||||
import { Button } from 'ant-design-vue';
|
|
||||||
import { Page } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Button } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { DocAlert } from '#/components/doc-alert';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page>
|
<Page>
|
||||||
<DocAlert title="工作流手册" url="https://doc.iocoder.cn/bpm/" />
|
<DocAlert title="工作流手册" url="https://doc.iocoder.cn/bpm/" />
|
||||||
<Button danger type="link" target="_blank" href="https://github.com/yudaocode/yudao-ui-admin-vue3">
|
<Button
|
||||||
|
danger
|
||||||
|
type="link"
|
||||||
|
target="_blank"
|
||||||
|
href="https://github.com/yudaocode/yudao-ui-admin-vue3"
|
||||||
|
>
|
||||||
该功能支持 Vue3 + element-plus 版本!
|
该功能支持 Vue3 + element-plus 版本!
|
||||||
</Button>
|
</Button>
|
||||||
<br />
|
<br />
|
||||||
<Button type="link" target="_blank" href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/group/index">
|
<Button
|
||||||
可参考 https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/group/index 代码,pull request 贡献给我们!
|
type="link"
|
||||||
|
target="_blank"
|
||||||
|
href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/group/index"
|
||||||
|
>
|
||||||
|
可参考
|
||||||
|
https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/group/index
|
||||||
|
代码,pull request 贡献给我们!
|
||||||
</Button>
|
</Button>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,34 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { DocAlert } from '#/components/doc-alert';
|
|
||||||
import { Button } from 'ant-design-vue';
|
|
||||||
import { Page } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Button } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { DocAlert } from '#/components/doc-alert';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page>
|
<Page>
|
||||||
<DocAlert title="审批接入(业务表单)" url="https://doc.iocoder.cn/bpm/use-business-form/" />
|
<DocAlert
|
||||||
<Button danger type="link" target="_blank" href="https://github.com/yudaocode/yudao-ui-admin-vue3">
|
title="审批接入(业务表单)"
|
||||||
|
url="https://doc.iocoder.cn/bpm/use-business-form/"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
danger
|
||||||
|
type="link"
|
||||||
|
target="_blank"
|
||||||
|
href="https://github.com/yudaocode/yudao-ui-admin-vue3"
|
||||||
|
>
|
||||||
该功能支持 Vue3 + element-plus 版本!
|
该功能支持 Vue3 + element-plus 版本!
|
||||||
</Button>
|
</Button>
|
||||||
<br />
|
<br />
|
||||||
<Button type="link" target="_blank" href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/oa/leave/index">
|
<Button
|
||||||
可参考 https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/oa/leave/index 代码,pull request 贡献给我们!
|
type="link"
|
||||||
|
target="_blank"
|
||||||
|
href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/oa/leave/index"
|
||||||
|
>
|
||||||
|
可参考
|
||||||
|
https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/oa/leave/index
|
||||||
|
代码,pull request 贡献给我们!
|
||||||
</Button>
|
</Button>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,31 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { DocAlert } from '#/components/doc-alert';
|
|
||||||
import { Button } from 'ant-design-vue';
|
|
||||||
import { Page } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Button } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { DocAlert } from '#/components/doc-alert';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page>
|
<Page>
|
||||||
<DocAlert title="流程表达式" url="https://doc.iocoder.cn/bpm/expression/" />
|
<DocAlert title="流程表达式" url="https://doc.iocoder.cn/bpm/expression/" />
|
||||||
<Button danger type="link" target="_blank" href="https://github.com/yudaocode/yudao-ui-admin-vue3">
|
<Button
|
||||||
|
danger
|
||||||
|
type="link"
|
||||||
|
target="_blank"
|
||||||
|
href="https://github.com/yudaocode/yudao-ui-admin-vue3"
|
||||||
|
>
|
||||||
该功能支持 Vue3 + element-plus 版本!
|
该功能支持 Vue3 + element-plus 版本!
|
||||||
</Button>
|
</Button>
|
||||||
<br />
|
<br />
|
||||||
<Button type="link" target="_blank" href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/processExpression/index">
|
<Button
|
||||||
可参考 https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/processExpression/index 代码,pull request 贡献给我们!
|
type="link"
|
||||||
|
target="_blank"
|
||||||
|
href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/processExpression/index"
|
||||||
|
>
|
||||||
|
可参考
|
||||||
|
https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/processExpression/index
|
||||||
|
代码,pull request 贡献给我们!
|
||||||
</Button>
|
</Button>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,34 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { DocAlert } from '#/components/doc-alert';
|
|
||||||
import { Button } from 'ant-design-vue';
|
|
||||||
import { Page } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Button } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { DocAlert } from '#/components/doc-alert';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page>
|
<Page>
|
||||||
<DocAlert title="流程发起、取消、重新发起" url="https://doc.iocoder.cn/bpm/process-instance/" />
|
<DocAlert
|
||||||
<Button danger type="link" target="_blank" href="https://github.com/yudaocode/yudao-ui-admin-vue3">
|
title="流程发起、取消、重新发起"
|
||||||
|
url="https://doc.iocoder.cn/bpm/process-instance/"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
danger
|
||||||
|
type="link"
|
||||||
|
target="_blank"
|
||||||
|
href="https://github.com/yudaocode/yudao-ui-admin-vue3"
|
||||||
|
>
|
||||||
该功能支持 Vue3 + element-plus 版本!
|
该功能支持 Vue3 + element-plus 版本!
|
||||||
</Button>
|
</Button>
|
||||||
<br />
|
<br />
|
||||||
<Button type="link" target="_blank" href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/processInstance/index">
|
<Button
|
||||||
可参考 https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/processInstance/index 代码,pull request 贡献给我们!
|
type="link"
|
||||||
|
target="_blank"
|
||||||
|
href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/processInstance/index"
|
||||||
|
>
|
||||||
|
可参考
|
||||||
|
https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/processInstance/index
|
||||||
|
代码,pull request 贡献给我们!
|
||||||
</Button>
|
</Button>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,31 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { DocAlert } from '#/components/doc-alert';
|
|
||||||
import { Button } from 'ant-design-vue';
|
|
||||||
import { Page } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Button } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { DocAlert } from '#/components/doc-alert';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page>
|
<Page>
|
||||||
<DocAlert title="工作流手册" url="https://doc.iocoder.cn/bpm/" />
|
<DocAlert title="工作流手册" url="https://doc.iocoder.cn/bpm/" />
|
||||||
<Button danger type="link" target="_blank" href="https://github.com/yudaocode/yudao-ui-admin-vue3">
|
<Button
|
||||||
|
danger
|
||||||
|
type="link"
|
||||||
|
target="_blank"
|
||||||
|
href="https://github.com/yudaocode/yudao-ui-admin-vue3"
|
||||||
|
>
|
||||||
该功能支持 Vue3 + element-plus 版本!
|
该功能支持 Vue3 + element-plus 版本!
|
||||||
</Button>
|
</Button>
|
||||||
<br />
|
<br />
|
||||||
<Button type="link" target="_blank" href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/processInstance/manager/index">
|
<Button
|
||||||
可参考 https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/processInstance/manager/index 代码,pull request 贡献给我们!
|
type="link"
|
||||||
|
target="_blank"
|
||||||
|
href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/processInstance/manager/index"
|
||||||
|
>
|
||||||
|
可参考
|
||||||
|
https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/processInstance/manager/index
|
||||||
|
代码,pull request 贡献给我们!
|
||||||
</Button>
|
</Button>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,34 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { DocAlert } from '#/components/doc-alert';
|
|
||||||
import { Button } from 'ant-design-vue';
|
|
||||||
import { Page } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Button } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { DocAlert } from '#/components/doc-alert';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page>
|
<Page>
|
||||||
<DocAlert title="执行监听器、任务监听器" url="https://doc.iocoder.cn/bpm/listener/" />
|
<DocAlert
|
||||||
<Button danger type="link" target="_blank" href="https://github.com/yudaocode/yudao-ui-admin-vue3">
|
title="执行监听器、任务监听器"
|
||||||
|
url="https://doc.iocoder.cn/bpm/listener/"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
danger
|
||||||
|
type="link"
|
||||||
|
target="_blank"
|
||||||
|
href="https://github.com/yudaocode/yudao-ui-admin-vue3"
|
||||||
|
>
|
||||||
该功能支持 Vue3 + element-plus 版本!
|
该功能支持 Vue3 + element-plus 版本!
|
||||||
</Button>
|
</Button>
|
||||||
<br />
|
<br />
|
||||||
<Button type="link" target="_blank" href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/processListener/index">
|
<Button
|
||||||
可参考 https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/processListener/index 代码,pull request 贡献给我们!
|
type="link"
|
||||||
|
target="_blank"
|
||||||
|
href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/processListener/index"
|
||||||
|
>
|
||||||
|
可参考
|
||||||
|
https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/processListener/index
|
||||||
|
代码,pull request 贡献给我们!
|
||||||
</Button>
|
</Button>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,34 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { DocAlert } from '#/components/doc-alert';
|
|
||||||
import { Button } from 'ant-design-vue';
|
|
||||||
import { Page } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Button } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { DocAlert } from '#/components/doc-alert';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page>
|
<Page>
|
||||||
<DocAlert title="审批转办、委派、抄送" url="https://doc.iocoder.cn/bpm/task-delegation-and-cc/" />
|
<DocAlert
|
||||||
<Button danger type="link" target="_blank" href="https://github.com/yudaocode/yudao-ui-admin-vue3">
|
title="审批转办、委派、抄送"
|
||||||
|
url="https://doc.iocoder.cn/bpm/task-delegation-and-cc/"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
danger
|
||||||
|
type="link"
|
||||||
|
target="_blank"
|
||||||
|
href="https://github.com/yudaocode/yudao-ui-admin-vue3"
|
||||||
|
>
|
||||||
该功能支持 Vue3 + element-plus 版本!
|
该功能支持 Vue3 + element-plus 版本!
|
||||||
</Button>
|
</Button>
|
||||||
<br />
|
<br />
|
||||||
<Button type="link" target="_blank" href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/task/copy/index">
|
<Button
|
||||||
可参考 https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/task/copy/index 代码,pull request 贡献给我们!
|
type="link"
|
||||||
|
target="_blank"
|
||||||
|
href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/task/copy/index"
|
||||||
|
>
|
||||||
|
可参考
|
||||||
|
https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/task/copy/index
|
||||||
|
代码,pull request 贡献给我们!
|
||||||
</Button>
|
</Button>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,40 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { DocAlert } from '#/components/doc-alert';
|
|
||||||
import { Button } from 'ant-design-vue';
|
|
||||||
import { Page } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Button } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { DocAlert } from '#/components/doc-alert';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page>
|
<Page>
|
||||||
<DocAlert title="审批通过、不通过、驳回" url="https://doc.iocoder.cn/bpm/task-todo-done/" />
|
<DocAlert
|
||||||
|
title="审批通过、不通过、驳回"
|
||||||
|
url="https://doc.iocoder.cn/bpm/task-todo-done/"
|
||||||
|
/>
|
||||||
<DocAlert title="审批加签、减签" url="https://doc.iocoder.cn/bpm/sign/" />
|
<DocAlert title="审批加签、减签" url="https://doc.iocoder.cn/bpm/sign/" />
|
||||||
<DocAlert title="审批转办、委派、抄送" url="https://doc.iocoder.cn/bpm/task-delegation-and-cc/" />
|
<DocAlert
|
||||||
|
title="审批转办、委派、抄送"
|
||||||
|
url="https://doc.iocoder.cn/bpm/task-delegation-and-cc/"
|
||||||
|
/>
|
||||||
<DocAlert title="审批加签、减签" url="https://doc.iocoder.cn/bpm/sign/" />
|
<DocAlert title="审批加签、减签" url="https://doc.iocoder.cn/bpm/sign/" />
|
||||||
<Button danger type="link" target="_blank" href="https://github.com/yudaocode/yudao-ui-admin-vue3">
|
<Button
|
||||||
|
danger
|
||||||
|
type="link"
|
||||||
|
target="_blank"
|
||||||
|
href="https://github.com/yudaocode/yudao-ui-admin-vue3"
|
||||||
|
>
|
||||||
该功能支持 Vue3 + element-plus 版本!
|
该功能支持 Vue3 + element-plus 版本!
|
||||||
</Button>
|
</Button>
|
||||||
<br />
|
<br />
|
||||||
<Button type="link" target="_blank" href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/task/done/index">
|
<Button
|
||||||
可参考 https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/task/done/index 代码,pull request 贡献给我们!
|
type="link"
|
||||||
|
target="_blank"
|
||||||
|
href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/task/done/index"
|
||||||
|
>
|
||||||
|
可参考
|
||||||
|
https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/task/done/index
|
||||||
|
代码,pull request 贡献给我们!
|
||||||
</Button>
|
</Button>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,31 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { DocAlert } from '#/components/doc-alert';
|
|
||||||
import { Button } from 'ant-design-vue';
|
|
||||||
import { Page } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Button } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { DocAlert } from '#/components/doc-alert';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page>
|
<Page>
|
||||||
<DocAlert title="工作流手册" url="https://doc.iocoder.cn/bpm/" />
|
<DocAlert title="工作流手册" url="https://doc.iocoder.cn/bpm/" />
|
||||||
<Button danger type="link" target="_blank" href="https://github.com/yudaocode/yudao-ui-admin-vue3">
|
<Button
|
||||||
|
danger
|
||||||
|
type="link"
|
||||||
|
target="_blank"
|
||||||
|
href="https://github.com/yudaocode/yudao-ui-admin-vue3"
|
||||||
|
>
|
||||||
该功能支持 Vue3 + element-plus 版本!
|
该功能支持 Vue3 + element-plus 版本!
|
||||||
</Button>
|
</Button>
|
||||||
<br />
|
<br />
|
||||||
<Button type="link" target="_blank" href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/task/manager/index">
|
<Button
|
||||||
可参考 https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/task/manager/index 代码,pull request 贡献给我们!
|
type="link"
|
||||||
|
target="_blank"
|
||||||
|
href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/task/manager/index"
|
||||||
|
>
|
||||||
|
可参考
|
||||||
|
https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/task/manager/index
|
||||||
|
代码,pull request 贡献给我们!
|
||||||
</Button>
|
</Button>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,40 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { DocAlert } from '#/components/doc-alert';
|
|
||||||
import { Button } from 'ant-design-vue';
|
|
||||||
import { Page } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Button } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { DocAlert } from '#/components/doc-alert';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page>
|
<Page>
|
||||||
<DocAlert title="审批通过、不通过、驳回" url="https://doc.iocoder.cn/bpm/task-todo-done/" />
|
<DocAlert
|
||||||
|
title="审批通过、不通过、驳回"
|
||||||
|
url="https://doc.iocoder.cn/bpm/task-todo-done/"
|
||||||
|
/>
|
||||||
<DocAlert title="审批加签、减签" url="https://doc.iocoder.cn/bpm/sign/" />
|
<DocAlert title="审批加签、减签" url="https://doc.iocoder.cn/bpm/sign/" />
|
||||||
<DocAlert title="审批转办、委派、抄送" url="https://doc.iocoder.cn/bpm/task-delegation-and-cc/" />
|
<DocAlert
|
||||||
|
title="审批转办、委派、抄送"
|
||||||
|
url="https://doc.iocoder.cn/bpm/task-delegation-and-cc/"
|
||||||
|
/>
|
||||||
<DocAlert title="审批加签、减签" url="https://doc.iocoder.cn/bpm/sign/" />
|
<DocAlert title="审批加签、减签" url="https://doc.iocoder.cn/bpm/sign/" />
|
||||||
<Button danger type="link" target="_blank" href="https://github.com/yudaocode/yudao-ui-admin-vue3">
|
<Button
|
||||||
|
danger
|
||||||
|
type="link"
|
||||||
|
target="_blank"
|
||||||
|
href="https://github.com/yudaocode/yudao-ui-admin-vue3"
|
||||||
|
>
|
||||||
该功能支持 Vue3 + element-plus 版本!
|
该功能支持 Vue3 + element-plus 版本!
|
||||||
</Button>
|
</Button>
|
||||||
<br />
|
<br />
|
||||||
<Button type="link" target="_blank" href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/task/todo/index">
|
<Button
|
||||||
可参考 https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/task/todo/index 代码,pull request 贡献给我们!
|
type="link"
|
||||||
|
target="_blank"
|
||||||
|
href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/task/todo/index"
|
||||||
|
>
|
||||||
|
可参考
|
||||||
|
https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/bpm/task/todo/index
|
||||||
|
代码,pull request 贡献给我们!
|
||||||
</Button>
|
</Button>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,7 @@ const todoItems = ref<WorkbenchTodoItem[]>([
|
||||||
content: `国内使用最广泛的快速开发平台,远超 10w+ 企业使用`,
|
content: `国内使用最广泛的快速开发平台,远超 10w+ 企业使用`,
|
||||||
date: '2024-07-10 11:15:00',
|
date: '2024-07-10 11:15:00',
|
||||||
title: '广泛企业认可',
|
title: '广泛企业认可',
|
||||||
}
|
},
|
||||||
]);
|
]);
|
||||||
const trendItems: WorkbenchTrendItem[] = [
|
const trendItems: WorkbenchTrendItem[] = [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,9 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page auto-content-height>
|
<Page auto-content-height>
|
||||||
<DocAlert title="系统日志" url="https://doc.iocoder.cn/system-log/" />
|
<template #doc>
|
||||||
|
<DocAlert title="系统日志" url="https://doc.iocoder.cn/system-log/" />
|
||||||
|
</template>
|
||||||
|
|
||||||
<DetailModal @success="onRefresh" />
|
<DetailModal @success="onRefresh" />
|
||||||
<Grid table-title="API 访问日志列表">
|
<Grid table-title="API 访问日志列表">
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,9 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page auto-content-height>
|
<Page auto-content-height>
|
||||||
<DocAlert title="系统日志" url="https://doc.iocoder.cn/system-log/" />
|
<template #doc>
|
||||||
|
<DocAlert title="系统日志" url="https://doc.iocoder.cn/system-log/" />
|
||||||
|
</template>
|
||||||
|
|
||||||
<DetailModal @success="onRefresh" />
|
<DetailModal @success="onRefresh" />
|
||||||
<Grid table-title="API 错误日志列表">
|
<Grid table-title="API 错误日志列表">
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,182 @@
|
||||||
|
<!-- eslint-disable no-useless-escape -->
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, ref, unref } from 'vue';
|
||||||
|
|
||||||
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
import { isString } from '@vben/utils';
|
||||||
|
|
||||||
|
import formCreate from '@form-create/ant-design-vue';
|
||||||
|
import FcDesigner from '@form-create/antd-designer';
|
||||||
|
import { useClipboard } from '@vueuse/core';
|
||||||
|
import { Button, message } from 'ant-design-vue';
|
||||||
|
import hljs from 'highlight.js';
|
||||||
|
import xml from 'highlight.js/lib/languages/java';
|
||||||
|
import json from 'highlight.js/lib/languages/json';
|
||||||
|
|
||||||
|
import { useFormCreateDesigner } from '#/components/form-create';
|
||||||
|
|
||||||
|
import 'highlight.js/styles/github.css';
|
||||||
|
|
||||||
|
defineOptions({ name: 'InfraBuild' });
|
||||||
|
|
||||||
|
const [Modal, modalApi] = useVbenModal();
|
||||||
|
|
||||||
|
const designer = ref(); // 表单设计器
|
||||||
|
|
||||||
|
// 表单设计器配置
|
||||||
|
const designerConfig = ref({
|
||||||
|
switchType: [], // 是否可以切换组件类型,或者可以相互切换的字段
|
||||||
|
autoActive: true, // 是否自动选中拖入的组件
|
||||||
|
useTemplate: false, // 是否生成vue2语法的模板组件
|
||||||
|
formOptions: {
|
||||||
|
form: {
|
||||||
|
labelWidth: '100px', // 设置默认的 label 宽度为 100px
|
||||||
|
},
|
||||||
|
}, // 定义表单配置默认值
|
||||||
|
fieldReadonly: false, // 配置field是否可以编辑
|
||||||
|
hiddenDragMenu: false, // 隐藏拖拽操作按钮
|
||||||
|
hiddenDragBtn: false, // 隐藏拖拽按钮
|
||||||
|
hiddenMenu: [], // 隐藏部分菜单
|
||||||
|
hiddenItem: [], // 隐藏部分组件
|
||||||
|
hiddenItemConfig: {}, // 隐藏组件的部分配置项
|
||||||
|
disabledItemConfig: {}, // 禁用组件的部分配置项
|
||||||
|
showSaveBtn: false, // 是否显示保存按钮
|
||||||
|
showConfig: true, // 是否显示右侧的配置界面
|
||||||
|
showBaseForm: true, // 是否显示组件的基础配置表单
|
||||||
|
showControl: true, // 是否显示组件联动
|
||||||
|
showPropsForm: true, // 是否显示组件的属性配置表单
|
||||||
|
showEventForm: true, // 是否显示组件的事件配置表单
|
||||||
|
showValidateForm: true, // 是否显示组件的验证配置表单
|
||||||
|
showFormConfig: true, // 是否显示表单配置
|
||||||
|
showInputData: true, // 是否显示录入按钮
|
||||||
|
showDevice: true, // 是否显示多端适配选项
|
||||||
|
appendConfigData: [], // 定义渲染规则所需的formData
|
||||||
|
});
|
||||||
|
|
||||||
|
const dialogVisible = ref(false); // 弹窗的是否展示
|
||||||
|
const dialogTitle = ref(''); // 弹窗的标题
|
||||||
|
const formType = ref(-1); // 表单的类型:0 - 生成 JSON;1 - 生成 Options;2 - 生成组件
|
||||||
|
const formData = ref(''); // 表单数据
|
||||||
|
useFormCreateDesigner(designer); // 表单设计器增强
|
||||||
|
|
||||||
|
/** 打开弹窗 */
|
||||||
|
const openModel = (title: string) => {
|
||||||
|
dialogVisible.value = true;
|
||||||
|
dialogTitle.value = title;
|
||||||
|
modalApi.open();
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 生成 JSON */
|
||||||
|
const showJson = () => {
|
||||||
|
openModel('生成 JSON');
|
||||||
|
formType.value = 0;
|
||||||
|
formData.value = designer.value.getRule();
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 生成 Options */
|
||||||
|
const showOption = () => {
|
||||||
|
openModel('生成 Options');
|
||||||
|
formType.value = 1;
|
||||||
|
formData.value = designer.value.getOption();
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 生成组件 */
|
||||||
|
const showTemplate = () => {
|
||||||
|
openModel('生成组件');
|
||||||
|
formType.value = 2;
|
||||||
|
formData.value = makeTemplate();
|
||||||
|
};
|
||||||
|
|
||||||
|
const makeTemplate = () => {
|
||||||
|
const rule = designer.value.getRule();
|
||||||
|
const opt = designer.value.getOption();
|
||||||
|
return `<template>
|
||||||
|
<form-create
|
||||||
|
v-model:api="fApi"
|
||||||
|
:rule="rule"
|
||||||
|
:option="option"
|
||||||
|
@submit="onSubmit"
|
||||||
|
></form-create>
|
||||||
|
</template>
|
||||||
|
<script setup lang=ts>
|
||||||
|
const faps = ref(null)
|
||||||
|
const rule = ref('')
|
||||||
|
const option = ref('')
|
||||||
|
const init = () => {
|
||||||
|
rule.value = formCreate.parseJson('${formCreate.toJson(rule).replaceAll('\\', '\\\\')}')
|
||||||
|
option.value = formCreate.parseJson('${JSON.stringify(opt, null, 2)}')
|
||||||
|
}
|
||||||
|
const onSubmit = (formData) => {
|
||||||
|
//todo 提交表单
|
||||||
|
}
|
||||||
|
init()
|
||||||
|
<\/script>`;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 复制 */
|
||||||
|
const copy = async (text: string) => {
|
||||||
|
const textToCopy = JSON.stringify(text, null, 2);
|
||||||
|
const { copy, copied, isSupported } = useClipboard({ source: textToCopy });
|
||||||
|
if (isSupported) {
|
||||||
|
await copy();
|
||||||
|
if (unref(copied)) {
|
||||||
|
message.success('复制成功');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
message.error('复制失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代码高亮
|
||||||
|
*/
|
||||||
|
const highlightedCode = (code: string) => {
|
||||||
|
// 处理语言和代码
|
||||||
|
let language = 'json';
|
||||||
|
if (formType.value === 2) {
|
||||||
|
language = 'xml';
|
||||||
|
}
|
||||||
|
// debugger
|
||||||
|
if (!isString(code)) {
|
||||||
|
code = JSON.stringify(code, null, 2);
|
||||||
|
}
|
||||||
|
// 高亮
|
||||||
|
const result = hljs.highlight(code, { language, ignoreIllegals: true });
|
||||||
|
return result.value || ' ';
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 初始化 */
|
||||||
|
onMounted(async () => {
|
||||||
|
// 注册代码高亮的各种语言
|
||||||
|
hljs.registerLanguage('xml', xml);
|
||||||
|
hljs.registerLanguage('json', json);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<FcDesigner ref="designer" height="90vh" :config="designerConfig">
|
||||||
|
<template #handle>
|
||||||
|
<Button size="small" type="primary" ghost @click="showJson">
|
||||||
|
生成JSON
|
||||||
|
</Button>
|
||||||
|
<Button size="small" type="primary" ghost @click="showOption">
|
||||||
|
生成Options
|
||||||
|
</Button>
|
||||||
|
<Button size="small" type="primary" ghost @click="showTemplate">
|
||||||
|
生成组件
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
</FcDesigner>
|
||||||
|
|
||||||
|
<!-- 弹窗:表单预览 -->
|
||||||
|
<Modal :title="dialogTitle" :footer="false" :fullscreen-button="false">
|
||||||
|
<div>
|
||||||
|
<Button style="float: right" @click="copy(formData)"> 复制 </Button>
|
||||||
|
<div>
|
||||||
|
<pre><code v-dompurify-html="highlightedCode(formData)" class="hljs"></code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
|
|
@ -6,11 +6,12 @@ import type {
|
||||||
import type { InfraCodegenApi } from '#/api/infra/codegen';
|
import type { InfraCodegenApi } from '#/api/infra/codegen';
|
||||||
import type { InfraDataSourceConfigApi } from '#/api/infra/data-source-config';
|
import type { InfraDataSourceConfigApi } from '#/api/infra/data-source-config';
|
||||||
|
|
||||||
import { DocAlert } from '#/components/doc-alert';
|
import { ref } from 'vue';
|
||||||
import ImportTable from './modules/import-table.vue';
|
import { useRouter } from 'vue-router';
|
||||||
import PreviewCode from './modules/preview-code.vue';
|
|
||||||
import { Page, useVbenModal } from '@vben/common-ui';
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
import { Plus } from '@vben/icons';
|
import { Plus } from '@vben/icons';
|
||||||
|
|
||||||
import { Button, message } from 'ant-design-vue';
|
import { Button, message } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
|
@ -21,12 +22,12 @@ import {
|
||||||
syncCodegenFromDB,
|
syncCodegenFromDB,
|
||||||
} from '#/api/infra/codegen';
|
} from '#/api/infra/codegen';
|
||||||
import { getDataSourceConfigList } from '#/api/infra/data-source-config';
|
import { getDataSourceConfigList } from '#/api/infra/data-source-config';
|
||||||
|
import { DocAlert } from '#/components/doc-alert';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
import { ref } from 'vue';
|
|
||||||
|
|
||||||
import { useGridColumns, useGridFormSchema } from './data';
|
import { useGridColumns, useGridFormSchema } from './data';
|
||||||
|
import ImportTable from './modules/import-table.vue';
|
||||||
import { useRouter } from 'vue-router';
|
import PreviewCode from './modules/preview-code.vue';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const dataSourceConfigList = ref<InfraDataSourceConfigApi.DataSourceConfig[]>(
|
const dataSourceConfigList = ref<InfraDataSourceConfigApi.DataSourceConfig[]>(
|
||||||
|
|
@ -139,14 +140,14 @@ function onActionClick({
|
||||||
row,
|
row,
|
||||||
}: OnActionClickParams<InfraCodegenApi.CodegenTable>) {
|
}: OnActionClickParams<InfraCodegenApi.CodegenTable>) {
|
||||||
switch (code) {
|
switch (code) {
|
||||||
case 'edit': {
|
|
||||||
onEdit(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'delete': {
|
case 'delete': {
|
||||||
onDelete(row);
|
onDelete(row);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'edit': {
|
||||||
|
onEdit(row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'generate': {
|
case 'generate': {
|
||||||
onGenerate(row);
|
onGenerate(row);
|
||||||
break;
|
break;
|
||||||
|
|
@ -205,19 +206,21 @@ initDataSourceConfig();
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<Page auto-content-height>
|
<Page auto-content-height>
|
||||||
<DocAlert
|
<template #doc>
|
||||||
title="代码生成(单表)"
|
<DocAlert
|
||||||
url="https://doc.iocoder.cn/new-feature/"
|
title="代码生成(单表)"
|
||||||
/>
|
url="https://doc.iocoder.cn/new-feature/"
|
||||||
<DocAlert
|
/>
|
||||||
title="代码生成(树表)"
|
<DocAlert
|
||||||
url="https://doc.iocoder.cn/new-feature/tree/"
|
title="代码生成(树表)"
|
||||||
/>
|
url="https://doc.iocoder.cn/new-feature/tree/"
|
||||||
<DocAlert
|
/>
|
||||||
title="代码生成(主子表)"
|
<DocAlert
|
||||||
url="https://doc.iocoder.cn/new-feature/master-sub/"
|
title="代码生成(主子表)"
|
||||||
/>
|
url="https://doc.iocoder.cn/new-feature/master-sub/"
|
||||||
<DocAlert title="单元测试" url="https://doc.iocoder.cn/unit-test/" />
|
/>
|
||||||
|
<DocAlert title="单元测试" url="https://doc.iocoder.cn/unit-test/" />
|
||||||
|
</template>
|
||||||
|
|
||||||
<ImportModal @success="onRefresh" />
|
<ImportModal @success="onRefresh" />
|
||||||
<PreviewModal />
|
<PreviewModal />
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { InfraCodegenApi } from '#/api/infra/codegen';
|
import type { InfraCodegenApi } from '#/api/infra/codegen';
|
||||||
|
|
||||||
import { useVbenForm } from '#/adapter/form';
|
|
||||||
import { watch } from 'vue';
|
import { watch } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
|
||||||
import { useBasicInfoFormSchema } from '../data';
|
import { useBasicInfoFormSchema } from '../data';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,12 @@
|
||||||
import type { InfraCodegenApi } from '#/api/infra/codegen';
|
import type { InfraCodegenApi } from '#/api/infra/codegen';
|
||||||
import type { SystemDictTypeApi } from '#/api/system/dict/type';
|
import type { SystemDictTypeApi } from '#/api/system/dict/type';
|
||||||
|
|
||||||
|
import { nextTick, onMounted, ref, watch } from 'vue';
|
||||||
|
|
||||||
import { Checkbox, Input, Select } from 'ant-design-vue';
|
import { Checkbox, Input, Select } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import { getSimpleDictTypeList } from '#/api/system/dict/type';
|
import { getSimpleDictTypeList } from '#/api/system/dict/type';
|
||||||
import { nextTick, onMounted, ref, watch } from 'vue';
|
|
||||||
|
|
||||||
import { useCodegenColumnTableColumns } from '../data';
|
import { useCodegenColumnTableColumns } from '../data';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,13 @@ const getTitle = computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const [Form, formApi] = useVbenForm({
|
const [Form, formApi] = useVbenForm({
|
||||||
|
commonConfig: {
|
||||||
|
componentProps: {
|
||||||
|
class: 'w-full',
|
||||||
|
},
|
||||||
|
formItemClass: 'col-span-2',
|
||||||
|
labelWidth: 80,
|
||||||
|
},
|
||||||
layout: 'horizontal',
|
layout: 'horizontal',
|
||||||
schema: useFormSchema(),
|
schema: useFormSchema(),
|
||||||
showDefaultActions: false,
|
showDefaultActions: false,
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,13 @@ const getTitle = computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const [Form, formApi] = useVbenForm({
|
const [Form, formApi] = useVbenForm({
|
||||||
|
commonConfig: {
|
||||||
|
componentProps: {
|
||||||
|
class: 'w-full',
|
||||||
|
},
|
||||||
|
formItemClass: 'col-span-2',
|
||||||
|
labelWidth: 80,
|
||||||
|
},
|
||||||
layout: 'horizontal',
|
layout: 'horizontal',
|
||||||
schema: useFormSchema(),
|
schema: useFormSchema(),
|
||||||
showDefaultActions: false,
|
showDefaultActions: false,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
|
|
||||||
|
|
||||||
import type { VbenFormSchema } from '#/adapter/form';
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
import type { OnActionClickFn } from '#/adapter/vxe-table';
|
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { Demo01ContactApi } from '#/api/infra/demo/demo01';
|
import type { Demo01ContactApi } from '#/api/infra/demo/demo01';
|
||||||
|
|
||||||
import { useAccess } from '@vben/access';
|
import { useAccess } from '@vben/access';
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,13 @@ const getTitle = computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const [Form, formApi] = useVbenForm({
|
const [Form, formApi] = useVbenForm({
|
||||||
|
commonConfig: {
|
||||||
|
componentProps: {
|
||||||
|
class: 'w-full',
|
||||||
|
},
|
||||||
|
formItemClass: 'col-span-2',
|
||||||
|
labelWidth: 80,
|
||||||
|
},
|
||||||
layout: 'horizontal',
|
layout: 'horizontal',
|
||||||
schema: useFormSchema(),
|
schema: useFormSchema(),
|
||||||
showDefaultActions: false,
|
showDefaultActions: false,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
|
|
||||||
|
|
||||||
import type { VbenFormSchema } from '#/adapter/form';
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
import type { OnActionClickFn } from '#/adapter/vxe-table';
|
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { Demo02CategoryApi } from '#/api/infra/demo/demo02';
|
import type { Demo02CategoryApi } from '#/api/infra/demo/demo02';
|
||||||
|
|
||||||
import { useAccess } from '@vben/access';
|
import { useAccess } from '@vben/access';
|
||||||
|
|
@ -36,7 +34,6 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||||
});
|
});
|
||||||
return handleTree(data);
|
return handleTree(data);
|
||||||
},
|
},
|
||||||
class: 'w-full',
|
|
||||||
labelField: 'name',
|
labelField: 'name',
|
||||||
valueField: 'id',
|
valueField: 'id',
|
||||||
childrenField: 'children',
|
childrenField: 'children',
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,13 @@ const getTitle = computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const [Form, formApi] = useVbenForm({
|
const [Form, formApi] = useVbenForm({
|
||||||
|
commonConfig: {
|
||||||
|
componentProps: {
|
||||||
|
class: 'w-full',
|
||||||
|
},
|
||||||
|
formItemClass: 'col-span-2',
|
||||||
|
labelWidth: 80,
|
||||||
|
},
|
||||||
layout: 'horizontal',
|
layout: 'horizontal',
|
||||||
schema: useFormSchema(),
|
schema: useFormSchema(),
|
||||||
showDefaultActions: false,
|
showDefaultActions: false,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
|
|
||||||
|
|
||||||
import type { VbenFormSchema } from '#/adapter/form';
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
import type { OnActionClickFn } from '#/adapter/vxe-table';
|
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/erp';
|
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/erp';
|
||||||
|
|
||||||
import { useAccess } from '@vben/access';
|
import { useAccess } from '@vben/access';
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,13 @@ const getTitle = computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const [Form, formApi] = useVbenForm({
|
const [Form, formApi] = useVbenForm({
|
||||||
|
commonConfig: {
|
||||||
|
componentProps: {
|
||||||
|
class: 'w-full',
|
||||||
|
},
|
||||||
|
formItemClass: 'col-span-2',
|
||||||
|
labelWidth: 80,
|
||||||
|
},
|
||||||
layout: 'horizontal',
|
layout: 'horizontal',
|
||||||
schema: useDemo03CourseFormSchema(),
|
schema: useDemo03CourseFormSchema(),
|
||||||
showDefaultActions: false,
|
showDefaultActions: false,
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,13 @@ const getTitle = computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const [Form, formApi] = useVbenForm({
|
const [Form, formApi] = useVbenForm({
|
||||||
|
commonConfig: {
|
||||||
|
componentProps: {
|
||||||
|
class: 'w-full',
|
||||||
|
},
|
||||||
|
formItemClass: 'col-span-2',
|
||||||
|
labelWidth: 80,
|
||||||
|
},
|
||||||
layout: 'horizontal',
|
layout: 'horizontal',
|
||||||
schema: useDemo03GradeFormSchema(),
|
schema: useDemo03GradeFormSchema(),
|
||||||
showDefaultActions: false,
|
showDefaultActions: false,
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,13 @@ const getTitle = computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const [Form, formApi] = useVbenForm({
|
const [Form, formApi] = useVbenForm({
|
||||||
|
commonConfig: {
|
||||||
|
componentProps: {
|
||||||
|
class: 'w-full',
|
||||||
|
},
|
||||||
|
formItemClass: 'col-span-2',
|
||||||
|
labelWidth: 80,
|
||||||
|
},
|
||||||
layout: 'horizontal',
|
layout: 'horizontal',
|
||||||
schema: useFormSchema(),
|
schema: useFormSchema(),
|
||||||
showDefaultActions: false,
|
showDefaultActions: false,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
|
|
||||||
|
|
||||||
import type { VbenFormSchema } from '#/adapter/form';
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
import type { OnActionClickFn } from '#/adapter/vxe-table';
|
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/inner';
|
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/inner';
|
||||||
|
|
||||||
import { useAccess } from '@vben/access';
|
import { useAccess } from '@vben/access';
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,13 @@ const demo03CourseFormRef = ref<InstanceType<typeof Demo03CourseForm>>();
|
||||||
const demo03GradeFormRef = ref<InstanceType<typeof Demo03GradeForm>>();
|
const demo03GradeFormRef = ref<InstanceType<typeof Demo03GradeForm>>();
|
||||||
|
|
||||||
const [Form, formApi] = useVbenForm({
|
const [Form, formApi] = useVbenForm({
|
||||||
|
commonConfig: {
|
||||||
|
componentProps: {
|
||||||
|
class: 'w-full',
|
||||||
|
},
|
||||||
|
formItemClass: 'col-span-2',
|
||||||
|
labelWidth: 80,
|
||||||
|
},
|
||||||
layout: 'horizontal',
|
layout: 'horizontal',
|
||||||
schema: useFormSchema(),
|
schema: useFormSchema(),
|
||||||
showDefaultActions: false,
|
showDefaultActions: false,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
|
|
||||||
|
|
||||||
import type { VbenFormSchema } from '#/adapter/form';
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
import type { OnActionClickFn } from '#/adapter/vxe-table';
|
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/normal';
|
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/normal';
|
||||||
|
|
||||||
import { useAccess } from '@vben/access';
|
import { useAccess } from '@vben/access';
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,13 @@ const demo03CourseFormRef = ref<InstanceType<typeof Demo03CourseForm>>();
|
||||||
const demo03GradeFormRef = ref<InstanceType<typeof Demo03GradeForm>>();
|
const demo03GradeFormRef = ref<InstanceType<typeof Demo03GradeForm>>();
|
||||||
|
|
||||||
const [Form, formApi] = useVbenForm({
|
const [Form, formApi] = useVbenForm({
|
||||||
|
commonConfig: {
|
||||||
|
componentProps: {
|
||||||
|
class: 'w-full',
|
||||||
|
},
|
||||||
|
formItemClass: 'col-span-2',
|
||||||
|
labelWidth: 80,
|
||||||
|
},
|
||||||
layout: 'horizontal',
|
layout: 'horizontal',
|
||||||
schema: useFormSchema(),
|
schema: useFormSchema(),
|
||||||
showDefaultActions: false,
|
showDefaultActions: false,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,267 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { Demo01ContactApi } from '#/api/infra/demo/demo01';
|
||||||
|
|
||||||
|
import { h, onMounted, reactive, ref } from 'vue';
|
||||||
|
|
||||||
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
import { Download, Plus, RefreshCw, Search } from '@vben/icons';
|
||||||
|
import { cloneDeep, formatDateTime } from '@vben/utils';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Form,
|
||||||
|
Input,
|
||||||
|
message,
|
||||||
|
Pagination,
|
||||||
|
RangePicker,
|
||||||
|
Select,
|
||||||
|
} from 'ant-design-vue';
|
||||||
|
import { VxeColumn, VxeTable } from 'vxe-table';
|
||||||
|
|
||||||
|
import {
|
||||||
|
deleteDemo01Contact,
|
||||||
|
exportDemo01Contact,
|
||||||
|
getDemo01ContactPage,
|
||||||
|
} from '#/api/infra/demo/demo01';
|
||||||
|
import { ContentWrap } from '#/components/content-wrap';
|
||||||
|
import { DictTag } from '#/components/dict-tag';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
import { getRangePickerDefaultProps } from '#/utils/date';
|
||||||
|
import { DICT_TYPE, getDictOptions } from '#/utils/dict';
|
||||||
|
import { downloadByData } from '#/utils/download';
|
||||||
|
|
||||||
|
import Demo01ContactForm from './modules/form.vue';
|
||||||
|
|
||||||
|
const loading = ref(true); // 列表的加载中
|
||||||
|
const list = ref<Demo01ContactApi.Demo01Contact[]>([]); // 列表的数据
|
||||||
|
const total = ref(0); // 列表的总页数
|
||||||
|
const queryParams = reactive({
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
name: undefined,
|
||||||
|
sex: undefined,
|
||||||
|
createTime: undefined,
|
||||||
|
});
|
||||||
|
const queryFormRef = ref(); // 搜索的表单
|
||||||
|
const exportLoading = ref(false); // 导出的加载中
|
||||||
|
|
||||||
|
/** 查询列表 */
|
||||||
|
const getList = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const params = cloneDeep(queryParams) as any;
|
||||||
|
if (params.createTime && Array.isArray(params.createTime)) {
|
||||||
|
params.createTime = (params.createTime as string[]).join(',');
|
||||||
|
}
|
||||||
|
const data = await getDemo01ContactPage(params);
|
||||||
|
list.value = data.list;
|
||||||
|
total.value = data.total;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 搜索按钮操作 */
|
||||||
|
const handleQuery = () => {
|
||||||
|
queryParams.pageNo = 1;
|
||||||
|
getList();
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 重置按钮操作 */
|
||||||
|
const resetQuery = () => {
|
||||||
|
queryFormRef.value.resetFields();
|
||||||
|
handleQuery();
|
||||||
|
};
|
||||||
|
|
||||||
|
const [FormModal, formModalApi] = useVbenModal({
|
||||||
|
connectedComponent: Demo01ContactForm,
|
||||||
|
destroyOnClose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 创建示例联系人 */
|
||||||
|
function onCreate() {
|
||||||
|
formModalApi.setData({}).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 编辑示例联系人 */
|
||||||
|
function onEdit(row: Demo01ContactApi.Demo01Contact) {
|
||||||
|
formModalApi.setData(row).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除示例联系人 */
|
||||||
|
async function onDelete(row: Demo01ContactApi.Demo01Contact) {
|
||||||
|
const hideLoading = message.loading({
|
||||||
|
content: $t('ui.actionMessage.deleting', [row.id]),
|
||||||
|
duration: 0,
|
||||||
|
key: 'action_process_msg',
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await deleteDemo01Contact(row.id as number);
|
||||||
|
message.success({
|
||||||
|
content: $t('ui.actionMessage.deleteSuccess', [row.id]),
|
||||||
|
key: 'action_process_msg',
|
||||||
|
});
|
||||||
|
await getList();
|
||||||
|
} catch {
|
||||||
|
hideLoading();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 导出表格 */
|
||||||
|
async function onExport() {
|
||||||
|
try {
|
||||||
|
exportLoading.value = true;
|
||||||
|
const data = await exportDemo01Contact(queryParams);
|
||||||
|
downloadByData(data, '示例联系人.xls');
|
||||||
|
} finally {
|
||||||
|
exportLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 初始化 */
|
||||||
|
onMounted(() => {
|
||||||
|
getList();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<FormModal @success="getList" />
|
||||||
|
|
||||||
|
<ContentWrap>
|
||||||
|
<!-- 搜索工作栏 -->
|
||||||
|
<!-- TODO @puhui999:貌似 -mb-15px 没效果?可能和 ContentWrap 有关系? -->
|
||||||
|
<Form
|
||||||
|
class="-mb-15px"
|
||||||
|
:model="queryParams"
|
||||||
|
ref="queryFormRef"
|
||||||
|
layout="inline"
|
||||||
|
>
|
||||||
|
<Form.Item label="名字" name="name">
|
||||||
|
<!-- TODO @puhui999:貌似不一定 240?看着和 schema 还是不太一样 -->
|
||||||
|
<Input
|
||||||
|
v-model:value="queryParams.name"
|
||||||
|
placeholder="请输入名字"
|
||||||
|
allow-clear
|
||||||
|
@press-enter="handleQuery"
|
||||||
|
class="!w-240px"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="性别" name="sex">
|
||||||
|
<Select
|
||||||
|
v-model:value="queryParams.sex"
|
||||||
|
placeholder="请选择性别"
|
||||||
|
allow-clear
|
||||||
|
class="!w-240px"
|
||||||
|
>
|
||||||
|
<Select.Option
|
||||||
|
v-for="dict in getDictOptions(
|
||||||
|
DICT_TYPE.SYSTEM_USER_SEX,
|
||||||
|
'number',
|
||||||
|
)"
|
||||||
|
:key="dict.value"
|
||||||
|
:label="dict.label"
|
||||||
|
:value="dict.value"
|
||||||
|
/>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="创建时间" name="createTime">
|
||||||
|
<!-- TODO @puhui999:这里有个红色的告警,看看有办法处理哇? -->
|
||||||
|
<RangePicker
|
||||||
|
v-model:value="queryParams.createTime"
|
||||||
|
v-bind="getRangePickerDefaultProps()"
|
||||||
|
class="!w-220px"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item>
|
||||||
|
<!-- TODO @puhui999:搜索和重置;貌似样子和位置不太一样,有木有办法一致 -->
|
||||||
|
<!-- TODO @puhui999:收齐、展开,好弄哇? -->
|
||||||
|
<Button class="ml-2" @click="handleQuery" :icon="h(Search)">
|
||||||
|
搜索
|
||||||
|
</Button>
|
||||||
|
<Button class="ml-2" @click="resetQuery" :icon="h(RefreshCw)">
|
||||||
|
重置
|
||||||
|
</Button>
|
||||||
|
<!-- TODO @puhui999:有办法放到 VxeTable 哪里么? -->
|
||||||
|
<Button
|
||||||
|
class="ml-2"
|
||||||
|
:icon="h(Plus)"
|
||||||
|
type="primary"
|
||||||
|
@click="onCreate"
|
||||||
|
v-access:code="['infra:demo01-contact:create']"
|
||||||
|
>
|
||||||
|
{{ $t('ui.actionTitle.create', ['示例联系人']) }}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
:icon="h(Download)"
|
||||||
|
type="primary"
|
||||||
|
class="ml-2"
|
||||||
|
:loading="exportLoading"
|
||||||
|
@click="onExport"
|
||||||
|
v-access:code="['infra:demo01-contact:export']"
|
||||||
|
>
|
||||||
|
{{ $t('ui.actionTitle.export') }}
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<!-- 列表 -->
|
||||||
|
<!-- TODO @puhui999:title 要不还是假起来? -->
|
||||||
|
<ContentWrap>
|
||||||
|
<VxeTable :data="list" show-overflow :loading="loading">
|
||||||
|
<VxeColumn field="id" title="编号" align="center" />
|
||||||
|
<VxeColumn field="name" title="名字" align="center" />
|
||||||
|
<VxeColumn field="sex" title="性别" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<DictTag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="row.sex" />
|
||||||
|
</template>
|
||||||
|
</VxeColumn>
|
||||||
|
<VxeColumn field="birthday" title="出生年" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ formatDateTime(row.birthday) }}
|
||||||
|
</template>
|
||||||
|
</VxeColumn>
|
||||||
|
<VxeColumn field="description" title="简介" align="center" />
|
||||||
|
<VxeColumn field="avatar" title="头像" align="center" />
|
||||||
|
<VxeColumn field="createTime" title="创建时间" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ formatDateTime(row.createTime) }}
|
||||||
|
</template>
|
||||||
|
</VxeColumn>
|
||||||
|
<VxeColumn field="operation" title="操作" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
type="link"
|
||||||
|
@click="onEdit(row as any)"
|
||||||
|
v-access:code="['infra:demo01-contact:update']"
|
||||||
|
>
|
||||||
|
{{ $t('ui.actionTitle.edit') }}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
type="link"
|
||||||
|
class="ml-2"
|
||||||
|
@click="onDelete(row as any)"
|
||||||
|
v-access:code="['infra:demo01-contact:delete']"
|
||||||
|
>
|
||||||
|
{{ $t('ui.actionTitle.delete') }}
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
</VxeColumn>
|
||||||
|
</VxeTable>
|
||||||
|
<!-- 分页 -->
|
||||||
|
<div class="mt-2 flex justify-end">
|
||||||
|
<!-- TODO @puhui999:这个分页,看着不太一致 -->
|
||||||
|
<Pagination
|
||||||
|
:total="total"
|
||||||
|
v-model:current="queryParams.pageNo"
|
||||||
|
v-model:page-size="queryParams.pageSize"
|
||||||
|
show-size-changer
|
||||||
|
@change="getList"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</ContentWrap>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,150 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { Rule } from 'ant-design-vue/es/form';
|
||||||
|
|
||||||
|
import type { Demo01ContactApi } from '#/api/infra/demo/demo01';
|
||||||
|
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import {
|
||||||
|
DatePicker,
|
||||||
|
Form,
|
||||||
|
Input,
|
||||||
|
message,
|
||||||
|
Radio,
|
||||||
|
RadioGroup,
|
||||||
|
} from 'ant-design-vue';
|
||||||
|
|
||||||
|
import {
|
||||||
|
createDemo01Contact,
|
||||||
|
getDemo01Contact,
|
||||||
|
updateDemo01Contact,
|
||||||
|
} from '#/api/infra/demo/demo01';
|
||||||
|
import { Tinymce as RichTextarea } from '#/components/tinymce';
|
||||||
|
import { ImageUpload } from '#/components/upload';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
import { DICT_TYPE, getDictOptions } from '#/utils/dict';
|
||||||
|
|
||||||
|
const emit = defineEmits(['success']); // TODO @puhui999:emit 和下面空一行?
|
||||||
|
const formRef = ref();
|
||||||
|
// TODO @puhui999:labelCol、wrapperCol 直接写?不用单独定义变量,
|
||||||
|
const labelCol = { span: 5 };
|
||||||
|
const wrapperCol = { span: 13 };
|
||||||
|
const formData = ref<Partial<Demo01ContactApi.Demo01Contact>>({
|
||||||
|
id: undefined,
|
||||||
|
name: undefined,
|
||||||
|
sex: undefined,
|
||||||
|
birthday: undefined,
|
||||||
|
description: undefined,
|
||||||
|
avatar: undefined,
|
||||||
|
});
|
||||||
|
const rules: Record<string, Rule[]> = {
|
||||||
|
name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],
|
||||||
|
sex: [{ required: true, message: '性别不能为空', trigger: 'blur' }],
|
||||||
|
birthday: [{ required: true, message: '出生年不能为空', trigger: 'blur' }],
|
||||||
|
description: [{ required: true, message: '简介不能为空', trigger: 'blur' }],
|
||||||
|
};
|
||||||
|
const getTitle = computed(() => {
|
||||||
|
return formData.value?.id
|
||||||
|
? $t('ui.actionTitle.edit', ['示例联系人'])
|
||||||
|
: $t('ui.actionTitle.create', ['示例联系人']);
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 重置表单 */
|
||||||
|
const resetForm = () => {
|
||||||
|
formData.value = {
|
||||||
|
id: undefined,
|
||||||
|
name: undefined,
|
||||||
|
sex: undefined,
|
||||||
|
birthday: undefined,
|
||||||
|
description: undefined,
|
||||||
|
avatar: undefined,
|
||||||
|
};
|
||||||
|
formRef.value?.resetFields();
|
||||||
|
};
|
||||||
|
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
async onConfirm() {
|
||||||
|
await formRef.value?.validate();
|
||||||
|
modalApi.lock();
|
||||||
|
// 提交表单
|
||||||
|
const data = formData.value as Demo01ContactApi.Demo01Contact;
|
||||||
|
try {
|
||||||
|
await (formData.value?.id
|
||||||
|
? updateDemo01Contact(data)
|
||||||
|
: createDemo01Contact(data));
|
||||||
|
// 关闭并提示
|
||||||
|
await modalApi.close();
|
||||||
|
emit('success');
|
||||||
|
message.success({
|
||||||
|
content: $t('ui.actionMessage.operationSuccess'),
|
||||||
|
key: 'action_process_msg',
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
modalApi.lock(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async onOpenChange(isOpen: boolean) {
|
||||||
|
if (!isOpen) {
|
||||||
|
resetForm();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载数据 TODO @puhui999:这里不用空行
|
||||||
|
let data = modalApi.getData<Demo01ContactApi.Demo01Contact>();
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data.id) {
|
||||||
|
modalApi.lock();
|
||||||
|
try {
|
||||||
|
data = await getDemo01Contact(data.id);
|
||||||
|
} finally {
|
||||||
|
modalApi.lock(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
formData.value = data;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Modal :title="getTitle">
|
||||||
|
<Form
|
||||||
|
ref="formRef"
|
||||||
|
:model="formData"
|
||||||
|
:rules="rules"
|
||||||
|
:label-col="labelCol"
|
||||||
|
:wrapper-col="wrapperCol"
|
||||||
|
>
|
||||||
|
<Form.Item label="名字" name="name">
|
||||||
|
<Input v-model:value="formData.name" placeholder="请输入名字" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="性别" name="sex">
|
||||||
|
<RadioGroup v-model:value="formData.sex">
|
||||||
|
<Radio
|
||||||
|
v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number')"
|
||||||
|
:key="dict.value"
|
||||||
|
:value="dict.value"
|
||||||
|
>
|
||||||
|
{{ dict.label }}
|
||||||
|
</Radio>
|
||||||
|
</RadioGroup>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="出生年" name="birthday">
|
||||||
|
<DatePicker
|
||||||
|
v-model:value="formData.birthday"
|
||||||
|
value-format="x"
|
||||||
|
placeholder="选择出生年"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="简介" name="description">
|
||||||
|
<RichTextarea v-model="formData.description" height="500px" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="头像" name="avatar">
|
||||||
|
<ImageUpload v-model:value="formData.avatar" />
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
@ -25,11 +25,13 @@ onMounted(async () => {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page auto-content-height>
|
<Page auto-content-height>
|
||||||
<DocAlert title="数据库 MyBatis" url="https://doc.iocoder.cn/mybatis/" />
|
<template #doc>
|
||||||
<DocAlert
|
<DocAlert title="数据库 MyBatis" url="https://doc.iocoder.cn/mybatis/" />
|
||||||
title="多数据源(读写分离)"
|
<DocAlert
|
||||||
url="https://doc.iocoder.cn/dynamic-datasource/"
|
title="多数据源(读写分离)"
|
||||||
/>
|
url="https://doc.iocoder.cn/dynamic-datasource/"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
<IFrame v-if="!loading" v-loading="loading" :src="src" />
|
<IFrame v-if="!loading" v-loading="loading" :src="src" />
|
||||||
</Page>
|
</Page>
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,17 @@ import { useFormSchema } from '../data';
|
||||||
const emit = defineEmits(['success']);
|
const emit = defineEmits(['success']);
|
||||||
|
|
||||||
const [Form, formApi] = useVbenForm({
|
const [Form, formApi] = useVbenForm({
|
||||||
|
commonConfig: {
|
||||||
|
componentProps: {
|
||||||
|
class: 'w-full',
|
||||||
|
},
|
||||||
|
formItemClass: 'col-span-2',
|
||||||
|
labelWidth: 80,
|
||||||
|
hideLabel: true,
|
||||||
|
},
|
||||||
layout: 'horizontal',
|
layout: 'horizontal',
|
||||||
schema: useFormSchema().map((item) => ({ ...item, label: '' })), // 去除label
|
schema: useFormSchema().map((item) => ({ ...item, label: '' })), // 去除label
|
||||||
showDefaultActions: false,
|
showDefaultActions: false,
|
||||||
commonConfig: {
|
|
||||||
hideLabel: true,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const [Modal, modalApi] = useVbenModal({
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,6 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||||
component: 'InputNumber',
|
component: 'InputNumber',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
min: 0,
|
min: 0,
|
||||||
class: 'w-full',
|
|
||||||
controlsPosition: 'right',
|
controlsPosition: 'right',
|
||||||
placeholder: '请输入主机端口',
|
placeholder: '请输入主机端口',
|
||||||
},
|
},
|
||||||
|
|
@ -134,6 +133,8 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||||
{ label: '主动模式', value: 'Active' },
|
{ label: '主动模式', value: 'Active' },
|
||||||
{ label: '被动模式', value: 'Passive' },
|
{ label: '被动模式', value: 'Passive' },
|
||||||
],
|
],
|
||||||
|
buttonStyle: 'solid',
|
||||||
|
optionType: 'button',
|
||||||
},
|
},
|
||||||
rules: 'required',
|
rules: 'required',
|
||||||
dependencies: {
|
dependencies: {
|
||||||
|
|
@ -194,6 +195,25 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||||
show: (formValues) => formValues.storage === 20,
|
show: (formValues) => formValues.storage === 20,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'config.enablePathStyleAccess',
|
||||||
|
label: '是否 Path Style',
|
||||||
|
component: 'RadioGroup',
|
||||||
|
componentProps: {
|
||||||
|
options: [
|
||||||
|
{ label: '启用', value: true },
|
||||||
|
{ label: '禁用', value: false },
|
||||||
|
],
|
||||||
|
buttonStyle: 'solid',
|
||||||
|
optionType: 'button',
|
||||||
|
},
|
||||||
|
rules: 'required',
|
||||||
|
dependencies: {
|
||||||
|
triggerFields: ['storage'],
|
||||||
|
show: (formValues) => formValues.storage === 20,
|
||||||
|
},
|
||||||
|
defaultValue: false,
|
||||||
|
},
|
||||||
// 通用
|
// 通用
|
||||||
{
|
{
|
||||||
fieldName: 'config.domain',
|
fieldName: 'config.domain',
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,13 @@ const getTitle = computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const [Form, formApi] = useVbenForm({
|
const [Form, formApi] = useVbenForm({
|
||||||
|
commonConfig: {
|
||||||
|
componentProps: {
|
||||||
|
class: 'w-full',
|
||||||
|
},
|
||||||
|
formItemClass: 'col-span-2',
|
||||||
|
labelWidth: 80,
|
||||||
|
},
|
||||||
layout: 'horizontal',
|
layout: 'horizontal',
|
||||||
schema: useFormSchema(),
|
schema: useFormSchema(),
|
||||||
showDefaultActions: false,
|
showDefaultActions: false,
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,6 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||||
componentProps: {
|
componentProps: {
|
||||||
placeholder: '请输入重试次数。设置为 0 时,不进行重试',
|
placeholder: '请输入重试次数。设置为 0 时,不进行重试',
|
||||||
min: 0,
|
min: 0,
|
||||||
class: 'w-full',
|
|
||||||
},
|
},
|
||||||
rules: 'required',
|
rules: 'required',
|
||||||
},
|
},
|
||||||
|
|
@ -76,7 +75,6 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||||
componentProps: {
|
componentProps: {
|
||||||
placeholder: '请输入重试间隔,单位:毫秒。设置为 0 时,无需间隔',
|
placeholder: '请输入重试间隔,单位:毫秒。设置为 0 时,无需间隔',
|
||||||
min: 0,
|
min: 0,
|
||||||
class: 'w-full',
|
|
||||||
},
|
},
|
||||||
rules: 'required',
|
rules: 'required',
|
||||||
},
|
},
|
||||||
|
|
@ -87,7 +85,6 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||||
componentProps: {
|
componentProps: {
|
||||||
placeholder: '请输入监控超时时间,单位:毫秒',
|
placeholder: '请输入监控超时时间,单位:毫秒',
|
||||||
min: 0,
|
min: 0,
|
||||||
class: 'w-full',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
@ -196,7 +193,7 @@ export function useGridColumns<T = InfraJobApi.Job>(
|
||||||
text: '暂停',
|
text: '暂停',
|
||||||
show: (row: any) =>
|
show: (row: any) =>
|
||||||
hasAccessByCodes(['infra:job:update']) &&
|
hasAccessByCodes(['infra:job:update']) &&
|
||||||
row.status == InfraJobStatusEnum.NORMAL,
|
row.status === InfraJobStatusEnum.NORMAL,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: 'trigger',
|
code: 'trigger',
|
||||||
|
|
|
||||||
|
|
@ -192,9 +192,11 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page auto-content-height>
|
<Page auto-content-height>
|
||||||
<DocAlert title="定时任务" url="https://doc.iocoder.cn/job/" />
|
<template #doc>
|
||||||
<DocAlert title="异步任务" url="https://doc.iocoder.cn/async-task/" />
|
<DocAlert title="定时任务" url="https://doc.iocoder.cn/job/" />
|
||||||
<DocAlert title="消息队列" url="https://doc.iocoder.cn/message-queue/" />
|
<DocAlert title="异步任务" url="https://doc.iocoder.cn/async-task/" />
|
||||||
|
<DocAlert title="消息队列" url="https://doc.iocoder.cn/message-queue/" />
|
||||||
|
</template>
|
||||||
|
|
||||||
<FormModal @success="onRefresh" />
|
<FormModal @success="onRefresh" />
|
||||||
<DetailModal />
|
<DetailModal />
|
||||||
|
|
|
||||||
|
|
@ -88,9 +88,11 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page auto-content-height>
|
<Page auto-content-height>
|
||||||
<DocAlert title="定时任务" url="https://doc.iocoder.cn/job/" />
|
<template #doc>
|
||||||
<DocAlert title="异步任务" url="https://doc.iocoder.cn/async-task/" />
|
<DocAlert title="定时任务" url="https://doc.iocoder.cn/job/" />
|
||||||
<DocAlert title="消息队列" url="https://doc.iocoder.cn/message-queue/" />
|
<DocAlert title="异步任务" url="https://doc.iocoder.cn/async-task/" />
|
||||||
|
<DocAlert title="消息队列" url="https://doc.iocoder.cn/message-queue/" />
|
||||||
|
</template>
|
||||||
|
|
||||||
<DetailModal />
|
<DetailModal />
|
||||||
<Grid table-title="任务日志列表">
|
<Grid table-title="任务日志列表">
|
||||||
|
|
|
||||||
|
|
@ -22,12 +22,16 @@ const getTitle = computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const [Form, formApi] = useVbenForm({
|
const [Form, formApi] = useVbenForm({
|
||||||
|
commonConfig: {
|
||||||
|
componentProps: {
|
||||||
|
class: 'w-full',
|
||||||
|
},
|
||||||
|
formItemClass: 'col-span-2',
|
||||||
|
labelWidth: 80,
|
||||||
|
},
|
||||||
layout: 'horizontal',
|
layout: 'horizontal',
|
||||||
schema: useFormSchema(),
|
schema: useFormSchema(),
|
||||||
showDefaultActions: false,
|
showDefaultActions: false,
|
||||||
commonConfig: {
|
|
||||||
labelWidth: 140,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const [Modal, modalApi] = useVbenModal({
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,10 @@ onMounted(() => {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page auto-content-height>
|
<Page auto-content-height>
|
||||||
<DocAlert title="Redis 缓存" url="https://doc.iocoder.cn/redis-cache/" />
|
<template #doc>
|
||||||
<DocAlert title="本地缓存" url="https://doc.iocoder.cn/local-cache/" />
|
<DocAlert title="Redis 缓存" url="https://doc.iocoder.cn/redis-cache/" />
|
||||||
|
<DocAlert title="本地缓存" url="https://doc.iocoder.cn/local-cache/" />
|
||||||
|
</template>
|
||||||
|
|
||||||
<Card class="mt-5" title="Redis 概览">
|
<Card class="mt-5" title="Redis 概览">
|
||||||
<Info :redis-data="redisData" />
|
<Info :redis-data="redisData" />
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ defineProps<{
|
||||||
{{ redisData?.info?.redis_version }}
|
{{ redisData?.info?.redis_version }}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label="运行模式">
|
<Descriptions.Item label="运行模式">
|
||||||
{{ redisData?.info?.redis_mode == 'standalone' ? '单机' : '集群' }}
|
{{ redisData?.info?.redis_mode === 'standalone' ? '单机' : '集群' }}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label="端口">
|
<Descriptions.Item label="端口">
|
||||||
{{ redisData?.info?.tcp_port }}
|
{{ redisData?.info?.tcp_port }}
|
||||||
|
|
@ -44,7 +44,7 @@ defineProps<{
|
||||||
{{ redisData?.info?.maxmemory_human }}
|
{{ redisData?.info?.maxmemory_human }}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label="AOF 是否开启">
|
<Descriptions.Item label="AOF 是否开启">
|
||||||
{{ redisData?.info?.aof_enabled == '0' ? '否' : '是' }}
|
{{ redisData?.info?.aof_enabled === '0' ? '否' : '是' }}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label="RDB 是否成功">
|
<Descriptions.Item label="RDB 是否成功">
|
||||||
{{ redisData?.info?.rdb_last_bgsave_status }}
|
{{ redisData?.info?.rdb_last_bgsave_status }}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,9 @@ onMounted(async () => {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page auto-content-height>
|
<Page auto-content-height>
|
||||||
<DocAlert title="服务监控" url="https://doc.iocoder.cn/server-monitor/" />
|
<template #doc>
|
||||||
|
<DocAlert title="服务监控" url="https://doc.iocoder.cn/server-monitor/" />
|
||||||
|
</template>
|
||||||
|
|
||||||
<IFrame v-if="!loading" v-loading="loading" :src="src" />
|
<IFrame v-if="!loading" v-loading="loading" :src="src" />
|
||||||
</Page>
|
</Page>
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,9 @@ onMounted(async () => {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page auto-content-height>
|
<Page auto-content-height>
|
||||||
<DocAlert title="服务监控" url="https://doc.iocoder.cn/server-monitor/" />
|
<template #doc>
|
||||||
|
<DocAlert title="服务监控" url="https://doc.iocoder.cn/server-monitor/" />
|
||||||
|
</template>
|
||||||
|
|
||||||
<IFrame v-if="!loading" v-loading="loading" :src="src" />
|
<IFrame v-if="!loading" v-loading="loading" :src="src" />
|
||||||
</Page>
|
</Page>
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,9 @@ onMounted(async () => {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page auto-content-height>
|
<Page auto-content-height>
|
||||||
<DocAlert title="接口文档" url="https://doc.iocoder.cn/api-doc/" />
|
<template #doc>
|
||||||
|
<DocAlert title="接口文档" url="https://doc.iocoder.cn/api-doc/" />
|
||||||
|
</template>
|
||||||
|
|
||||||
<IFrame v-if="!loading" :src="src" />
|
<IFrame v-if="!loading" :src="src" />
|
||||||
</Page>
|
</Page>
|
||||||
|
|
|
||||||
|
|
@ -175,10 +175,12 @@ onMounted(async () => {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page>
|
<Page>
|
||||||
<DocAlert
|
<template #doc>
|
||||||
title="WebSocket 实时通信"
|
<DocAlert
|
||||||
url="https://doc.iocoder.cn/websocket/"
|
title="WebSocket 实时通信"
|
||||||
/>
|
url="https://doc.iocoder.cn/websocket/"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
<div class="mt-4 flex flex-col gap-4 md:flex-row">
|
<div class="mt-4 flex flex-col gap-4 md:flex-row">
|
||||||
<!-- 左侧:建立连接、发送消息 -->
|
<!-- 左侧:建立连接、发送消息 -->
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
|
|
||||||
|
|
||||||
import type { VbenFormSchema } from '#/adapter/form';
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { SystemAreaApi } from '#/api/system/area';
|
import type { SystemAreaApi } from '#/api/system/area';
|
||||||
|
|
||||||
/** 查询 IP 的表单 */
|
/** 查询 IP 的表单 */
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,9 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page auto-content-height>
|
<Page auto-content-height>
|
||||||
<DocAlert title="地区 & IP" url="https://doc.iocoder.cn/area-and-ip/" />
|
<template #doc>
|
||||||
|
<DocAlert title="地区 & IP" url="https://doc.iocoder.cn/area-and-ip/" />
|
||||||
|
</template>
|
||||||
|
|
||||||
<FormModal @success="onRefresh" />
|
<FormModal @success="onRefresh" />
|
||||||
<Grid table-title="地区列表">
|
<Grid table-title="地区列表">
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue