Browse Source

refactor: 优化系统模块,token过期登录后,直接跳转到首页

laiqi 1 year ago
parent
commit
fe4a854b47

+ 0 - 1
apps/web-ele/components.d.ts

@@ -12,7 +12,6 @@ declare module 'vue' {
     ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
     ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
     ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
     ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
     ElDivider: typeof import('element-plus/es')['ElDivider']
     ElDivider: typeof import('element-plus/es')['ElDivider']
-    ElImage: typeof import('element-plus/es')['ElImage']
     ElInput: typeof import('element-plus/es')['ElInput']
     ElInput: typeof import('element-plus/es')['ElInput']
     ElTag: typeof import('element-plus/es')['ElTag']
     ElTag: typeof import('element-plus/es')['ElTag']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterLink: typeof import('vue-router')['RouterLink']

+ 2 - 2
apps/web-ele/src/api/request.ts

@@ -1,4 +1,4 @@
-/**
+/** ,
  * 该文件可自行根据业务逻辑进行调整
  * 该文件可自行根据业务逻辑进行调整
  */
  */
 import type { RequestClientOptions } from '@vben/request';
 import type { RequestClientOptions } from '@vben/request';
@@ -42,7 +42,7 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) {
     ) {
     ) {
       accessStore.setLoginExpired(true);
       accessStore.setLoginExpired(true);
     } else {
     } else {
-      await authStore.logout();
+      await authStore.logout(false);
     }
     }
   }
   }
 
 

+ 0 - 54
playground/src/api/system/dept.ts

@@ -1,54 +0,0 @@
-import { requestClient } from '#/api/request';
-
-export namespace SystemDeptApi {
-  export interface SystemDept {
-    [key: string]: any;
-    children?: SystemDept[];
-    id: string;
-    name: string;
-    remark?: string;
-    status: 0 | 1;
-  }
-}
-
-/**
- * 获取部门列表数据
- */
-async function getDeptList() {
-  return requestClient.get<Array<SystemDeptApi.SystemDept>>(
-    '/system/dept/list',
-  );
-}
-
-/**
- * 创建部门
- * @param data 部门数据
- */
-async function createDept(
-  data: Omit<SystemDeptApi.SystemDept, 'children' | 'id'>,
-) {
-  return requestClient.post('/system/dept', data);
-}
-
-/**
- * 更新部门
- *
- * @param id 部门 ID
- * @param data 部门数据
- */
-async function updateDept(
-  id: string,
-  data: Omit<SystemDeptApi.SystemDept, 'children' | 'id'>,
-) {
-  return requestClient.put(`/system/dept/${id}`, data);
-}
-
-/**
- * 删除部门
- * @param id 部门 ID
- */
-async function deleteDept(id: string) {
-  return requestClient.delete(`/system/dept/${id}`);
-}
-
-export { createDept, deleteDept, getDeptList, updateDept };

+ 0 - 3
playground/src/api/system/index.ts

@@ -1,3 +0,0 @@
-export * from './dept';
-export * from './menu';
-export * from './role';

+ 0 - 158
playground/src/api/system/menu.ts

@@ -1,158 +0,0 @@
-import type { Recordable } from '@vben/types';
-
-import { requestClient } from '#/api/request';
-
-export namespace SystemMenuApi {
-  /** 徽标颜色集合 */
-  export const BadgeVariants = [
-    'default',
-    'destructive',
-    'primary',
-    'success',
-    'warning',
-  ] as const;
-  /** 徽标类型集合 */
-  export const BadgeTypes = ['dot', 'normal'] as const;
-  /** 菜单类型集合 */
-  export const MenuTypes = [
-    'catalog',
-    'menu',
-    'embedded',
-    'link',
-    'button',
-  ] as const;
-  /** 系统菜单 */
-  export interface SystemMenu {
-    [key: string]: any;
-    /** 后端权限标识 */
-    authCode: string;
-    /** 子级 */
-    children?: SystemMenu[];
-    /** 组件 */
-    component?: string;
-    /** 菜单ID */
-    id: string;
-    /** 菜单元数据 */
-    meta?: {
-      /** 激活时显示的图标 */
-      activeIcon?: string;
-      /** 作为路由时,需要激活的菜单的Path */
-      activePath?: string;
-      /** 固定在标签栏 */
-      affixTab?: boolean;
-      /** 在标签栏固定的顺序 */
-      affixTabOrder?: number;
-      /** 徽标内容(当徽标类型为normal时有效) */
-      badge?: string;
-      /** 徽标类型 */
-      badgeType?: (typeof BadgeTypes)[number];
-      /** 徽标颜色 */
-      badgeVariants?: (typeof BadgeVariants)[number];
-      /** 在菜单中隐藏下级 */
-      hideChildrenInMenu?: boolean;
-      /** 在面包屑中隐藏 */
-      hideInBreadcrumb?: boolean;
-      /** 在菜单中隐藏 */
-      hideInMenu?: boolean;
-      /** 在标签栏中隐藏 */
-      hideInTab?: boolean;
-      /** 菜单图标 */
-      icon?: string;
-      /** 内嵌Iframe的URL */
-      iframeSrc?: string;
-      /** 是否缓存页面 */
-      keepAlive?: boolean;
-      /** 外链页面的URL */
-      link?: string;
-      /** 同一个路由最大打开的标签数 */
-      maxNumOfOpenTab?: number;
-      /** 无需基础布局 */
-      noBasicLayout?: boolean;
-      /** 是否在新窗口打开 */
-      openInNewWindow?: boolean;
-      /** 菜单排序 */
-      order?: number;
-      /** 额外的路由参数 */
-      query?: Recordable<any>;
-      /** 菜单标题 */
-      title?: string;
-    };
-    /** 菜单名称 */
-    name: string;
-    /** 路由路径 */
-    path: string;
-    /** 父级ID */
-    pid: string;
-    /** 重定向 */
-    redirect?: string;
-    /** 菜单类型 */
-    type: (typeof MenuTypes)[number];
-  }
-}
-
-/**
- * 获取菜单数据列表
- */
-async function getMenuList() {
-  return requestClient.get<Array<SystemMenuApi.SystemMenu>>(
-    '/system/menu/list',
-  );
-}
-
-async function isMenuNameExists(
-  name: string,
-  id?: SystemMenuApi.SystemMenu['id'],
-) {
-  return requestClient.get<boolean>('/system/menu/name-exists', {
-    params: { id, name },
-  });
-}
-
-async function isMenuPathExists(
-  path: string,
-  id?: SystemMenuApi.SystemMenu['id'],
-) {
-  return requestClient.get<boolean>('/system/menu/path-exists', {
-    params: { id, path },
-  });
-}
-
-/**
- * 创建菜单
- * @param data 菜单数据
- */
-async function createMenu(
-  data: Omit<SystemMenuApi.SystemMenu, 'children' | 'id'>,
-) {
-  return requestClient.post('/system/menu', data);
-}
-
-/**
- * 更新菜单
- *
- * @param id 菜单 ID
- * @param data 菜单数据
- */
-async function updateMenu(
-  id: string,
-  data: Omit<SystemMenuApi.SystemMenu, 'children' | 'id'>,
-) {
-  return requestClient.put(`/system/menu/${id}`, data);
-}
-
-/**
- * 删除菜单
- * @param id 菜单 ID
- */
-async function deleteMenu(id: string) {
-  return requestClient.delete(`/system/menu/${id}`);
-}
-
-export {
-  createMenu,
-  deleteMenu,
-  getMenuList,
-  isMenuNameExists,
-  isMenuPathExists,
-  updateMenu,
-};

+ 0 - 55
playground/src/api/system/role.ts

@@ -1,55 +0,0 @@
-import type { Recordable } from '@vben/types';
-
-import { requestClient } from '#/api/request';
-
-export namespace SystemRoleApi {
-  export interface SystemRole {
-    [key: string]: any;
-    id: string;
-    name: string;
-    permissions: string[];
-    remark?: string;
-    status: 0 | 1;
-  }
-}
-
-/**
- * 获取角色列表数据
- */
-async function getRoleList(params: Recordable<any>) {
-  return requestClient.get<Array<SystemRoleApi.SystemRole>>(
-    '/system/role/list',
-    { params },
-  );
-}
-
-/**
- * 创建角色
- * @param data 角色数据
- */
-async function createRole(data: Omit<SystemRoleApi.SystemRole, 'id'>) {
-  return requestClient.post('/system/role', data);
-}
-
-/**
- * 更新角色
- *
- * @param id 角色 ID
- * @param data 角色数据
- */
-async function updateRole(
-  id: string,
-  data: Omit<SystemRoleApi.SystemRole, 'id'>,
-) {
-  return requestClient.put(`/system/role/${id}`, data);
-}
-
-/**
- * 删除角色
- * @param id 角色 ID
- */
-async function deleteRole(id: string) {
-  return requestClient.delete(`/system/role/${id}`);
-}
-
-export { createRole, deleteRole, getRoleList, updateRole };

+ 0 - 65
playground/src/locales/langs/en-US/system.json

@@ -1,65 +0,0 @@
-{
-  "title": "System Management",
-  "dept": {
-    "name": "Department",
-    "title": "Department Management",
-    "deptName": "Department Name",
-    "status": "Status",
-    "createTime": "Create Time",
-    "remark": "Remark",
-    "operation": "Operation",
-    "parentDept": "Parent Department"
-  },
-  "menu": {
-    "title": "Menu Management",
-    "parent": "Parent Menu",
-    "menuTitle": "Title",
-    "menuName": "Menu Name",
-    "name": "Menu",
-    "type": "Type",
-    "typeCatalog": "Catalog",
-    "typeMenu": "Menu",
-    "typeButton": "Button",
-    "typeLink": "Link",
-    "typeEmbedded": "Embedded",
-    "icon": "Icon",
-    "activeIcon": "Active Icon",
-    "activePath": "Active Path",
-    "path": "Route Path",
-    "component": "Component",
-    "status": "Status",
-    "authCode": "Auth Code",
-    "badge": "Badge",
-    "operation": "Operation",
-    "linkSrc": "Link Address",
-    "affixTab": "Affix In Tabs",
-    "keepAlive": "Keep Alive",
-    "hideInMenu": "Hide In Menu",
-    "hideInTab": "Hide In Tabbar",
-    "hideChildrenInMenu": "Hide Children In Menu",
-    "hideInBreadcrumb": "Hide In Breadcrumb",
-    "advancedSettings": "Other Settings",
-    "activePathMustExist": "The path could not find a valid menu",
-    "activePathHelp": "When jumping to the current route, \nthe menu path that needs to be activated must be specified when it does not display in the navigation menu.",
-    "badgeType": {
-      "title": "Badge Type",
-      "dot": "Dot",
-      "normal": "Text",
-      "none": "None"
-    },
-    "badgeVariants": "Badge Style"
-  },
-  "role": {
-    "title": "Role Management",
-    "list": "Role List",
-    "name": "Role",
-    "roleName": "Role Name",
-    "id": "Role ID",
-    "status": "Status",
-    "remark": "Remark",
-    "createTime": "Creation Time",
-    "operation": "Operation",
-    "permissions": "Permissions",
-    "setPermissions": "Permissions"
-  }
-}

