This is an automated email from the ASF dual-hosted git repository.

nicholasjiang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/paimon-webui.git


The following commit(s) were added to refs/heads/main by this push:
     new 44edfeb  [Feature] Introduce user management (#244)
44edfeb is described below

commit 44edfeb499f8314d53eb6e04478bc078e80c132c
Author: xiaomo <[email protected]>
AuthorDate: Sun May 26 11:32:57 2024 +0800

    [Feature] Introduce user management (#244)
---
 .../{vite.config.ts => __unconfig_vite.config.ts}  |  41 +++--
 paimon-web-ui/mock/modules/index.js                |   4 +-
 paimon-web-ui/mock/modules/system.js               | 152 ++++++++++++++++-
 paimon-web-ui/src/api/models/role/index.ts         |   2 +-
 .../api/models/role/{types/role.ts => types.ts}    |   0
 .../src/api/models/{role => user}/index.ts         |  54 +++---
 .../models/{role/types/role.ts => user/types.ts}   |  56 +++---
 paimon-web-ui/src/locales/en/modules/system.ts     |   7 +
 paimon-web-ui/src/locales/zh/modules/system.ts     |   7 +
 paimon-web-ui/src/store/permission/index.ts        |   2 +-
 .../system/role/components/role-form/index.tsx     |   2 +-
 paimon-web-ui/src/views/system/role/index.tsx      |   2 +-
 paimon-web-ui/src/views/system/role/use-table.ts   |   2 +-
 .../system/user/components/user-delete/index.tsx   |  55 ++++++
 .../components/user-form}/index.tsx                | 126 +++++++-------
 paimon-web-ui/src/views/system/user/index.tsx      | 189 ++++++++++++++++++++-
 paimon-web-ui/vite.config.ts                       |  33 ++--
 17 files changed, 562 insertions(+), 172 deletions(-)

diff --git a/paimon-web-ui/vite.config.ts 
b/paimon-web-ui/__unconfig_vite.config.ts
similarity index 71%
copy from paimon-web-ui/vite.config.ts
copy to paimon-web-ui/__unconfig_vite.config.ts
index 859f6c5..d6c159e 100644
--- a/paimon-web-ui/vite.config.ts
+++ b/paimon-web-ui/__unconfig_vite.config.ts
@@ -1,3 +1,7 @@
+
+let __unconfig_data;
+let __unconfig_stub = function (data = {}) { __unconfig_data = data };
+__unconfig_stub.default = (data = {}) => { __unconfig_data = data };
 /* Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
 distributed with this work for additional information
@@ -15,7 +19,7 @@ KIND, either express or implied.  See the License for the
 specific language governing permissions and limitations
 under the License. */
 
-import { fileURLToPath, URL } from 'node:url'
+import { URL, fileURLToPath } from 'node:url'
 
 import { defineConfig } from 'vite'
 import vue from '@vitejs/plugin-vue'
@@ -23,9 +27,10 @@ import vueJsx from '@vitejs/plugin-vue-jsx'
 import AutoImport from 'unplugin-auto-import/vite'
 import Components from 'unplugin-vue-components/vite'
 import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'
+import autoprefixer from 'autoprefixer'
 
 // https://vitejs.dev/config/
-export default defineConfig({
+const __unconfig_default =  defineConfig({
   server: {
     proxy: {
       '/mock': {
@@ -35,8 +40,8 @@ export default defineConfig({
       '/api': {
         target: 'http://127.0.0.1:10088',
         changeOrigin: true,
-      }
-    }
+      },
+    },
   },
   plugins: [
     vue(),
@@ -51,32 +56,34 @@ export default defineConfig({
             'useDialog',
             'useMessage',
             'useNotification',
-            'useLoadingBar'
-          ]
-        }
+            'useLoadingBar',
+          ],
+        },
       ],
       dts: './auto-imports.d.ts',
       dirs: [
         './src/composables',
       ],
       eslintrc: {
-        enabled: false
-      }
+        enabled: false,
+      },
     }),
     Components({
-      resolvers: [NaiveUiResolver()]
-    })
+      resolvers: [NaiveUiResolver()],
+    }),
   ],
   css: {
     postcss: {
       plugins: [
-        require("autoprefixer")
-      ]
-    }
+        autoprefixer(),
+      ],
+    },
   },
   resolve: {
     alias: {
-      '@': fileURLToPath(new URL('./src', import.meta.url))
-    }
-  }
+      '@': fileURLToPath(new URL('./src', import.meta.url)),
+    },
+  },
 })