+ 0 - 67
playground/src/locales/langs/zh-CN/system.json

@@ -1,67 +0,0 @@
-{
-  "dept": {
-    "list": "部门列表",
-    "createTime": "创建时间",
-    "deptName": "部门名称",
-    "name": "部门",
-    "operation": "操作",
-    "parentDept": "上级部门",
-    "remark": "备注",
-    "status": "状态",
-    "title": "部门管理"
-  },
-  "menu": {
-    "list": "菜单列表",
-    "activeIcon": "激活图标",
-    "activePath": "激活路径",
-    "activePathHelp": "跳转到当前路由时,需要激活的菜单路径。\n当不在导航菜单中显示时,需要指定激活路径",
-    "activePathMustExist": "该路径未能找到有效的菜单",
-    "advancedSettings": "其它设置",
-    "affixTab": "固定在标签",
-    "authCode": "权限标识",
-    "badge": "徽章内容",
-    "badgeVariants": "徽标样式",
-    "badgeType": {
-      "dot": "点",
-      "none": "无",
-      "normal": "文字",
-      "title": "徽标类型"
-    },
-    "component": "页面组件",
-    "hideChildrenInMenu": "隐藏子菜单",
-    "hideInBreadcrumb": "在面包屑中隐藏",
-    "hideInMenu": "隐藏菜单",
-    "hideInTab": "在标签栏中隐藏",
-    "icon": "图标",
-    "keepAlive": "缓存标签页",
-    "linkSrc": "链接地址",
-    "menuName": "菜单名称",
-    "menuTitle": "标题",
-    "name": "菜单",
-    "operation": "操作",
-    "parent": "上级菜单",
-    "path": "路由地址",
-    "status": "状态",
-    "title": "菜单管理",
-    "type": "类型",
-    "typeButton": "按钮",
-    "typeCatalog": "目录",
-    "typeEmbedded": "内嵌",
-    "typeLink": "外链",
-    "typeMenu": "菜单"
-  },
-  "role": {
-    "title": "角色管理",
-    "list": "角色列表",
-    "name": "角色",
-    "roleName": "角色名称",
-    "id": "角色ID",
-    "status": "状态",
-    "remark": "备注",
-    "createTime": "创建时间",
-    "operation": "操作",
-    "permissions": "权限",
-    "setPermissions": "授权"
-  },
-  "title": "系统管理"
-}

+ 0 - 46
playground/src/router/routes/modules/system.ts

@@ -1,46 +0,0 @@
-import type { RouteRecordRaw } from 'vue-router';
-
-import { $t } from '#/locales';
-
-const routes: RouteRecordRaw[] = [
-  {
-    meta: {
-      icon: 'ion:settings-outline',
-      order: 9997,
-      title: $t('system.title'),
-    },
-    name: 'System',
-    path: '/system',
-    children: [
-      {
-        path: '/system/role',
-        name: 'SystemRole',
-        meta: {
-          icon: 'mdi:account-group',
-          title: $t('system.role.title'),
-        },
-        component: () => import('#/views/system/role/list.vue'),
-      },
-      {
-        path: '/system/menu',
-        name: 'SystemMenu',
-        meta: {
-          icon: 'mdi:menu',
-          title: $t('system.menu.title'),
-        },
-        component: () => import('#/views/system/menu/list.vue'),
-      },
-      {
-        path: '/system/dept',
-        name: 'SystemDept',
-        meta: {
-          icon: 'charm:organisation',
-          title: $t('system.dept.title'),
-        },
-        component: () => import('#/views/system/dept/list.vue'),
-      },
-    ],
-  },
-];
-
-export default routes;

+ 0 - 194
playground/src/views/examples/button-group/index.vue

@@ -1,194 +0,0 @@
-<script lang="ts" setup>
-import type { Recordable } from '@vben/types';
-
-import { reactive, ref } from 'vue';
-
-import {
-  Page,
-  VbenButton,
-  VbenButtonGroup,
-  VbenCheckButtonGroup,
-} from '@vben/common-ui';
-
-import { Button, Card, message } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-
-const radioValue = ref<string | undefined>('a');
-const checkValue = ref(['a', 'b']);
-
-const options = [
-  { label: '选项1', value: 'a' },
-  { label: '选项2', value: 'b' },
-  { label: '选项3', value: 'c' },
-  { label: '选项4', value: 'd' },
-  { label: '选项5', value: 'e' },
-  { label: '选项6', value: 'f' },
-];
-
-function resetValues() {
-  radioValue.value = undefined;
-  checkValue.value = [];
-}
-
-function beforeChange(v: any, isChecked: boolean) {
-  return new Promise((resolve) => {
-    message.loading({
-      content: `正在设置${v}为${isChecked ? '选中' : '未选中'}...`,
-      duration: 0,
-      key: 'beforeChange',
-    });
-    setTimeout(() => {
-      message.success({ content: `${v} 已设置成功`, key: 'beforeChange' });
-      resolve(true);
-    }, 2000);
-  });
-}
-
-const compProps = reactive({
-  beforeChange: undefined,
-  disabled: false,
-  gap: 0,
-  showIcon: true,
-  size: 'middle',
-} as Recordable<any>);
-
-const [Form] = useVbenForm({
-  handleValuesChange(values) {
-    Object.keys(values).forEach((k) => {
-      if (k === 'beforeChange') {
-        compProps[k] = values[k] ? beforeChange : undefined;
-      } else {
-        compProps[k] = values[k];
-      }
-    });
-  },
-  schema: [
-    {
-      component: 'RadioGroup',
-      componentProps: {
-        options: [
-          { label: '大', value: 'large' },
-          { label: '中', value: 'middle' },
-          { label: '小', value: 'small' },
-        ],
-      },
-      defaultValue: compProps.size,
-      fieldName: 'size',
-      label: '尺寸',
-    },
-    {
-      component: 'RadioGroup',
-      componentProps: {
-        options: [
-          { label: '无', value: 0 },
-          { label: '小', value: 5 },
-          { label: '中', value: 15 },
-          { label: '大', value: 30 },
-        ],
-      },
-      defaultValue: compProps.gap,
-      fieldName: 'gap',
-      label: '间距',
-    },
-    {
-      component: 'Switch',
-      defaultValue: compProps.showIcon,
-      fieldName: 'showIcon',
-      label: '显示图标',
-    },
-    {
-      component: 'Switch',
-      defaultValue: compProps.disabled,
-      fieldName: 'disabled',
-      label: '禁用',
-    },
-    {
-      component: 'Switch',
-      defaultValue: false,
-      fieldName: 'beforeChange',
-      label: '前置回调',
-    },
-  ],
-  showDefaultActions: false,
-  submitOnChange: true,
-});
-
-function onBtnClick(value: any) {
-  const opt = options.find((o) => o.value === value);
-  if (opt) {
-    message.success(`点击了按钮${opt.label},value = ${value}`);
-  }
-}
-</script>
-<template>
-  <Page
-    title="VbenButtonGroup 按钮组"
-    description="VbenButtonGroup是一个按钮容器,用于包裹一组按钮,协调整体样式。VbenCheckButtonGroup则可以作为一个表单组件,提供单选或多选功能"
-  >
-    <Card title="基本用法">
-      <template #extra>
-        <Button type="primary" @click="resetValues">清空值</Button>
-      </template>
-      <p class="mt-4">按钮组:</p>
-      <div class="mt-2 flex flex-col gap-2">
-        <VbenButtonGroup v-bind="compProps" border>
-          <VbenButton
-            v-for="btn in options"
-            :key="btn.value"
-            variant="link"
-            @click="onBtnClick(btn.value)"
-          >
-            {{ btn.label }}
-          </VbenButton>
-        </VbenButtonGroup>
-        <VbenButtonGroup v-bind="compProps" border>
-          <VbenButton
-            v-for="btn in options"
-            :key="btn.value"
-            variant="outline"
-            @click="onBtnClick(btn.value)"
-          >
-            {{ btn.label }}
-          </VbenButton>
-        </VbenButtonGroup>
-      </div>
-      <p class="mt-4">单选:{{ radioValue }}</p>
-      <div class="mt-2 flex flex-col gap-2">
-        <VbenCheckButtonGroup
-          v-model="radioValue"
-          :options="options"
-          v-bind="compProps"
-        />
-      </div>
-      <p class="mt-4">单选插槽:{{ radioValue }}</p>
-      <div class="mt-2 flex flex-col gap-2">
-        <VbenCheckButtonGroup
-          v-model="radioValue"
-          :options="options"
-          v-bind="compProps"
-        >
-          <template #option="{ label, value }">
-            <div class="flex items-center">
-              <span>{{ label }}</span>
-              <span class="ml-2 text-gray-400">{{ value }}</span>
-            </div>
-          </template>
-        </VbenCheckButtonGroup>
-      </div>
-      <p class="mt-4">多选{{ checkValue }}</p>
-      <div class="mt-2 flex flex-col gap-2">
-        <VbenCheckButtonGroup
-          v-model="checkValue"
-          multiple
-          :options="options"
-          v-bind="compProps"
-        />
-      </div>
-    </Card>
-
-    <Card title="设置" class="mt-4">
-      <Form />
-    </Card>
-  </Page>
-</template>

+ 0 - 178
playground/src/views/examples/count-to/index.vue

@@ -1,178 +0,0 @@
-<script lang="ts" setup>
-import type { CountToProps, TransitionPresets } from '@vben/common-ui';
-
-import { reactive } from 'vue';
-
-import { CountTo, Page, TransitionPresetsKeys } from '@vben/common-ui';
-import { IconifyIcon } from '@vben/icons';
-
-import {
-  Button,
-  Card,
-  Col,
-  Form,
-  FormItem,
-  Input,
-  InputNumber,
-  message,
-  Row,
-  Select,
-  Switch,
-} from 'ant-design-vue';
-
-const props = reactive<CountToProps & { transition: TransitionPresets }>({
-  decimal: '.',
-  decimals: 2,
-  decimalStyle: {
-    fontSize: 'small',
-    fontStyle: 'italic',
-  },
-  delay: 0,
-  disabled: false,
-  duration: 2000,
-  endVal: 100_000,
-  mainStyle: {
-    color: 'hsl(var(--primary))',
-    fontSize: 'xx-large',
-    fontWeight: 'bold',
-  },
-  prefix: '¥',
-  prefixStyle: {
-    paddingRight: '0.5rem',
-  },
-  separator: ',',
-  startVal: 0,
-  suffix: '元',
-  suffixStyle: {
-    paddingLeft: '0.5rem',
-  },
-  transition: 'easeOutQuart',
-});
-
-function changeNumber() {
-  props.endVal =
-    Math.floor(Math.random() * 100_000_000) / 10 ** (props.decimals || 0);
-}
-
-function openDocumentation() {
-  window.open('https://vueuse.org/core/useTransition/', '_blank');
-}
-
-function onStarted() {
-  message.loading({
-    content: '动画已开始',
-    duration: 0,
-    key: 'animator-info',
-  });
-}
-
-function onFinished() {
-  message.success({
-    content: '动画已结束',
-    duration: 2,
-    key: 'animator-info',
-  });
-}
-</script>
-<template>
-  <Page title="CountTo" description="数字滚动动画组件。使用">
-    <template #description>
-      <span>
-        使用useTransition封装的数字滚动动画组件,每次改变当前值都会产生过渡动画。
-      </span>
-      <Button type="link" @click="openDocumentation">
-        查看useTransition文档
-      </Button>
-    </template>
-    <Card title="基本用法">
-      <div class="flex w-full items-center justify-center pb-4">
-        <CountTo v-bind="props" @started="onStarted" @finished="onFinished" />
-      </div>
-      <Form :model="props">
-        <Row :gutter="20">
-          <Col :span="8">
-            <FormItem label="初始值" name="startVal">
-              <InputNumber v-model:value="props.startVal" />
-            </FormItem>
-          </Col>
-          <Col :span="8">
-            <FormItem label="当前值" name="endVal">
-              <InputNumber
-                v-model:value="props.endVal"
-                class="w-full"
-                :precision="props.decimals"
-              >
-                <template #addonAfter>
-                  <IconifyIcon
-                    v-tippy="`设置一个随机值`"
-                    class="size-5 cursor-pointer outline-none"
-                    icon="ix:random-filled"
-                    @click="changeNumber"
-                  />
-                </template>
-              </InputNumber>
-            </FormItem>
-          </Col>
-          <Col :span="8">
-            <FormItem label="禁用动画" name="disabled">
-              <Switch v-model:checked="props.disabled" />
-            </FormItem>
-          </Col>
-          <Col :span="8">
-            <FormItem label="延迟动画" name="delay">
-              <InputNumber v-model:value="props.delay" :min="0" />
-            </FormItem>
-          </Col>
-          <Col :span="8">
-            <FormItem label="持续时间" name="duration">
-              <InputNumber v-model:value="props.duration" :min="0" />
-            </FormItem>
-          </Col>
-
-          <Col :span="8">
-            <FormItem label="小数位数" name="decimals">
-              <InputNumber
-                v-model:value="props.decimals"
-                :min="0"
-                :precision="0"
-              />
-            </FormItem>
-          </Col>
-          <Col :span="8">
-            <FormItem label="分隔符" name="separator">
-              <Input v-model:value="props.separator" />
-            </FormItem>
-          </Col>
-          <Col :span="8">
-            <FormItem label="小数点" name="decimal">
-              <Input v-model:value="props.decimal" />
-            </FormItem>
-          </Col>
-          <Col :span="8">
-            <FormItem label="动画" name="transition">
-              <Select v-model:value="props.transition">
-                <Select.Option
-                  v-for="preset in TransitionPresetsKeys"
-                  :key="preset"
-                  :value="preset"
-                >
-                  {{ preset }}
-                </Select.Option>
-              </Select>
-            </FormItem>
-          </Col>
-          <Col :span="8">
-            <FormItem label="前缀" name="prefix">
-              <Input v-model:value="props.prefix" />
-            </FormItem>
-          </Col>
-          <Col :span="8">
-            <FormItem label="后缀" name="suffix">
-              <Input v-model:value="props.suffix" />
-            </FormItem>
-          </Col>
-        </Row>
-      </Form>
-    </Card>
-  </Page>
-</template>

+ 0 - 111
playground/src/views/examples/form/custom-layout.vue

@@ -1,111 +0,0 @@
-<script lang="ts" setup>
-import { h } from 'vue';
-
-import { Page } from '@vben/common-ui';
-
-import { Card } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-
-import DocButton from '../doc-button.vue';
-
-const [CustomLayoutForm] = useVbenForm({
-  // 所有表单项共用,可单独在表单内覆盖
-  commonConfig: {
-    // 所有表单项
-    componentProps: {
-      class: 'w-full',
-    },
-  },
-  layout: 'horizontal',
-  schema: [
-    {
-      component: 'Select',
-      fieldName: 'field1',
-      label: '字符串',
-    },
-    {
-      component: 'TreeSelect',
-      fieldName: 'field2',
-      label: '字符串',
-    },
-    {
-      component: 'Mentions',
-      fieldName: 'field3',
-      label: '字符串',
-    },
-    {
-      component: 'Input',
-      fieldName: 'field4',
-      label: '字符串',
-    },
-    {
-      component: 'InputNumber',
-      fieldName: 'field5',
-      // 从第三列开始 相当于中间空了一列
-      formItemClass: 'col-start-3',
-      label: '前面空了一列',
-    },
-    {
-      component: 'Divider',
-      fieldName: '_divider',
-      formItemClass: 'col-span-3',
-      hideLabel: true,
-      renderComponentContent: () => {
-        return {
-          default: () => h('div', '分割线'),
-        };
-      },
-    },
-    {
-      component: 'Textarea',
-      fieldName: 'field6',
-      // 占满三列空间 基线对齐
-      formItemClass: 'col-span-3 items-baseline',
-      label: '占满三列',
-    },
-    {
-      component: 'Input',
-      fieldName: 'field7',
-      // 占满2列空间 从第二列开始 相当于前面空了一列
-      formItemClass: 'col-span-2 col-start-2',
-      label: '占满2列',
-    },
-    {
-      component: 'Input',
-      fieldName: 'field8',
-      // 左右留空
-      formItemClass: 'col-start-2',
-      label: '左右留空',
-    },
-    {
-      component: 'InputPassword',
-      fieldName: 'field9',
-      formItemClass: 'col-start-1',
-      label: '字符串',
-    },
-  ],
-  // 一共三列
-  wrapperClass: 'grid-cols-3',
-});
-</script>
-
-<template>
-  <Page
-    content-class="flex flex-col gap-4"
-    description="使用tailwind自定义表单项的布局"
-    title="表单自定义布局"
-  >
-    <template #description>
-      <div class="text-muted-foreground">
-        <p>使用tailwind自定义表单项的布局,使用Divider分割表单。</p>
-      </div>
-    </template>
-    <template #extra>
-      <DocButton class="mb-2" path="/components/common-ui/vben-form" />
-    </template>
-    <Card title="使用tailwind自定义布局">
-      <CustomLayoutForm />
-    </Card>
-  </Page>
-</template>

+ 0 - 66
playground/src/views/examples/json-viewer/data.ts

@@ -1,66 +0,0 @@
-export const json1 = {
-  additionalInfo: {
-    author: 'Your Name',
-    debug: true,
-    version: '1.3.10',
-    versionCode: 132,
-  },
-  additionalNotes: 'This JSON is used for demonstration purposes',
-  tools: [
-    {
-      description: 'Description of Tool 1',
-      name: 'Tool 1',
-    },
-    {
-      description: 'Description of Tool 2',
-      name: 'Tool 2',
-    },
-    {
-      description: 'Description of Tool 3',
-      name: 'Tool 3',
-    },
-    {
-      description: 'Description of Tool 4',
-      name: 'Tool 4',
-    },
-  ],
-};
-
-export const json2 = JSON.parse(`
-  {
-	"id": "chatcmpl-123",
-	"object": "chat.completion",
-	"created": 1677652288,
-	"model": "gpt-3.5-turbo-0613",
-	"system_fingerprint": "fp_44709d6fcb",
-	"choices": [{
-		"index": 0,
-		"message": {
-			"role": "assistant",
-			"content": "Hello there, how may I assist you today?"
-		},
-		"finish_reason": "stop"
-	}],
-	"usage": {
-		"prompt_tokens": 9,
-		"completion_tokens": 12,
-		"total_tokens": 21,
-    "debug_mode": true
-	},
-  "debug": {
-    "startAt": "2021-08-01T00:00:00Z",
-    "logs": [
-      {
-        "timestamp": "2021-08-01T00:00:00Z",
-        "message": "This is a debug message",
-        "extra":[ "extra1", "extra2" ]
-      },
-      {
-        "timestamp": "2021-08-01T00:00:01Z",
-        "message": "This is another debug message",
-        "extra":[ "extra3", "extra4" ]
-      }
-    ]
-  }
-}
-  `);

+ 0 - 51
playground/src/views/examples/json-viewer/index.vue

@@ -1,51 +0,0 @@
-<script lang="ts" setup>
-import type { JsonViewerAction, JsonViewerValue } from '@vben/common-ui';
-
-import { JsonViewer, Page } from '@vben/common-ui';
-
-import { Card, message } from 'ant-design-vue';
-
-import { json1, json2 } from './data';
-
-function handleKeyClick(key: string) {
-  message.info(`点击了Key ${key}`);
-}
-
-function handleValueClick(value: JsonViewerValue) {
-  message.info(`点击了Value ${JSON.stringify(value)}`);
-}
-
-function handleCopied(_event: JsonViewerAction) {
-  message.success('已复制JSON');
-}
-</script>
-<template>
-  <Page
-    title="Json Viewer"
-    description="一个渲染 JSON 结构数据的组件,支持复制、展开等,简单易用"
-  >
-    <Card title="默认配置">
-      <JsonViewer :value="json1" />
-    </Card>
-    <Card title="可复制、默认展开3层、显示边框、事件处理" class="mt-4">
-      <JsonViewer
-        :value="json2"
-        :expand-depth="3"
-        copyable
-        :sort="false"
-        @key-click="handleKeyClick"
-        @value-click="handleValueClick"
-        @copied="handleCopied"
-        boxed
-      />
-    </Card>
-    <Card title="预览模式" class="mt-4">
-      <JsonViewer
-        :value="json2"
-        copyable
-        preview-mode
-        :show-array-index="false"
-      />
-    </Card>
-  </Page>
-</template>

+ 0 - 101
playground/src/views/examples/loading/index.vue