+
+if (typeof __unconfig_default === "function") 
__unconfig_default(...[{"command":"serve","mode":"development"}]);export 
default __unconfig_data;
\ No newline at end of file
diff --git a/paimon-web-ui/mock/modules/index.js 
b/paimon-web-ui/mock/modules/index.js
index 5895c6b..6497be2 100644
--- a/paimon-web-ui/mock/modules/index.js
+++ b/paimon-web-ui/mock/modules/index.js
@@ -16,7 +16,9 @@ specific language governing permissions and limitations
 under the License. */
 
 const metadata = require(`./metadata`)
+const system = require(`./system`)
 
 module.exports = {
-  metadata
+  metadata,
+  system
 }
diff --git a/paimon-web-ui/mock/modules/system.js 
b/paimon-web-ui/mock/modules/system.js
index 49b6e9e..007f728 100644
--- a/paimon-web-ui/mock/modules/system.js
+++ b/paimon-web-ui/mock/modules/system.js
@@ -51,6 +51,89 @@ module.exports = (mockUtil) => ({
     ]
   }),
 
+  '/menu/treeselect': mockUtil({
+    "code": 200,
+    "msg": "Successfully",
+    "data": [
+      {
+        "id": 1,
+        "label": "system",
+        "children": [
+          {
+            "id": 300,
+            "label": "menu_manager",
+            "children": [
+              {
+                "id": 3000,
+                "label": "menu_query"
+              },
+              {
+                "id": 3001,
+                "label": "menu_add"
+              },
+              {
+                "id": 3002,
+                "label": "menu_update"
+              },
+              {
+                "id": 3003,
+                "label": "menu_delete"
+              }
+            ]
+          },
+          {
+            "id": 100,
+            "label": "user_manager",
+            "children": [
+              {
+                "id": 1000,
+                "label": "user_query"
+              },
+              {
+                "id": 1001,
+                "label": "user_add"
+              },
+              {
+                "id": 1002,
+                "label": "user_update"
+              },
+              {
+                "id": 1003,
+                "label": "user_delete"
+              },
+              {
+                "id": 1004,
+                "label": "user_reset"
+              }
+            ]
+          },
+          {
+            "id": 200,
+            "label": "role_manager",
+            "children": [
+              {
+                "id": 2000,
+                "label": "role_query"
+              },
+              {
+                "id": 2001,
+                "label": "role_add"
+              },
+              {
+                "id": 2002,
+                "label": "role_update"
+              },
+              {
+                "id": 2003,
+                "label": "role_delete"
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }),
+
   '/menu/roleMenuTreeselect/:roleId': mockUtil({
     "code": 200,
     "msg": "Successfully",
@@ -135,5 +218,72 @@ module.exports = (mockUtil) => ({
         }
       ]
     }
-  })
+  }),
+
+  '/user/list': mockUtil({
+    "total": "2",
+    "success": true,
+    "data": [
+      {
+        "id": 1,
+        "username": "admin",
+        "nickname": "admin",
+        "userType": "0",
+        "mobile": "13400263598",
+        "email": "[email protected]",
+        "enabled": true,
+        "createTime": "2024-05-11T13:49:27",
+        "updateTime": null,
+        "roles": null
+      },
+      {
+        "id": 2,
+        "username": "common",
+        "nickname": "common",
+        "userType": "0",
+        "mobile": "13400263598",
+        "email": "[email protected]",
+        "enabled": true,
+        "createTime": "2024-05-11T13:49:27",
+        "updateTime": null,
+        "roles": null
+      }
+    ]
+  }),
+
+  'post /user': mockUtil({
+    "code": 200,
+    "msg": "Successfully",
+    "data": null
+  }),
+
+  'put /user': mockUtil({
+    "code": 200,
+    "msg": "Successfully",
+    "data": null
+  }),
+
+  'delete /user/:userid': mockUtil({
+    "code": 200,
+    "msg": "Successfully",
+    "data": null
+  }),
+
+  'post /user/change/password': mockUtil({
+    "code": 200,
+    "msg": "Successfully",
+    "data": null
+  }),
+
+  'put /user/changeStatus': mockUtil({
+    "code": 200,
+    "msg": "Successfully",
+    "data": null
+  }),
+
+  'post /user/allocate': mockUtil({
+    "code": 200,
+    "msg": "Successfully",
+    "data": null
+  }),
 })
diff --git a/paimon-web-ui/src/api/models/role/index.ts 
b/paimon-web-ui/src/api/models/role/index.ts
index da0ce32..ced5297 100644
--- a/paimon-web-ui/src/api/models/role/index.ts
+++ b/paimon-web-ui/src/api/models/role/index.ts
@@ -16,7 +16,7 @@ specific language governing permissions and limitations
 under the License. */
 
 import httpRequest from '../../request'
-import type { Role, RoleDTO, RoleDetail, RoleMenu, RoleParams } from 
'./types/role'
+import type { Role, RoleDTO, RoleDetail, RoleMenu, RoleParams } from './types'
 import type { ResponseOptions } from '@/api/types'
 
 /**
diff --git a/paimon-web-ui/src/api/models/role/types/role.ts 
b/paimon-web-ui/src/api/models/role/types.ts
similarity index 100%
copy from paimon-web-ui/src/api/models/role/types/role.ts
copy to paimon-web-ui/src/api/models/role/types.ts
diff --git a/paimon-web-ui/src/api/models/role/index.ts 
b/paimon-web-ui/src/api/models/user/index.ts
similarity index 51%
copy from paimon-web-ui/src/api/models/role/index.ts
copy to paimon-web-ui/src/api/models/user/index.ts
index da0ce32..a855187 100644
--- a/paimon-web-ui/src/api/models/role/index.ts
+++ b/paimon-web-ui/src/api/models/user/index.ts
@@ -16,56 +16,42 @@ specific language governing permissions and limitations
 under the License. */
 
 import httpRequest from '../../request'
-import type { Role, RoleDTO, RoleDetail, RoleMenu, RoleParams } from 
'./types/role'
+import type { User, UserDTO, UserParams } from './types'
 import type { ResponseOptions } from '@/api/types'
 
 /**
- * # create a role
+ * # List user
  */
-export function createRole() {
-  return httpRequest.createHooks!<unknown, RoleDTO>({
-    url: '/role',
-    method: 'post',
+export function getUserList() {
+  return httpRequest.createHooks!<ResponseOptions<User[]>, UserParams>({
+    url: '/user/list',
+    method: 'get',
   })
 }
 
 /**
- * # update a role
+ * # Create user
  */
-export function updateRole() {
-  return httpRequest.createHooks!<unknown, RoleDTO>({
-    url: '/role',
-    method: 'put',
+export function createUser() {
+  return httpRequest.createHooks!<unknown, UserDTO>({
+    url: '/user',
+    method: 'post',
   })
 }
 
 /**
- * # delete a role
- */
-export function deleteRole(roleId: number) {
-  return httpRequest.delete!<unknown, RoleDTO>(`/role/${roleId}`)
-}
-
-/**
- * # permission tree
+ * # Update user
  */
-export function getPermissionTree() {
-  return httpRequest.get!<string, 
ResponseOptions<RoleMenu[]>>(`/menu/treeselect`)
-}
-
-/**
- * # permission tree by role Id
- */
-export function getPermissionByRoleId(roleId: number) {
-  return httpRequest.get!<string, 
ResponseOptions<RoleDetail>>(`/menu/roleMenuTreeselect/${roleId}`)
+export function updateUser() {
+  return httpRequest.createHooks!<unknown, UserDTO>({
+    url: '/user',
+    method: 'put',
+  })
 }
 
 /**
- * # List roles
+ * # delete a user
  */
-export function listRoles() {
-  return httpRequest.createHooks!<ResponseOptions<Role[]>, RoleParams>({
-    url: '/role/list',
-    method: 'get',
-  })
+export function deleteUser(userId: number) {
+  return httpRequest.delete!<unknown, UserDTO>(`/user/${userId}`)
 }
diff --git a/paimon-web-ui/src/api/models/role/types/role.ts 
b/paimon-web-ui/src/api/models/user/types.ts
similarity index 62%
rename from paimon-web-ui/src/api/models/role/types/role.ts
rename to paimon-web-ui/src/api/models/user/types.ts
index 828ef7b..6ef5792 100644
--- a/paimon-web-ui/src/api/models/role/types/role.ts
+++ b/paimon-web-ui/src/api/models/user/types.ts
@@ -1,3 +1,4 @@
+
 /* Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
 distributed with this work for additional information
@@ -15,44 +16,35 @@ KIND, either express or implied.  See the License for the
 specific language governing permissions and limitations
 under the License. */
 
-export interface RoleDetail {
-  checkedKeys: number[]
-  menus: RoleMenu[]
-}
-
-export interface RoleMenu {
-  id: number
-  label: string
-  children: RoleMenu[]
-}
-
-export interface Role {
+export interface User {
   id: number
-  createTime: string
-  updateTime?: string
-  roleName: string
-  roleKey: string
-  sort: number
+  username: string
+  nickname: string
+  userType: string
+  mobile?: string
+  email?: string
   enabled: boolean
-  isDelete: boolean
-  admin?: boolean
-  remark?: string
-  flag: boolean
-  menuIds: null
-  permissions: null
+  createTime: string
+  updateTime: string
+  roles?: []
 }
 
-export interface RoleParams {
-  roleName?: string
-  currentPage: number
+export interface UserParams {
+  username?: string
+  pageNum: number
   pageSize: number
 }
 
-export interface RoleDTO {
+export interface UserDTO {
   id?: number
-  roleName: string
-  roleKey: string
-  enabled: boolean
-  remark?: string
-  menuIds: number[]
+  username: string
+  password: string
+  nickname?: string
+  mobile?: string
+  email?: string
+  enabled: true
+  roleIds?: string[]
+  createTime?: string
+  updateTime?: string
+  roles?: []
 }
diff --git a/paimon-web-ui/src/locales/en/modules/system.ts 
b/paimon-web-ui/src/locales/en/modules/system.ts
index 52cf5a1..7fe5b7f 100644
--- a/paimon-web-ui/src/locales/en/modules/system.ts
+++ b/paimon-web-ui/src/locales/en/modules/system.ts
@@ -21,6 +21,13 @@ const user = {
   nickname: 'Nickname',
   mobile: 'Mobile',
   email: 'Email',
+  password: 'Password',
+
+  create: 'Create User',
+  update: 'Update User',
+
+  enabled: 'Enabled',
+  roleIds: 'Role',
 }
 
 const role = {
diff --git a/paimon-web-ui/src/locales/zh/modules/system.ts 
b/paimon-web-ui/src/locales/zh/modules/system.ts
index 7e3630a..1530974 100644
--- a/paimon-web-ui/src/locales/zh/modules/system.ts
+++ b/paimon-web-ui/src/locales/zh/modules/system.ts
@@ -21,6 +21,13 @@ const user = {
   nickname: '昵称',
   mobile: '手机号',
   email: '邮箱',
+  password: '密码',
+
+  create: '新增用户',
+  update: '更新用户',
+
+  enabled: '是否启用',
+  roleIds: '角色',
 }
 
 const role = {
diff --git a/paimon-web-ui/src/store/permission/index.ts 
b/paimon-web-ui/src/store/permission/index.ts
index ea32ae8..496a964 100644
--- a/paimon-web-ui/src/store/permission/index.ts
+++ b/paimon-web-ui/src/store/permission/index.ts
@@ -17,7 +17,7 @@ under the License. */
 
 import { getPermissionTree } from '@/api/models/role'
 
-import type { RoleMenu } from '@/api/models/role/types/role'
+import type { RoleMenu } from '@/api/models/role/types'
 
 export const usePermissionStore = defineStore('permission', () => {
   const permissionList = ref<RoleMenu[]>([])
diff --git a/paimon-web-ui/src/views/system/role/components/role-form/index.tsx 
b/paimon-web-ui/src/views/system/role/components/role-form/index.tsx
index 9f971cc..2d5075c 100644
--- a/paimon-web-ui/src/views/system/role/components/role-form/index.tsx
+++ b/paimon-web-ui/src/views/system/role/components/role-form/index.tsx
@@ -16,7 +16,7 @@ specific language governing permissions and limitations
 under the License. */
 
 import type { FormRules, TreeOption } from 'naive-ui'
-import type { RoleDTO } from '@/api/models/role/types/role'
+import type { RoleDTO } from '@/api/models/role/types'
 import { usePermissionStore } from '@/store/permission'
 
 const props = {
diff --git a/paimon-web-ui/src/views/system/role/index.tsx 
b/paimon-web-ui/src/views/system/role/index.tsx
index 0d1db4b..9b4e19f 100644
--- a/paimon-web-ui/src/views/system/role/index.tsx
+++ b/paimon-web-ui/src/views/system/role/index.tsx
@@ -23,7 +23,7 @@ import RoleForm from './components/role-form'
 import RoleDetail from './components/role-detail'
 import RoleDelete from './components/role-delete'
 import { createRole, getPermissionByRoleId, updateRole } from 
'@/api/models/role'
-import type { Role, RoleDTO } from '@/api/models/role/types/role'
+import type { Role, RoleDTO } from '@/api/models/role/types'
 
 export default defineComponent({
   name: 'RolePage',
diff --git a/paimon-web-ui/src/views/system/role/use-table.ts 
b/paimon-web-ui/src/views/system/role/use-table.ts
index 12f3a32..0a7d9e7 100644
--- a/paimon-web-ui/src/views/system/role/use-table.ts
+++ b/paimon-web-ui/src/views/system/role/use-table.ts
@@ -37,7 +37,7 @@ export function useTable() {
 
   const [roleList, useRoleList, { loading }] = listRoles()
 
-  const getTableData = () => {
+  function getTableData() {
     const params = {
       roleName: tableVariables.searchForm.roleName,
       currentPage: tableVariables.pagination.page,
diff --git 
a/paimon-web-ui/src/views/system/user/components/user-delete/index.tsx 
b/paimon-web-ui/src/views/system/user/components/user-delete/index.tsx
new file mode 100644
index 0000000..f03f963
--- /dev/null
+++ b/paimon-web-ui/src/views/system/user/components/user-delete/index.tsx
@@ -0,0 +1,55 @@
+/* Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License. */
+
+import { RemoveCircleOutline, Warning } from '@vicons/ionicons5'
+import { deleteUser } from '@/api/models/user'
+
+export default defineComponent({
+  props: {
+    userId: {
+      type: Number,
+    },
+    onDelete: Function,
+  },
+  setup(props) {
+    const onDelete = async () => {
+      (props.userId || props.userId === 0) && await deleteUser(props.userId)
+      props.onDelete && props.onDelete()
+    }
+
+    return {
+      onDelete,
+    }
+  },
+  render() {
+    return (
+      <n-popconfirm onPositiveClick={this.onDelete}>
+        {{
+          default: () => 'Confirm to delete ? ',
+          trigger: () => (
+            <n-button strong secondary circle type="error">
+              {{
+                icon: () => <n-icon component={RemoveCircleOutline} />,
+              }}
+            </n-button>
+          ),
+          icon: () => <n-icon color="#EC4C4D" component={Warning} />,
+        }}
+      </n-popconfirm>
+    )
+  },
+})
diff --git a/paimon-web-ui/src/views/system/role/components/role-form/index.tsx 
b/paimon-web-ui/src/views/system/user/components/user-form/index.tsx
similarity index 58%
copy from paimon-web-ui/src/views/system/role/components/role-form/index.tsx
copy to paimon-web-ui/src/views/system/user/components/user-form/index.tsx
index 9f971cc..5bd86ae 100644
--- a/paimon-web-ui/src/views/system/role/components/role-form/index.tsx
+++ b/paimon-web-ui/src/views/system/user/components/user-form/index.tsx
@@ -15,9 +15,10 @@ KIND, either express or implied.  See the License for the
 specific language governing permissions and limitations
 under the License. */
 
-import type { FormRules, TreeOption } from 'naive-ui'
-import type { RoleDTO } from '@/api/models/role/types/role'
-import { usePermissionStore } from '@/store/permission'
+import type { FormItemRule, TreeOption } from 'naive-ui'
+
+import type { UserDTO } from '@/api/models/user/types'
+import { listRoles } from '@/api/models/role'
 
 const props = {
   'visible': {
@@ -34,7 +35,7 @@ const props = {
   },
 
   'formValue': {
-    type: Object as PropType<RoleDTO>,
+    type: Object as PropType<UserDTO>,
     default: () => ({
       roleName: '',
       roleKey: '',
@@ -43,59 +44,59 @@ const props = {
       menuIds: [],
     }),
   },
-  'onUpdate:formValue': [Function, Object] as PropType<((value: RoleDTO) => 
void) | undefined>,
+  'onUpdate:formValue': [Function, Object] as PropType<((value: UserDTO) => 
void) | undefined>,
   'onConfirm': Function,
 }
 
 export default defineComponent({
-  name: 'RoleForm',
+  name: 'UserForm',
   props,
   setup(props) {
     const rules = {
-      roleName: {
+      username: {
         required: true,
         trigger: ['blur', 'input'],
-        message: 'role name required',
+        message: 'username required',
       },
-      roleKey: {
+      password: {
         required: true,
         trigger: ['blur', 'input'],
-        message: 'role key required',
+        message: 'password required',
+      },
+      roleIds: {
+        required: true,
+        type: 'number',
+        trigger: ['blur', 'change'],
+        message: 'roleIds required',
       },
-      menuIds: {
+      mobile: {
         required: true,
-        trigger: ['blur'],
-        validator: (_: FormRules, value: string) => {
-          return new Promise<void>((resolve, reject) => {
-            if (!value?.length)
-              reject(new Error('menu ids required'))
-            else
-              resolve()
-          })
+        trigger: ['input'],
+        validator: (rule: FormItemRule, value: string) => {
+          return /^1+[3,8]+\d{9}$/.test(value)
         },
       },
     }
 
     const { t } = useLocaleHooks()
-    const permissionStore = usePermissionStore()
-    const { permissionList } = storeToRefs(permissionStore)
+    const [roleList, useRoleList] = listRoles()
 
     const formRef = ref()
 
+    watch(
+      () => props.visible,
+      (visible) => {
+        if (visible)
+          useRoleList()
+      },
+    )
+
     const handleCloseModal = () => {
       props['onUpdate:visible'] && props['onUpdate:visible'](false)
       resetState()
     }
 
-    const renderLabel = ({ option }: { option: TreeOption }) => {
-      return t(`system.roleKey.${option.label}`)
-    }
-
-    const onUpdateMenuIds = (checkIds: Array<number>) => {
-      props.formValue.menuIds = checkIds
-    }
-
-    const handleConfirm = async () => {
+    async function handleConfirm() {
       await formRef.value.validate()
       props && props.onConfirm && props.onConfirm()
       handleCloseModal()
@@ -104,11 +105,13 @@ export default defineComponent({
 
     function resetState() {
       props['onUpdate:formValue'] && props['onUpdate:formValue']({
-        roleName: '',
-        roleKey: '',
+        username: '',
+        nickname: '',
+        password: '',
         enabled: true,
-        remark: '',
-        menuIds: [],
+        mobile: '',
+        email: '',
+        roleIds: undefined,
       })
     }
 
@@ -116,10 +119,8 @@ export default defineComponent({
       ...toRefs(props),
       formRef,
       rules,
-      permissionTree: permissionList,
+      roleList,
       handleCloseModal,
-      renderLabel,
-      onUpdateMenuIds,
       handleConfirm,
       t,
     }
@@ -138,37 +139,34 @@ export default defineComponent({
                 rules={this.rules}
                 model={this.formValue}
               >
-                <n-form-item label={this.t('system.role.role_name')} 
path="roleName">
-                  <n-input v-model:value={this.formValue.roleName} />
+                <n-form-item label={this.t('system.user.username')} 
path="username">
+                  <n-input v-model:value={this.formValue.username} />
                 </n-form-item>
-                <n-form-item label={this.t('system.role.role_key')} 
path="roleKey">
-                  <n-input v-model:value={this.formValue.roleKey} />
+                <n-form-item label={this.t('system.user.nickname')} 
path="nickname">
+                  <n-input v-model:value={this.formValue.nickname} />
                 </n-form-item>
-                <n-form-item label={this.t('system.role.enabled')} 
path="enabled">
-                  <n-switch v-model:value={this.formValue.enabled} />
+                {
+                  this.formType === 'create' && (
+                    <n-form-item label={this.t('system.user.password')} 
path="password">
+                      <n-input type="password" show-password-on="click" 
v-model:value={this.formValue.password} />
+                    </n-form-item>
+                  )
+                }
+                <n-form-item label={this.t('system.user.mobile')} 
path="mobile">
+                  <n-input v-model:value={this.formValue.mobile} />
                 </n-form-item>
-                <n-form-item label={this.t('system.role.remark')} 
path="remark">
-                  <n-input
-                    v-model:value={this.formValue.remark}
-                    type="textarea"
-                    autosize={{
-                      minRows: 3,
-                      maxRows: 5,
-                    }}
-                  />
+                <n-form-item label={this.t('system.user.email')} path="email">
+                  <n-input v-model:value={this.formValue.email} />
+                </n-form-item>
+                <n-form-item label={this.t('system.user.enabled')} 
path="enabled">
+                  <n-switch v-model:value={this.formValue.enabled} />
                 </n-form-item>
-                <n-form-item label={this.t('system.role.permission_setting')} 
path="menuIds">
-                  <n-tree
-                    key-field="id"
-                    default-expand-all
-                    block-line
-                    cascade
-                    renderLabel={this.renderLabel}
-                    onUpdate:checkedKeys={this.onUpdateMenuIds}
-                    checkedKeys={this.formValue.menuIds}
-                    data={this.permissionTree}
-                    expand-on-click
-                    checkable
+                <n-form-item label={this.t('system.user.roleIds')} 
path="roleIds">
+                  <n-select
+                    v-model:value={this.formValue.roleIds}
+                    options={this.roleList || []}
+                    value-field="id"
+                    label-field="roleName"
                   />
                 </n-form-item>
               </n-form>
diff --git a/paimon-web-ui/src/views/system/user/index.tsx 
b/paimon-web-ui/src/views/system/user/index.tsx
index 45360d3..29ce352 100644
--- a/paimon-web-ui/src/views/system/user/index.tsx
+++ b/paimon-web-ui/src/views/system/user/index.tsx
@@ -15,16 +15,201 @@ KIND, either express or implied.  See the License for the
 specific language governing permissions and limitations
 under the License. */
 
+import type { TableColumns } from 'naive-ui/es/data-table/src/interface'
+import dayjs from 'dayjs'
+import { EditOutlined } from '@vicons/antd'
+
+import UserForm from './components/user-form'
+import UserDelete from './components/user-delete'
+
 import styles from './index.module.scss'
 
+import { createUser, getUserList, updateUser } from '@/api/models/user'
+
+import type { User, UserDTO } from '@/api/models/user/types'
+
 export default defineComponent({
   name: 'UserPage',
   setup() {
-    return {}
+    const { t } = useLocaleHooks()
+    const rowKey = (rowData: any) => rowData.id
+    const message = useMessage()
+
+    const columns: TableColumns<UserDTO> = [
+      {
+        title: () => t('system.user.username'),
+        key: 'username',
+      },
+      {
+        title: () => t('system.user.nickname'),
+        key: 'nickname',
+      },
+      {
+        title: () => t('system.user.mobile'),
+        key: 'mobile',
+      },
+      {
+        title: () => t('system.user.enabled'),
+        key: 'enabled',
+        render: (row: UserDTO) => {
+          return row.enabled ? t('common.yes') : t('common.no')
+        },
+      },
+      {
+        title: () => t('common.create_time'),
+        key: 'createTime',
+        render: (row: UserDTO) => {
+          return row?.createTime ? dayjs(row?.createTime).format('YYYY-MM-DD 
HH:mm') : '-'
+        },
+      },
+      {
+        title: () => t('common.update_time'),
+        key: 'updateTime',
+        render: (row: UserDTO) => {
+          return row?.updateTime ? dayjs(row?.updateTime).format('YYYY-MM-DD 
HH:mm') : '-'
+        },
+      },
+      {
+        title: () => t('common.action'),
+        key: 'actions',
+        resizable: true,
+        render: (row: UserDTO) => {
+          return (
+            <n-space>
+              <n-button onClick={() => handleUpdateModal(row)} strong 
secondary circle>
+                {{
+                  icon: () => <n-icon component={EditOutlined} />,
+                }}
+              </n-button>
+              <UserDelete userId={row?.id} onDelete={getTableData} />
+            </n-space>
+          )
+        },
+      },
+    ]
+
+    const [userList, useAccountList, { loading }] = getUserList()
+    const [, createFetch, { loading: createLoading }] = createUser()
+    const [, updateFetch, { loading: updateLoading }] = updateUser()
+
+    const formType = ref<'create' | 'update'>('create')
+    const formVisible = ref(false)
+
+    const formValue = ref<UserDTO>({
+      username: '',
+      password: '',
+      nickname: '',
+      enabled: true,
+      mobile: '',
+      email: '',
+      roleIds: [],
+    })
+
+    onMounted(getTableData)
+
+    function handleCreateModal() {
+      formType.value = 'create'
+      formVisible.value = true
+    }
+
+    async function handleUpdateModal(user: UserDTO) {
+      formType.value = 'update'
+
+      delete user.createTime
+      delete user.updateTime
+      delete user.roles
+
+      formValue.value = { ...user }
+      formVisible.value = true
+    }
+
+    const tableVariables = reactive({
+      searchForm: {
+        username: '',
+      },
+      pagination: {
+        showQuickJumper: true,
+        showSizePicker: true,
+        pageSize: 10,
+        page: 1,
+        itemCount: 0,
+        onUpdatePage: (page: number) => {
+          tableVariables.pagination.page = page
+          getTableData()
+        },
+      },
+    })
+
+    function getTableData() {
+      const params = {
+        username: tableVariables.searchForm.username,
+        pageNum: tableVariables.pagination.page,
+        pageSize: tableVariables.pagination.pageSize,
+      }
+      useAccountList({ params })
+    }
+
+    const modelLoading = computed(() => createLoading.value || 
updateLoading.value)
+
+    async function onConfirm() {
+      const fn = formType.value === 'create' ? createFetch : updateFetch
+
+      const params = { ...toRaw(formValue.value) }
+
+      if (!Array.isArray(params.roleIds))
+        params.roleIds = [params.roleIds || '']
+
+      await fn({
+        params,
+      })
+
+      message.success(t(`Successfully`))
+
+      formVisible.value = false
+      getTableData()
+    }
+
+    return {
+      t,
+      rowKey,
+      modelLoading,
+      columns,
+      loading,
+      userList,
+      ...toRefs(tableVariables),
+
+      formType,
+      formVisible,
+      formValue,
+      handleCreateModal,
+      onConfirm,
+    }
   },
   render() {
     return (
-      <div class={styles.container}>UserPage</div>
+      <div class={styles.container}>
+        <n-card>
+          <n-space vertical>
+            <n-space justify="space-between">
+              <n-space>
+                <n-button onClick={this.handleCreateModal} 
type="primary">{this.t('system.user.add')}</n-button>
+              </n-space>
+              <n-space>
+                <></>
+              </n-space>
+            </n-space>
+            <n-data-table
+              columns={this.columns}
+              data={this.userList || []}
+              pagination={this.pagination}
+              loading={this.loading}
+              remote
+              rowKey={this.rowKey}
+            />
+          </n-space>
+        </n-card>
+        <UserForm modelLoading={this.modelLoading} formType={this.formType} 
v-model:visible={this.formVisible} v-model:formValue={this.formValue} 
onConfirm={this.onConfirm} />
+      </div>
     )
   },
 })
diff --git a/paimon-web-ui/vite.config.ts b/paimon-web-ui/vite.config.ts
index 859f6c5..3e34e2b 100644
--- a/paimon-web-ui/vite.config.ts
+++ b/paimon-web-ui/vite.config.ts
@@ -15,7 +15,7 @@ KIND, either express or implied.  See the License for the
 specific language governing permissions and limitations
 under the License. */
 
-import { fileURLToPath, URL } from 'node:url'
+import { URL, fileURLToPath } from 'node:url'
 
 import { defineConfig } from 'vite'
 import vue from '@vitejs/plugin-vue'
@@ -23,6 +23,7 @@ import vueJsx from '@vitejs/plugin-vue-jsx'
 import AutoImport from 'unplugin-auto-import/vite'
 import Components from 'unplugin-vue-components/vite'
 import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'
+import autoprefixer from 'autoprefixer'
 
 // https://vitejs.dev/config/
 export default defineConfig({
@@ -35,8 +36,8 @@ export default defineConfig({
       '/api': {
         target: 'http://127.0.0.1:10088',
         changeOrigin: true,
-      }
-    }
+      },
+    },
   },
   plugins: [
     vue(),
@@ -51,32 +52,32 @@ export default defineConfig({
             'useDialog',
             'useMessage',
             'useNotification',
-            'useLoadingBar'
-          ]
-        }
+            'useLoadingBar',
+          ],
+        },
       ],
       dts: './auto-imports.d.ts',
       dirs: [
         './src/composables',
       ],
       eslintrc: {
-        enabled: false
-      }
+        enabled: false,
+      },
     }),
     Components({
-      resolvers: [NaiveUiResolver()]
-    })
+      resolvers: [NaiveUiResolver()],
+    }),
   ],
   css: {
     postcss: {
       plugins: [
-        require("autoprefixer")
-      ]
-    }
+        autoprefixer(),
+      ],
+    },
   },
   resolve: {
     alias: {
-      '@': fileURLToPath(new URL('./src', import.meta.url))
-    }
-  }
+      '@': fileURLToPath(new URL('./src', import.meta.url)),
+    },
+  },
 })

Reply via email to