@@ -1,101 +0,0 @@
-<script lang="ts" setup>
-import { Loading, Page, Spinner } from '@vben/common-ui';
-import { IconifyIcon } from '@vben/icons';
-
-import { refAutoReset } from '@vueuse/core';
-import { Button, Card, Spin } from 'ant-design-vue';
-
-const spinning = refAutoReset(false, 3000);
-const loading = refAutoReset(false, 3000);
-
-const spinningV = refAutoReset(false, 3000);
-const loadingV = refAutoReset(false, 3000);
-</script>
-<template>
-  <Page
-    title="Vben Loading"
-    description="加载中状态组件。这个组件可以为其它作为容器的组件添加一个加载中的遮罩层。使用它们时,容器需要relative定位。"
-  >
-    <Card title="Antd Spin">
-      <template #actions>这是Antd 组件库自带的Spin组件演示</template>
-      <Spin :spinning="spinning" tip="加载中...">
-        <Button type="primary" @click="spinning = true">显示Spin</Button>
-      </Spin>
-    </Card>
-
-    <Card title="Vben Loading" v-loading="loadingV" class="mt-4">
-      <template #extra>
-        <Button type="primary" @click="loadingV = true">
-          v-loading 指令
-        </Button>
-      </template>
-      <template #actions>
-        Loading组件可以设置文字,并且也提供了icon插槽用于替换加载图标。
-      </template>
-      <div class="flex gap-4">
-        <div class="size-40">
-          <Loading
-            :spinning="loading"
-            text="正在加载..."
-            class="flex h-full w-full items-center justify-center"
-          >
-            <Button type="primary" @click="loading = true">默认动画</Button>
-          </Loading>
-        </div>
-        <div class="size-40">
-          <Loading
-            :spinning="loading"
-            class="flex h-full w-full items-center justify-center"
-          >
-            <Button type="primary" @click="loading = true">自定义动画1</Button>
-            <template #icon>
-              <IconifyIcon
-                icon="svg-spinners:ring-resize"
-                class="text-primary size-10"
-              />
-            </template>
-          </Loading>
-        </div>
-        <div class="size-40">
-          <Loading
-            :spinning="loading"
-            class="flex h-full w-full items-center justify-center"
-          >
-            <Button type="primary" @click="loading = true">自定义动画2</Button>
-            <template #icon>
-              <IconifyIcon
-                icon="svg-spinners:bars-scale"
-                class="text-primary size-10"
-              />
-            </template>
-          </Loading>
-        </div>
-      </div>
-    </Card>
-
-    <Card
-      title="Vben Spinner"
-      v-spinning="spinningV"
-      class="mt-4 overflow-hidden"
-      :body-style="{
-        position: 'relative',
-        overflow: 'hidden',
-      }"
-    >
-      <template #extra>
-        <Button type="primary" @click="spinningV = true">
-          v-spinning 指令
-        </Button>
-      </template>
-      <template #actions>
-        Spinner组件是Loading组件的一个特例,只有一个固定的统一样式。
-      </template>
-      <Spinner
-        :spinning="spinning"
-        class="flex size-40 items-center justify-center"
-      >
-        <Button type="primary" @click="spinning = true">显示Spinner</Button>
-      </Spinner>
-    </Card>
-  </Page>
-</template>

+ 0 - 213
playground/src/views/examples/motion/index.vue

@@ -1,213 +0,0 @@
-<script lang="ts" setup>
-import { reactive } from 'vue';
-
-import { Page } from '@vben/common-ui';
-import { Motion, MotionGroup, MotionPresets } from '@vben/plugins/motion';
-
-import { refAutoReset, watchDebounced } from '@vueuse/core';
-import {
-  Button,
-  Card,
-  Col,
-  Form,
-  FormItem,
-  InputNumber,
-  Row,
-  Select,
-} from 'ant-design-vue';
-// 本例子用不到visible类型的动画。带有VisibleOnce和Visible的类型会在组件进入视口被显示时执行动画,
-const presets = MotionPresets.filter((v) => !v.includes('Visible'));
-const showCard1 = refAutoReset(true, 100);
-const showCard2 = refAutoReset(true, 100);
-const showCard3 = refAutoReset(true, 100);
-const motionProps = reactive({
-  delay: 0,
-  duration: 300,
-  enter: { scale: 1 },
-  hovered: { scale: 1.1, transition: { delay: 0, duration: 50 } },
-  preset: 'fade',
-  tapped: { scale: 0.9, transition: { delay: 0, duration: 50 } },
-});
-
-const motionGroupProps = reactive({
-  delay: 0,
-  duration: 300,
-  enter: { scale: 1 },
-  hovered: { scale: 1.1, transition: { delay: 0, duration: 50 } },
-  preset: 'fade',
-  tapped: { scale: 0.9, transition: { delay: 0, duration: 50 } },
-});
-
-watchDebounced(
-  motionProps,
-  () => {
-    showCard2.value = false;
-  },
-  { debounce: 200, deep: true },
-);
-
-watchDebounced(
-  motionGroupProps,
-  () => {
-    showCard3.value = false;
-  },
-  { debounce: 200, deep: true },
-);
-
-function openDocPage() {
-  window.open('https://motion.vueuse.org/', '_blank');
-}
-</script>
-<template>
-  <Page title="Motion">
-    <template #description>
-      <span>一个易于使用的为其它组件赋予动画效果的组件。</span>
-      <Button type="link" @click="openDocPage">查看文档</Button>
-    </template>
-    <Card title="使用指令" :body-style="{ minHeight: '5rem' }">
-      <template #extra>
-        <Button type="primary" @click="showCard1 = false">重载</Button>
-      </template>
-      <div>
-        <div class="relative flex gap-2 overflow-hidden" v-if="showCard1">
-          <Button v-motion-fade-visible>fade</Button>
-          <Button v-motion-pop-visible :duration="500">pop</Button>
-          <Button v-motion-slide-left>slide-left</Button>
-          <Button v-motion-slide-right>slide-right</Button>
-          <Button v-motion-slide-bottom>slide-bottom</Button>
-          <Button v-motion-slide-top>slide-top</Button>
-        </div>
-      </div>
-    </Card>
-    <Card
-      class="mt-2"
-      title="使用组件(将内部作为一个整体添加动画)"
-      :body-style="{ padding: 0 }"
-    >
-      <div
-        class="relative flex min-h-32 items-center justify-center gap-2 overflow-hidden"
-      >
-        <Motion
-          v-bind="motionProps"
-          v-if="showCard2"
-          class="flex items-center gap-2"
-        >
-          <Button size="large">这个按钮在显示时会有动画效果</Button>
-          <span>附属组件,会作为整体处理动画</span>
-        </Motion>
-      </div>
-      <div
-        class="relative flex min-h-32 items-center justify-center gap-2 overflow-hidden"
-      >
-        <div v-if="showCard2" class="flex items-center gap-2">
-          <span>顺序延迟</span>
-          <Motion
-            v-bind="{
-              ...motionProps,
-              delay: motionProps.delay + 100 * i,
-            }"
-            v-for="i in 5"
-            :key="i"
-          >
-            <Button size="large">按钮{{ i }}</Button>
-          </Motion>
-        </div>
-      </div>
-      <div>
-        <Form :model="motionProps" :label-col="{ span: 10 }">
-          <Row>
-            <Col :span="8">
-              <FormItem prop="preset" label="动画效果">
-                <Select v-model:value="motionProps.preset">
-                  <Select.Option
-                    :value="preset"
-                    v-for="preset in presets"
-                    :key="preset"
-                  >
-                    {{ preset }}
-                  </Select.Option>
-                </Select>
-              </FormItem>
-            </Col>
-            <Col :span="8">
-              <FormItem prop="duration" label="持续时间">
-                <InputNumber v-model:value="motionProps.duration" />
-              </FormItem>
-            </Col>
-            <Col :span="8">
-              <FormItem prop="delay" label="延迟动画">
-                <InputNumber v-model:value="motionProps.delay" />
-              </FormItem>
-            </Col>
-            <Col :span="8">
-              <FormItem prop="hovered.scale" label="Hover缩放">
-                <InputNumber v-model:value="motionProps.hovered.scale" />
-              </FormItem>
-            </Col>
-            <Col :span="8">
-              <FormItem prop="hovered.tapped" label="按下时缩放">
-                <InputNumber v-model:value="motionProps.tapped.scale" />
-              </FormItem>
-            </Col>
-          </Row>
-        </Form>
-      </div>
-    </Card>
-    <Card
-      class="mt-2"
-      title="分组动画(每个子元素都会应用相同的独立动画)"
-      :body-style="{ padding: 0 }"
-    >
-      <div
-        class="relative flex min-h-32 items-center justify-center gap-2 overflow-hidden"
-      >
-        <MotionGroup v-bind="motionGroupProps" v-if="showCard3">
-          <Button size="large">按钮1</Button>
-          <Button size="large">按钮2</Button>
-          <Button size="large">按钮3</Button>
-          <Button size="large">按钮4</Button>
-          <Button size="large">按钮5</Button>
-        </MotionGroup>
-      </div>
-      <div>
-        <Form :model="motionGroupProps" :label-col="{ span: 10 }">
-          <Row>
-            <Col :span="8">
-              <FormItem prop="preset" label="动画效果">
-                <Select v-model:value="motionGroupProps.preset">
-                  <Select.Option
-                    :value="preset"
-                    v-for="preset in presets"
-                    :key="preset"
-                  >
-                    {{ preset }}
-                  </Select.Option>
-                </Select>
-              </FormItem>
-            </Col>
-            <Col :span="8">
-              <FormItem prop="duration" label="持续时间">
-                <InputNumber v-model:value="motionGroupProps.duration" />
-              </FormItem>
-            </Col>
-            <Col :span="8">
-              <FormItem prop="delay" label="延迟动画">
-                <InputNumber v-model:value="motionGroupProps.delay" />
-              </FormItem>
-            </Col>
-            <Col :span="8">
-              <FormItem prop="hovered.scale" label="Hover缩放">
-                <InputNumber v-model:value="motionGroupProps.hovered.scale" />
-              </FormItem>
-            </Col>
-            <Col :span="8">
-              <FormItem prop="hovered.tapped" label="按下时缩放">
-                <InputNumber v-model:value="motionGroupProps.tapped.scale" />
-              </FormItem>
-            </Col>
-          </Row>
-        </Form>
-      </div>
-    </Card>
-  </Page>
-</template>

+ 0 - 135
playground/src/views/system/dept/data.ts

@@ -1,135 +0,0 @@
-import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';
-
-import type { VbenFormSchema } from '#/adapter/form';
-import type { OnActionClickFn } from '#/adapter/vxe-table';
-import type { SystemDeptApi } from '#/api/system/dept';
-
-import { z } from '#/adapter/form';
-import { getDeptList } from '#/api/system/dept';
-import { $t } from '#/locales';
-
-/**
- * 获取编辑表单的字段配置。如果没有使用多语言,可以直接export一个数组常量
- */
-export function useSchema(): VbenFormSchema[] {
-  return [
-    {
-      component: 'Input',
-      fieldName: 'name',
-      label: $t('system.dept.deptName'),
-      rules: z
-        .string()
-        .min(2, $t('ui.formRules.minLength', [$t('system.dept.deptName'), 2]))
-        .max(
-          20,
-          $t('ui.formRules.maxLength', [$t('system.dept.deptName'), 20]),
-        ),
-    },
-    {
-      component: 'ApiTreeSelect',
-      componentProps: {
-        allowClear: true,
-        api: getDeptList,
-        class: 'w-full',
-        labelField: 'name',
-        valueField: 'id',
-        childrenField: 'children',
-      },
-      fieldName: 'pid',
-      label: $t('system.dept.parentDept'),
-    },
-    {
-      component: 'RadioGroup',
-      componentProps: {
-        buttonStyle: 'solid',
-        options: [
-          { label: $t('common.enabled'), value: 1 },
-          { label: $t('common.disabled'), value: 0 },
-        ],
-        optionType: 'button',
-      },
-      defaultValue: 1,
-      fieldName: 'status',
-      label: $t('system.dept.status'),
-    },
-    {
-      component: 'Textarea',
-      componentProps: {
-        maxLength: 50,
-        rows: 3,
-        showCount: true,
-      },
-      fieldName: 'remark',
-      label: $t('system.dept.remark'),
-      rules: z
-        .string()
-        .max(50, $t('ui.formRules.maxLength', [$t('system.dept.remark'), 50]))
-        .optional(),
-    },
-  ];
-}
-
-/**
- * 获取表格列配置
- * @description 使用函数的形式返回列数据而不是直接export一个Array常量,是为了响应语言切换时重新翻译表头
- * @param onActionClick 表格操作按钮点击事件
- */
-export function useColumns(
-  onActionClick?: OnActionClickFn<SystemDeptApi.SystemDept>,
-): VxeTableGridOptions<SystemDeptApi.SystemDept>['columns'] {
-  return [
-    {
-      align: 'left',
-      field: 'name',
-      fixed: 'left',
-      title: $t('system.dept.deptName'),
-      treeNode: true,
-      width: 150,
-    },
-    {
-      cellRender: { name: 'CellTag' },
-      field: 'status',
-      title: $t('system.dept.status'),
-      width: 100,
-    },
-    {
-      field: 'createTime',
-      title: $t('system.dept.createTime'),
-      width: 180,
-    },
-    {
-      field: 'remark',
-      title: $t('system.dept.remark'),
-    },
-    {
-      align: 'right',
-      cellRender: {
-        attrs: {
-          nameField: 'name',
-          nameTitle: $t('system.dept.name'),
-          onClick: onActionClick,
-        },
-        name: 'CellOperation',
-        options: [
-          {
-            code: 'append',
-            text: '新增下级',
-          },
-          'edit', // 默认的编辑按钮
-          {
-            code: 'delete', // 默认的删除按钮
-            disabled: (row: SystemDeptApi.SystemDept) => {
-              return !!(row.children && row.children.length > 0);
-            },
-          },
-        ],
-      },
-      field: 'operation',
-      fixed: 'right',
-      headerAlign: 'center',
-      showOverflow: false,
-      title: $t('system.dept.operation'),
-      width: 200,
-    },
-  ];
-}

+ 0 - 143
playground/src/views/system/dept/list.vue

@@ -1,143 +0,0 @@
-<script lang="ts" setup>
-import type {
-  OnActionClickParams,
-  VxeTableGridOptions,
-} from '#/adapter/vxe-table';
-import type { SystemDeptApi } from '#/api/system/dept';
-
-import { Page, useVbenModal } from '@vben/common-ui';
-import { Plus } from '@vben/icons';
-
-import { Button, message } from 'ant-design-vue';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import { deleteDept, getDeptList } from '#/api/system/dept';
-import { $t } from '#/locales';
-
-import { useColumns } from './data';
-import Form from './modules/form.vue';
-
-const [FormModal, formModalApi] = useVbenModal({
-  connectedComponent: Form,
-  destroyOnClose: true,
-});
-
-/**
- * 编辑部门
- * @param row
- */
-function onEdit(row: SystemDeptApi.SystemDept) {
-  formModalApi.setData(row).open();
-}
-
-/**
- * 添加下级部门
- * @param row
- */
-function onAppend(row: SystemDeptApi.SystemDept) {
-  formModalApi.setData({ pid: row.id }).open();
-}
-
-/**
- * 创建新部门
- */
-function onCreate() {
-  formModalApi.setData(null).open();
-}
-
-/**
- * 删除部门
- * @param row
- */
-function onDelete(row: SystemDeptApi.SystemDept) {
-  const hideLoading = message.loading({
-    content: $t('ui.actionMessage.deleting', [row.name]),
-    duration: 0,
-    key: 'action_process_msg',
-  });
-  deleteDept(row.id)
-    .then(() => {
-      message.success({
-        content: $t('ui.actionMessage.deleteSuccess', [row.name]),
-        key: 'action_process_msg',
-      });
-      refreshGrid();
-    })
-    .catch(() => {
-      hideLoading();
-    });
-}
-
-/**
- * 表格操作按钮的回调函数
- */
-function onActionClick({
-  code,
-  row,
-}: OnActionClickParams<SystemDeptApi.SystemDept>) {
-  switch (code) {
-    case 'append': {
-      onAppend(row);
-      break;
-    }
-    case 'delete': {
-      onDelete(row);
-      break;
-    }
-    case 'edit': {
-      onEdit(row);
-      break;
-    }
-  }
-}
-
-const [Grid, gridApi] = useVbenVxeGrid({
-  gridEvents: {},
-  gridOptions: {
-    columns: useColumns(onActionClick),
-    height: 'auto',
-    keepSource: true,
-    pagerConfig: {
-      enabled: false,
-    },
-    proxyConfig: {
-      ajax: {
-        query: async (_params) => {
-          return await getDeptList();
-        },
-      },
-    },
-    toolbarConfig: {
-      custom: true,
-      export: false,
-      refresh: { code: 'query' },
-      zoom: true,
-    },
-    treeConfig: {
-      parentField: 'pid',
-      rowField: 'id',
-      transform: false,
-    },
-  } as VxeTableGridOptions,
-});
-
-/**
- * 刷新表格
- */
-function refreshGrid() {
-  gridApi.query();
-}
-</script>
-<template>
-  <Page auto-content-height>
-    <FormModal @success="refreshGrid" />
-    <Grid table-title="部门列表">
-      <template #toolbar-tools>
-        <Button type="primary" @click="onCreate">
-          <Plus class="size-5" />
-          {{ $t('ui.actionTitle.create', [$t('system.dept.name')]) }}
-        </Button>
-      </template>
-    </Grid>
-  </Page>
-</template>

+ 0 - 78
playground/src/views/system/dept/modules/form.vue

@@ -1,78 +0,0 @@
-<script lang="ts" setup>
-import type { SystemDeptApi } from '#/api/system/dept';
-
-import { computed, ref } from 'vue';
-
-import { useVbenModal } from '@vben/common-ui';
-
-import { Button } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import { createDept, updateDept } from '#/api/system/dept';
-import { $t } from '#/locales';
-
-import { useSchema } from '../data';
-
-const emit = defineEmits(['success']);
-const formData = ref<SystemDeptApi.SystemDept>();
-const getTitle = computed(() => {
-  return formData.value?.id
-    ? $t('ui.actionTitle.edit', [$t('system.dept.name')])
-    : $t('ui.actionTitle.create', [$t('system.dept.name')]);
-});
-
-const [Form, formApi] = useVbenForm({
-  layout: 'vertical',
-  schema: useSchema(),
-  showDefaultActions: false,
-});
-
-function resetForm() {
-  formApi.resetForm();
-  formApi.setValues(formData.value || {});
-}
-
-const [Modal, modalApi] = useVbenModal({
-  async onConfirm() {
-    const { valid } = await formApi.validate();
-    if (valid) {
-      modalApi.lock();
-      const data = formApi.getValues();
-      try {
-        await (formData.value?.id
-          ? updateDept(formData.value.id, data)
-          : createDept(data));
-        modalApi.close();
-        emit('success');
-      } finally {
-        modalApi.lock(false);
-      }
-    }
-  },
-  onOpenChange(isOpen) {
-    if (isOpen) {
-      const data = modalApi.getData<SystemDeptApi.SystemDept>();
-      if (data) {
-        if (data.pid === 0) {
-          data.pid = undefined;
-        }
-        formData.value = data;
-        formApi.setValues(formData.value);
-      }
-    }
-  },
-});
-</script>
-
-<template>
-  <Modal :title="getTitle">
-    <Form class="mx-4" />
-    <template #prepend-footer>
-      <div class="flex-auto">
-        <Button type="primary" danger @click="resetForm">
-          {{ $t('common.reset') }}
-        </Button>
-      </div>
-    </template>
-  </Modal>
-</template>

+ 0 - 109
playground/src/views/system/menu/data.ts

@@ -1,109 +0,0 @@
-import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { SystemMenuApi } from '#/api/system/menu';
-
-import { $t } from '#/locales';
-
-export function getMenuTypeOptions() {
-  return [
-    {
-      color: 'processing',
-      label: $t('system.menu.typeCatalog'),
-      value: 'catalog',
-    },
-    { color: 'default', label: $t('system.menu.typeMenu'), value: 'menu' },
-    { color: 'error', label: $t('system.menu.typeButton'), value: 'button' },
-    {
-      color: 'success',
-      label: $t('system.menu.typeEmbedded'),
-      value: 'embedded',
-    },
-    { color: 'warning', label: $t('system.menu.typeLink'), value: 'link' },
-  ];
-}
-
-export function useColumns(
-  onActionClick: OnActionClickFn<SystemMenuApi.SystemMenu>,
-): VxeTableGridOptions<SystemMenuApi.SystemMenu>['columns'] {
-  return [
-    {
-      align: 'left',
-      field: 'meta.title',
-      fixed: 'left',
-      slots: { default: 'title' },
-      title: $t('system.menu.menuTitle'),
-      treeNode: true,
-      width: 250,
-    },
-    {
-      align: 'center',
-      cellRender: { name: 'CellTag', options: getMenuTypeOptions() },
-      field: 'type',
-      title: $t('system.menu.type'),
-      width: 100,
-    },
-    {
-      field: 'authCode',
-      title: $t('system.menu.authCode'),
-      width: 200,
-    },
-    {
-      align: 'left',
-      field: 'path',
-      title: $t('system.menu.path'),
-      width: 200,
-    },
-
-    {
-      align: 'left',
-      field: 'component',
-      formatter: ({ row }) => {
-        switch (row.type) {
-          case 'catalog':
-          case 'menu': {
-            return row.component ?? '';
-          }
-          case 'embedded': {
-            return row.meta?.iframeSrc ?? '';
-          }
-          case 'link': {
-            return row.meta?.link ?? '';
-          }
-        }
-        return '';
-      },
-      minWidth: 200,
-      title: $t('system.menu.component'),
-    },
-    {
-      cellRender: { name: 'CellTag' },
-      field: 'status',
-      title: $t('system.menu.status'),
-      width: 100,
-    },
-
-    {
-      align: 'right',
-      cellRender: {
-        attrs: {
-          nameField: 'name',
-          onClick: onActionClick,
-        },
-        name: 'CellOperation',
-        options: [
-          {
-            code: 'append',
-            text: '新增下级',
-          },
-          'edit', // 默认的编辑按钮
-          'delete', // 默认的删除按钮
-        ],
-      },
-      field: 'operation',
-      fixed: 'right',
-      headerAlign: 'center',
-      showOverflow: false,
-      title: $t('system.menu.operation'),
-      width: 200,
-    },
-  ];
-}

+ 0 - 162
playground/src/views/system/menu/list.vue

@@ -1,162 +0,0 @@
-<script lang="ts" setup>
-import type {
-  OnActionClickParams,
-  VxeTableGridOptions,
-} from '#/adapter/vxe-table';
-
-import { Page, useVbenDrawer } from '@vben/common-ui';
-import { IconifyIcon, Plus } from '@vben/icons';
-import { $t } from '@vben/locales';
-
-import { MenuBadge } from '@vben-core/menu-ui';
-
-import { Button, message } from 'ant-design-vue';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import { deleteMenu, getMenuList, SystemMenuApi } from '#/api/system/menu';
-
-import { useColumns } from './data';
-import Form from './modules/form.vue';
-
-const [FormDrawer, formDrawerApi] = useVbenDrawer({
-  connectedComponent: Form,
-  destroyOnClose: true,
-});
-
-const [Grid, gridApi] = useVbenVxeGrid({
-  gridOptions: {
-    columns: useColumns(onActionClick),
-    height: 'auto',
-    keepSource: true,
-    pagerConfig: {
-      enabled: false,
-    },
-    proxyConfig: {
-      ajax: {
-        query: async (_params) => {
-          return await getMenuList();
-        },
-      },
-    },
-    rowConfig: {
-      keyField: 'id',
-    },
-    toolbarConfig: {
-      custom: true,
-      export: false,
-      refresh: { code: 'query' },
-      zoom: true,
-    },
-    treeConfig: {
-      parentField: 'pid',
-      rowField: 'id',
-      transform: false,
-    },
-  } as VxeTableGridOptions,
-});
-
-function onActionClick({
-  code,
-  row,
-}: OnActionClickParams<SystemMenuApi.SystemMenu>) {
-  switch (code) {
-    case 'append': {
-      onAppend(row);
-      break;
-    }
-    case 'delete': {
-      onDelete(row);
-      break;
-    }
-    case 'edit': {
-      onEdit(row);
-      break;
-    }
-    default: {
-      break;
-    }
-  }
-}
-
-function onRefresh() {
-  gridApi.query();
-}
-function onEdit(row: SystemMenuApi.SystemMenu) {
-  formDrawerApi.setData(row).open();
-}
-function onCreate() {
-  formDrawerApi.setData({}).open();
-}
-function onAppend(row: SystemMenuApi.SystemMenu) {
-  formDrawerApi.setData({ pid: row.id }).open();
-}
-
-function onDelete(row: SystemMenuApi.SystemMenu) {
-  const hideLoading = message.loading({
-    content: $t('ui.actionMessage.deleting', [row.name]),
-    duration: 0,
-    key: 'action_process_msg',
-  });
-  deleteMenu(row.id)
-    .then(() => {
-      message.success({
-        content: $t('ui.actionMessage.deleteSuccess', [row.name]),
-        key: 'action_process_msg',
-      });
-      onRefresh();
-    })
-    .catch(() => {
-      hideLoading();
-    });
-}
-</script>
-<template>
-  <Page auto-content-height>
-    <FormDrawer @success="onRefresh" />
-    <Grid>
-      <template #toolbar-tools>
-        <Button type="primary" @click="onCreate">
-          <Plus class="size-5" />
-          {{ $t('ui.actionTitle.create', [$t('system.menu.name')]) }}
-        </Button>
-      </template>
-      <template #title="{ row }">
-        <div class="flex w-full items-center gap-1">
-          <div class="size-5 flex-shrink-0">
-            <IconifyIcon
-              v-if="row.type === 'button'"
-              icon="carbon:security"
-              class="size-full"
-            />
-            <IconifyIcon
-              v-else-if="row.meta?.icon"
-              :icon="row.meta?.icon || 'carbon:circle-dash'"
-              class="size-full"
-            />
-          </div>
-          <span class="flex-auto">{{ $t(row.meta?.title) }}</span>
-          <div class="items-center justify-end"></div>
-        </div>
-        <MenuBadge
-          v-if="row.meta?.badgeType"
-          class="menu-badge"
-          :badge="row.meta.badge"
-          :badge-type="row.meta.badgeType"
-          :badge-variants="row.meta.badgeVariants"
-        />
-      </template>
-    </Grid>
-  </Page>
-</template>
-<style lang="scss" scoped>
-.menu-badge {
-  top: 50%;
-  right: 0;
-  transform: translateY(-50%);
-
-  & > :deep(div) {
-    padding-top: 0;
-    padding-bottom: 0;
-  }
-}
-</style>

+ 0 - 521
playground/src/views/system/menu/modules/form.vue

@@ -1,521 +0,0 @@
-<script lang="ts" setup>
-import type { ChangeEvent } from 'ant-design-vue/es/_util/EventInterface';
-
-import type { Recordable } from '@vben/types';
-
-import type { VbenFormSchema } from '#/adapter/form';
-
-import { computed, h, ref } from 'vue';
-
-import { useVbenDrawer } from '@vben/common-ui';
-import { IconifyIcon } from '@vben/icons';
-import { $te } from '@vben/locales';
-import { getPopupContainer } from '@vben/utils';
-
-import { breakpointsTailwind, useBreakpoints } from '@vueuse/core';
-
-import { useVbenForm, z } from '#/adapter/form';
-import {
-  createMenu,
-  getMenuList,
-  isMenuNameExists,
-  isMenuPathExists,
-  SystemMenuApi,
-  updateMenu,
-} from '#/api/system/menu';
-import { $t } from '#/locales';
-import { componentKeys } from '#/router/routes';
-
-import { getMenuTypeOptions } from '../data';
-
-const emit = defineEmits<{
-  success: [];
-}>();
-const formData = ref<SystemMenuApi.SystemMenu>();
-const loading = ref(false);
-const titleSuffix = ref<string>();
-const schema: VbenFormSchema[] = [
-  {
-    component: 'RadioGroup',
-    componentProps: {
-      buttonStyle: 'solid',
-      options: getMenuTypeOptions(),
-      optionType: 'button',
-    },
-    defaultValue: 'menu',
-    fieldName: 'type',
-    formItemClass: 'col-span-2 md:col-span-2',
-    label: $t('system.menu.type'),
-  },
-  {
-    component: 'Input',
-    fieldName: 'name',
-    label: $t('system.menu.menuName'),
-    rules: z
-      .string()
-      .min(2, $t('ui.formRules.minLength', [$t('system.menu.menuName'), 2]))
-      .max(30, $t('ui.formRules.maxLength', [$t('system.menu.menuName'), 30]))
-      .refine(
-        async (value: string) => {
-          return !(await isMenuNameExists(value, formData.value?.id));
-        },
-        (value) => ({
-          message: $t('ui.formRules.alreadyExists', [
-            $t('system.menu.menuName'),
-            value,
-          ]),
-        }),
-      ),
-  },
-  {
-    component: 'ApiTreeSelect',
-    componentProps: {
-      api: getMenuList,
-      class: 'w-full',
-      filterTreeNode(input: string, node: Recordable<any>) {
-        if (!input || input.length === 0) {
-          return true;
-        }
-        const title: string = node.meta?.title ?? '';
-        if (!title) return false;
-        return title.includes(input) || $t(title).includes(input);
-      },
-      getPopupContainer,
-      labelField: 'meta.title',
-      showSearch: true,
-      treeDefaultExpandAll: true,
-      valueField: 'id',
-      childrenField: 'children',
-    },
-    fieldName: 'pid',
-    label: $t('system.menu.parent'),
-    renderComponentContent() {
-      return {
-        title({ label, meta }: { label: string; meta: Recordable<any> }) {
-          const coms = [];
-          if (!label) return '';
-          if (meta?.icon) {
-            coms.push(h(IconifyIcon, { class: 'size-4', icon: meta.icon }));
-          }
-          coms.push(h('span', { class: '' }, $t(label || '')));
-          return h('div', { class: 'flex items-center gap-1' }, coms);
-        },
-      };
-    },
-  },
-  {
-    component: 'Input',
-    componentProps() {
-      // 不需要处理多语言时就无需这么做
-      return {
-        addonAfter: titleSuffix.value,
-        onChange({ target: { value } }: ChangeEvent) {
-          titleSuffix.value = value && $te(value) ? $t(value) : undefined;
-        },
-      };
-    },
-    fieldName: 'meta.title',
-    label: $t('system.menu.menuTitle'),
-    rules: 'required',
-  },
-  {
-    component: 'Input',
-    dependencies: {
-      show: (values) => {
-        return ['catalog', 'embedded', 'menu'].includes(values.type);
-      },
-      triggerFields: ['type'],
-    },
-    fieldName: 'path',
-    label: $t('system.menu.path'),
-    rules: z
-      .string()
-      .min(2, $t('ui.formRules.minLength', [$t('system.menu.path'), 2]))
-      .max(100, $t('ui.formRules.maxLength', [$t('system.menu.path'), 100]))
-      .refine(
-        (value: string) => {
-          return value.startsWith('/');
-        },
-        $t('ui.formRules.startWith', [$t('system.menu.path'), '/']),
-      )
-      .refine(
-        async (value: string) => {
-          return !(await isMenuPathExists(value, formData.value?.id));
-        },
-        (value) => ({
-          message: $t('ui.formRules.alreadyExists', [
-            $t('system.menu.path'),
-            value,
-          ]),
-        }),
-      ),
-  },
-  {
-    component: 'Input',
-    dependencies: {
-      show: (values) => {
-        return ['embedded', 'menu'].includes(values.type);
-      },
-      triggerFields: ['type'],
-    },
-    fieldName: 'activePath',
-    help: $t('system.menu.activePathHelp'),
-    label: $t('system.menu.activePath'),
-    rules: z
-      .string()
-      .min(2, $t('ui.formRules.minLength', [$t('system.menu.path'), 2]))
-      .max(100, $t('ui.formRules.maxLength', [$t('system.menu.path'), 100]))
-      .refine(
-        (value: string) => {
-          return value.startsWith('/');
-        },
-        $t('ui.formRules.startWith', [$t('system.menu.path'), '/']),
-      )
-      .refine(async (value: string) => {
-        return await isMenuPathExists(value, formData.value?.id);
-      }, $t('system.menu.activePathMustExist'))
-      .optional(),
-  },
-  {
-    component: 'IconPicker',
-    componentProps: {
-      prefix: 'carbon',
-    },
-    dependencies: {
-      show: (values) => {
-        return ['catalog', 'embedded', 'link', 'menu'].includes(values.type);
-      },
-      triggerFields: ['type'],
-    },
-    fieldName: 'meta.icon',
-    label: $t('system.menu.icon'),
-  },
-  {
-    component: 'IconPicker',
-    componentProps: {
-      prefix: 'carbon',
-    },
-    dependencies: {
-      show: (values) => {
-        return ['catalog', 'embedded', 'menu'].includes(values.type);
-      },
-      triggerFields: ['type'],
-    },
-    fieldName: 'meta.activeIcon',
-    label: $t('system.menu.activeIcon'),
-  },
-  {
-    component: 'AutoComplete',
-    componentProps: {
-      allowClear: true,
-      class: 'w-full',
-      filterOption(input: string, option: { value: string }) {
-        return option.value.toLowerCase().includes(input.toLowerCase());
-      },
-      options: componentKeys.map((v) => ({ value: v })),
-    },
-    dependencies: {
-      rules: (values) => {
-        return values.type === 'menu' ? 'required' : null;
-      },
-      show: (values) => {
-        return values.type === 'menu';
-      },
-      triggerFields: ['type'],
-    },
-    fieldName: 'component',
-    label: $t('system.menu.component'),
-  },
-  {
-    component: 'Input',
-    dependencies: {
-      show: (values) => {
-        return ['embedded', 'link'].includes(values.type);
-      },
-      triggerFields: ['type'],
-    },
-    fieldName: 'linkSrc',
-    label: $t('system.menu.linkSrc'),
-    rules: z.string().url($t('ui.formRules.invalidURL')),
-  },
-  {
-    component: 'Input',
-    dependencies: {
-      rules: (values) => {
-        return values.type === 'button' ? 'required' : null;
-      },
-      show: (values) => {
-        return ['button', 'catalog', 'embedded', 'menu'].includes(values.type);
-      },
-      triggerFields: ['type'],
-    },
-    fieldName: 'authCode',
-    label: $t('system.menu.authCode'),
-  },
-  {
-    component: 'RadioGroup',
-    componentProps: {
-      buttonStyle: 'solid',
-      options: [
-        { label: $t('common.enabled'), value: 1 },
-        { label: $t('common.disabled'), value: 0 },
-      ],
-      optionType: 'button',
-    },
-    defaultValue: 1,
-    fieldName: 'status',
-    label: $t('system.menu.status'),
-  },
-  {
-    component: 'Select',
-    componentProps: {
-      allowClear: true,
-      class: 'w-full',
-      options: [
-        { label: $t('system.menu.badgeType.dot'), value: 'dot' },
-        { label: $t('system.menu.badgeType.normal'), value: 'normal' },
-      ],
-    },
-    dependencies: {
-      show: (values) => {
-        return values.type !== 'button';
-      },
-      triggerFields: ['type'],
-    },
-    fieldName: 'meta.badgeType',
-    label: $t('system.menu.badgeType.title'),
-  },
-  {
-    component: 'Input',
-    componentProps: (values) => {
-      return {
-        allowClear: true,
-        class: 'w-full',
-        disabled: values.meta?.badgeType !== 'normal',
-      };
-    },
-    dependencies: {
-      show: (values) => {
-        return values.type !== 'button';
-      },
-      triggerFields: ['type'],
-    },
-    fieldName: 'meta.badge',
-    label: $t('system.menu.badge'),
-  },
-  {
-    component: 'Select',
-    componentProps: {
-      allowClear: true,
-      class: 'w-full',
-      options: SystemMenuApi.BadgeVariants.map((v) => ({
-        label: v,
-        value: v,
-      })),
-    },
-    dependencies: {
-      show: (values) => {
-        return values.type !== 'button';
-      },
-      triggerFields: ['type'],
-    },
-    fieldName: 'meta.badgeVariants',
-    label: $t('system.menu.badgeVariants'),
-  },
-  {
-    component: 'Divider',
-    dependencies: {
-      show: (values) => {
-        return !['button', 'link'].includes(values.type);
-      },
-      triggerFields: ['type'],
-    },
-    fieldName: 'divider1',
-    formItemClass: 'col-span-2 md:col-span-2 pb-0',
-    hideLabel: true,
-    renderComponentContent() {
-      return {
-        default: () => $t('system.menu.advancedSettings'),
-      };
-    },
-  },
-  {
-    component: 'Checkbox',
-    dependencies: {
-      show: (values) => {
-        return ['menu'].includes(values.type);
-      },
-      triggerFields: ['type'],
-    },
-    fieldName: 'meta.keepAlive',
-    renderComponentContent() {
-      return {
-        default: () => $t('system.menu.keepAlive'),
-      };
-    },
-  },
-  {
-    component: 'Checkbox',
-    dependencies: {
-      show: (values) => {
-        return ['embedded', 'menu'].includes(values.type);
-      },
-      triggerFields: ['type'],
-    },
-    fieldName: 'meta.affixTab',
-    renderComponentContent() {
-      return {
-        default: () => $t('system.menu.affixTab'),
-      };
-    },
-  },
-  {
-    component: 'Checkbox',
-    dependencies: {
-      show: (values) => {
-        return !['button'].includes(values.type);
-      },
-      triggerFields: ['type'],
-    },
-    fieldName: 'meta.hideInMenu',
-    renderComponentContent() {
-      return {
-        default: () => $t('system.menu.hideInMenu'),
-      };
-    },
-  },
-  {
-    component: 'Checkbox',
-    dependencies: {
-      show: (values) => {
-        return ['catalog', 'menu'].includes(values.type);
-      },
-      triggerFields: ['type'],
-    },
-    fieldName: 'meta.hideChildrenInMenu',
-    renderComponentContent() {
-      return {
-        default: () => $t('system.menu.hideChildrenInMenu'),
-      };
-    },
-  },
-  {
-    component: 'Checkbox',
-    dependencies: {
-      show: (values) => {
-        return !['button', 'link'].includes(values.type);
-      },
-      triggerFields: ['type'],
-    },
-    fieldName: 'meta.hideInBreadcrumb',
-    renderComponentContent() {
-      return {
-        default: () => $t('system.menu.hideInBreadcrumb'),
-      };
-    },
-  },
-  {
-    component: 'Checkbox',
-    dependencies: {
-      show: (values) => {
-        return !['button', 'link'].includes(values.type);
-      },
-      triggerFields: ['type'],
-    },
-    fieldName: 'meta.hideInTab',
-    renderComponentContent() {
-      return {
-        default: () => $t('system.menu.hideInTab'),
-      };
-    },
-  },
-];
-
-const breakpoints = useBreakpoints(breakpointsTailwind);
-const isHorizontal = computed(() => breakpoints.greaterOrEqual('md').value);
-
-const [Form, formApi] = useVbenForm({
-  commonConfig: {
-    colon: true,
-    formItemClass: 'col-span-2 md:col-span-1',
-  },
-  schema,
-  showDefaultActions: false,
-  wrapperClass: 'grid-cols-2 gap-x-4',
-});
-
-const [Drawer, drawerApi] = useVbenDrawer({
-  onBeforeClose() {
-    if (loading.value) return false;
-  },
-  onConfirm: onSubmit,
-  onOpenChange(isOpen) {
-    if (isOpen) {
-      const data = drawerApi.getData<SystemMenuApi.SystemMenu>();
-      if (data?.type === 'link') {
-        data.linkSrc = data.meta?.link;
-      } else if (data?.type === 'embedded') {
-        data.linkSrc = data.meta?.iframeSrc;
-      }
-      if (data) {
-        formData.value = data;
-        formApi.setValues(formData.value);
-        titleSuffix.value = formData.value.meta?.title
-          ? $t(formData.value.meta.title)
-          : '';
-      } else {
-        formApi.resetForm();
-        titleSuffix.value = '';
-      }
-    }
-  },
-});
-
-async function onSubmit() {
-  const { valid } = await formApi.validate();
-  if (valid) {
-    loading.value = true;
-    drawerApi.setState({
-      closeOnClickModal: false,
-      closeOnPressEscape: false,
-      confirmLoading: true,
-      loading: true,
-    });
-    const data =
-      await formApi.getValues<
-        Omit<SystemMenuApi.SystemMenu, 'children' | 'id'>
-      >();
-    if (data.type === 'link') {
-      data.meta = { ...data.meta, link: data.linkSrc };
-    } else if (data.type === 'embedded') {
-      data.meta = { ...data.meta, iframeSrc: data.linkSrc };
-    }
-    delete data.linkSrc;
-    try {
-      await (formData.value?.id
-        ? updateMenu(formData.value.id, data)
-        : createMenu(data));
-      drawerApi.close();
-      emit('success');
-    } finally {
-      loading.value = false;
-      drawerApi.setState({
-        closeOnClickModal: true,
-        closeOnPressEscape: true,
-        confirmLoading: false,
-        loading: false,
-      });
-    }
-  }
-}
-const getDrawerTitle = computed(() =>
-  formData.value?.id
-    ? $t('ui.actionTitle.edit', [$t('system.menu.name')])
-    : $t('ui.actionTitle.create', [$t('system.menu.name')]),
-);
-</script>
-<template>
-  <Drawer class="w-full max-w-[800px]" :title="getDrawerTitle">
-    <Form class="mx-4" :layout="isHorizontal ? 'horizontal' : 'vertical'" />
-  </Drawer>
-</template>

+ 0 - 127
playground/src/views/system/role/data.ts

@@ -1,127 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { SystemRoleApi } from '#/api';
-
-import { $t } from '#/locales';
-
-export function useFormSchema(): VbenFormSchema[] {
-  return [
-    {
-      component: 'Input',
-      fieldName: 'name',
-      label: $t('system.role.roleName'),
-      rules: 'required',
-    },
-    {
-      component: 'RadioGroup',
-      componentProps: {
-        buttonStyle: 'solid',
-        options: [
-          { label: $t('common.enabled'), value: 1 },
-          { label: $t('common.disabled'), value: 0 },
-        ],
-        optionType: 'button',
-      },
-      defaultValue: 1,
-      fieldName: 'status',
-      label: $t('system.role.status'),
-    },
-    {
-      component: 'Textarea',
-      fieldName: 'remark',
-      label: $t('system.role.remark'),
-    },
-    {
-      component: 'Input',
-      fieldName: 'permissions',
-      formItemClass: 'items-start',
-      label: $t('system.role.setPermissions'),
-      modelPropName: 'modelValue',
-    },
-  ];
-}
-
-export function useGridFormSchema(): VbenFormSchema[] {
-  return [
-    {
-      component: 'Input',
-      fieldName: 'name',
-      label: $t('system.role.roleName'),
-    },
-    { component: 'Input', fieldName: 'id', label: $t('system.role.id') },
-    {
-      component: 'Select',
-      componentProps: {
-        allowClear: true,
-        options: [
-          { label: $t('common.enabled'), value: 1 },
-          { label: $t('common.disabled'), value: 0 },
-        ],
-      },
-      fieldName: 'status',
-      label: $t('system.role.status'),
-    },
-    {
-      component: 'Input',
-      fieldName: 'remark',
-      label: $t('system.role.remark'),
-    },
-    {
-      component: 'RangePicker',
-      fieldName: 'createTime',
-      label: $t('system.role.createTime'),
-    },
-  ];
-}
-
-export function useColumns<T = SystemRoleApi.SystemRole>(
-  onActionClick: OnActionClickFn<T>,
-  onStatusChange?: (newStatus: any, row: T) => PromiseLike<boolean | undefined>,
-): VxeTableGridOptions['columns'] {
-  return [
-    {
-      field: 'name',
-      title: $t('system.role.roleName'),
-      width: 200,
-    },
-    {
-      field: 'id',
-      title: $t('system.role.id'),
-      width: 200,
-    },
-    {
-      cellRender: {
-        attrs: { beforeChange: onStatusChange },
-        name: onStatusChange ? 'CellSwitch' : 'CellTag',
-      },
-      field: 'status',
-      title: $t('system.role.status'),
-      width: 100,
-    },
-    {
-      field: 'remark',
-      minWidth: 100,
-      title: $t('system.role.remark'),
-    },
-    {
-      field: 'createTime',
-      title: $t('system.role.createTime'),
-      width: 200,
-    },
-    {
-      align: 'center',
-      cellRender: {
-        attrs: {
-          nameField: 'name',
-          nameTitle: $t('system.role.name'),
-          onClick: onActionClick,
-        },
-        name: 'CellOperation',
-      },
-      field: 'operation',
-      fixed: 'right',
-      title: $t('system.role.operation'),
-      width: 130,
-    },
-  ];
-}

+ 0 - 164
playground/src/views/system/role/list.vue

@@ -1,164 +0,0 @@
-<script lang="ts" setup>
-import type { Recordable } from '@vben/types';
-
-import type {
-  OnActionClickParams,
-  VxeTableGridOptions,
-} from '#/adapter/vxe-table';
-import type { SystemRoleApi } from '#/api';
-
-import { Page, useVbenDrawer } from '@vben/common-ui';
-import { Plus } from '@vben/icons';
-
-import { Button, message, Modal } from 'ant-design-vue';
-
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
-import { deleteRole, getRoleList, updateRole } from '#/api';
-import { $t } from '#/locales';
-
-import { useColumns, useGridFormSchema } from './data';
-import Form from './modules/form.vue';
-
-const [FormDrawer, formDrawerApi] = useVbenDrawer({
-  connectedComponent: Form,
-  destroyOnClose: true,
-});
-
-const [Grid, gridApi] = useVbenVxeGrid({
-  formOptions: {
-    fieldMappingTime: [['createTime', ['startTime', 'endTime']]],
-    schema: useGridFormSchema(),
-    submitOnChange: true,
-  },
-  gridOptions: {
-    columns: useColumns(onActionClick, onStatusChange),
-    height: 'auto',
-    keepSource: true,
-    proxyConfig: {
-      ajax: {
-        query: async ({ page }, formValues) => {
-          return await getRoleList({
-            page: page.currentPage,
-            pageSize: page.pageSize,
-            ...formValues,
-          });
-        },
-      },
-    },
-    rowConfig: {
-      keyField: 'id',
-    },
-
-    toolbarConfig: {
-      custom: true,
-      export: false,
-      refresh: { code: 'query' },
-      search: true,
-      zoom: true,
-    },
-  } as VxeTableGridOptions<SystemRoleApi.SystemRole>,
-});
-
-function onActionClick(e: OnActionClickParams<SystemRoleApi.SystemRole>) {
-  switch (e.code) {
-    case 'delete': {
-      onDelete(e.row);
-      break;
-    }
-    case 'edit': {
-      onEdit(e.row);
-      break;
-    }
-  }
-}
-
-/**
- * 将Antd的Modal.confirm封装为promise,方便在异步函数中调用。
- * @param content 提示内容
- * @param title 提示标题
- */
-function confirm(content: string, title: string) {
-  return new Promise((reslove, reject) => {
-    Modal.confirm({
-      content,
-      onCancel() {
-        reject(new Error('已取消'));
-      },
-      onOk() {
-        reslove(true);
-      },
-      title,
-    });
-  });
-}
-
-/**
- * 状态开关即将改变
- * @param newStatus 期望改变的状态值
- * @param row 行数据
- * @returns 返回false则中止改变,返回其他值(undefined、true)则允许改变
- */
-async function onStatusChange(
-  newStatus: number,
-  row: SystemRoleApi.SystemRole,
-) {
-  const status: Recordable<string> = {
-    0: '禁用',
-    1: '启用',
-  };
-  try {
-    await confirm(
-      `你要将${row.name}的状态切换为 【${status[newStatus.toString()]}】 吗?`,
-      `切换状态`,
-    );
-    await updateRole(row.id, { status: newStatus });
-    return true;
-  } catch {
-    return false;
-  }
-}
-
-function onEdit(row: SystemRoleApi.SystemRole) {
-  formDrawerApi.setData(row).open();
-}
-
-function onDelete(row: SystemRoleApi.SystemRole) {
-  const hideLoading = message.loading({
-    content: $t('ui.actionMessage.deleting', [row.name]),
-    duration: 0,
-    key: 'action_process_msg',
-  });
-  deleteRole(row.id)
-    .then(() => {
-      message.success({
-        content: $t('ui.actionMessage.deleteSuccess', [row.name]),
-        key: 'action_process_msg',
-      });
-      onRefresh();
-    })
-    .catch(() => {
-      hideLoading();
-    });
-}
-
-function onRefresh() {
-  gridApi.query();
-}
-
-function onCreate() {
-  formDrawerApi.setData({}).open();
-}
-</script>
-<template>
-  <Page auto-content-height>
-    <FormDrawer />
-    <Grid :table-title="$t('system.role.list')">
-      <template #toolbar-tools>
-        <Button type="primary" @click="onCreate">
-          <Plus class="size-5" />
-          {{ $t('ui.actionTitle.create', [$t('system.role.name')]) }}
-        </Button>
-      </template>
-    </Grid>
-  </Page>
-</template>

+ 0 - 139
playground/src/views/system/role/modules/form.vue

@@ -1,139 +0,0 @@
-<script lang="ts" setup>
-import type { DataNode } from 'ant-design-vue/es/tree';
-
-import type { Recordable } from '@vben/types';
-
-import type { SystemRoleApi } from '#/api/system/role';
-
-import { computed, ref } from 'vue';
-
-import { useVbenDrawer, VbenTree } from '@vben/common-ui';
-import { IconifyIcon } from '@vben/icons';
-
-import { Spin } from 'ant-design-vue';
-
-import { useVbenForm } from '#/adapter/form';
-import { getMenuList } from '#/api/system/menu';
-import { createRole, updateRole } from '#/api/system/role';
-import { $t } from '#/locales';
-
-import { useFormSchema } from '../data';
-
-const emits = defineEmits(['success']);
-
-const formData = ref<SystemRoleApi.SystemRole>();
-
-const [Form, formApi] = useVbenForm({
-  schema: useFormSchema(),
-  showDefaultActions: false,
-});
-
-const permissions = ref<DataNode[]>([]);
-const loadingPermissions = ref(false);
-
-const id = ref();
-const [Drawer, drawerApi] = useVbenDrawer({
-  async onConfirm() {
-    const { valid } = await formApi.validate();
-    if (!valid) return;
-    const values = await formApi.getValues();
-    drawerApi.lock();
-    (id.value ? updateRole(id.value, values) : createRole(values))
-      .then(() => {
-        emits('success');
-        drawerApi.close();
-      })
-      .catch(() => {
-        drawerApi.unlock();
-      });
-  },
-  onOpenChange(isOpen) {
-    if (isOpen) {
-      const data = drawerApi.getData<SystemRoleApi.SystemRole>();
-      formApi.resetForm();
-      if (data) {
-        formData.value = data;
-        id.value = data.id;
-        formApi.setValues(data);
-      } else {
-        id.value = undefined;
-      }
-
-      if (permissions.value.length === 0) {
-        loadPermissions();
-      }
-    }
-  },
-});
-
-async function loadPermissions() {
-  loadingPermissions.value = true;
-  try {
-    const res = await getMenuList();
-    permissions.value = res as unknown as DataNode[];
-  } finally {
-    loadingPermissions.value = false;
-  }
-}
-
-const getDrawerTitle = computed(() => {
-  return formData.value?.id
-    ? $t('common.edit', $t('system.role.name'))
-    : $t('common.create', $t('system.role.name'));
-});
-
-function getNodeClass(node: Recordable<any>) {
-  const classes: string[] = [];
-  if (node.value?.type === 'button') {
-    classes.push('inline-flex');
-    if (node.index % 3 >= 1) {
-      classes.push('!pl-0');
-    }
-  }
-
-  return classes.join(' ');
-}
-</script>
-<template>
-  <Drawer :title="getDrawerTitle">
-    <Form>
-      <template #permissions="slotProps">
-        <Spin :spinning="loadingPermissions">
-          <VbenTree
-            :tree-data="permissions"
-            multiple
-            bordered
-            :default-expanded-level="2"
-            :get-node-class="getNodeClass"
-            v-bind="slotProps"
-            value-field="id"
-            label-field="meta.title"
-            icon-field="meta.icon"
-          >
-            <template #node="{ value }">
-              <IconifyIcon v-if="value.meta.icon" :icon="value.meta.icon" />
-              {{ $t(value.meta.title) }}
-            </template>
-          </VbenTree>
-        </Spin>
-      </template>
-    </Form>
-  </Drawer>
-</template>
-<style lang="css" scoped>
-:deep(.ant-tree-title) {
-  .tree-actions {
-    display: none;
-    margin-left: 20px;
-  }
-}
-
-:deep(.ant-tree-title:hover) {
-  .tree-actions {
-    display: flex;
-    flex: auto;
-    justify-content: flex-end;
-    margin-left: 20px;
-  }
-}
-</style>