Front end users refactoring
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { boot } from 'quasar/wrappers';
|
||||
import axios, { AxiosInstance } from 'axios';
|
||||
import axios, { AxiosInstance, AxiosResponse } from 'axios';
|
||||
import {router} from 'src/router';
|
||||
import { IResponse } from 'src/types/BaseTypes';
|
||||
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<q-item active v-if="allLoading" active-class="menu-active">
|
||||
<q-item-section class="text-weight-bold"> 読み込み中... </q-item-section>
|
||||
</q-item>
|
||||
<q-item v-else v-for="item in roles" :key="item.id" clickable v-ripple :active="selected.id === item.id" @click="roleClicked(item)" active-class="menu-active">
|
||||
<q-item v-else v-for="item in roles" :key="item.id" clickable v-ripple :active="selected?.id === item.id" @click="roleClicked(item)" active-class="menu-active">
|
||||
<q-item-section class="text-weight-bold"> {{ item.label }} </q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
@@ -28,7 +28,7 @@
|
||||
:pagination="pagination" >
|
||||
|
||||
<template v-slot:top>
|
||||
<q-btn color="primary" :disable="loading" label="追加" @click="showAddRoleDialog" />
|
||||
<q-btn color="primary" :disable="loading || selected?.id == -2" label="追加" @click="showAddRoleDialog" />
|
||||
<q-space />
|
||||
<q-input borderless dense filled debounce="300" v-model="filter" placeholder="検索">
|
||||
<template v-slot:append>
|
||||
@@ -153,7 +153,12 @@ const roleClicked = async (role) => {
|
||||
selected.value = role;
|
||||
}
|
||||
|
||||
const rows = computed(() => allRows.value.filter((item) => item.roles?.includes(selected.value.id) ));
|
||||
const rows = computed(() => allRows.value.filter((item) => {
|
||||
if (selected.value.id == -2) {
|
||||
return !item.roles || item.roles.length == 0;
|
||||
}
|
||||
return item.roles?.includes(selected.value.id);
|
||||
}));
|
||||
const rowIds = computed(() => new Set(rows.value?.map((item) => item.id)));
|
||||
|
||||
const getUsers = async (filter = () => true) => {
|
||||
@@ -170,7 +175,7 @@ const getRoles = async () => {
|
||||
const result = await api.get(`api/v1/roles`);
|
||||
roles.value = result.data.data.map((item) => {
|
||||
return { id: item.id, label: item.description }
|
||||
})
|
||||
}).concat({ id: -2, label: 'ロールなし' })
|
||||
selected.value = roles.value[0];
|
||||
}
|
||||
|
||||
|
||||
@@ -35,9 +35,9 @@
|
||||
<template v-slot:body-cell-roles="props">
|
||||
<q-td :props="props">
|
||||
<div class="row">
|
||||
<q-chip v-if="props.row.isSuperuser" square color="accent" text-color="white" icon="admin_panel_settings"
|
||||
<q-chip v-if="(props.row as IUserRolesDisplay).isSuperuser" square color="accent" text-color="white" icon="admin_panel_settings"
|
||||
label="システム管理者" size="sm" />
|
||||
<span v-else>{{ props.row.roles.map(r => r.description).join('、') }}</span>
|
||||
<span v-else>{{ (props.row as IUserRolesDisplay).roles.map(r => r.name).join('、') }}</span>
|
||||
</div>
|
||||
</q-td>
|
||||
</template>
|
||||
@@ -46,7 +46,7 @@
|
||||
<q-th :props="p">
|
||||
<div class="row items-center">
|
||||
<label class="q-mr-md">{{ p.col.label }}</label>
|
||||
<q-select v-model="statusFilter" :options="options" @update:model-value="updateStatusFilter" borderless
|
||||
<q-select v-model="statusFilter" :options="statusFilterOptions" borderless
|
||||
dense options-dense style="font-size: 12px; padding-top: 1px;" />
|
||||
</div>
|
||||
</q-th>
|
||||
@@ -186,9 +186,13 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
import { api } from 'boot/axios';
|
||||
import TableActionMenu from 'components/TableActionMenu.vue';
|
||||
import { useUserStore } from 'stores/useUserStore';
|
||||
import { IUserDisplay, IUserRolesDisplay } from 'src/types/UserTypes';
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
const columns = [
|
||||
{ name: 'id', label: 'ID', field: 'id', align: 'left', sortable: true },
|
||||
@@ -200,13 +204,21 @@ const columns = [
|
||||
{ name: 'actions', label: '操作', field: 'actions' }
|
||||
];
|
||||
|
||||
const statusFilterOptions = [
|
||||
{ label: '全データ', filter: () => true },
|
||||
{ label: 'システム管理者のみ', filter: (row: IUserRolesDisplay) => row.isSuperuser },
|
||||
{ label: '使用可能', filter: (row: IUserRolesDisplay) => row.isActive },
|
||||
{ label: '使用不可', filter: (row: IUserRolesDisplay) => !row.isActive },
|
||||
]
|
||||
|
||||
const pagination = ref({ sortBy: 'id', descending: true, rowsPerPage: 20 });
|
||||
|
||||
const loading = ref(false);
|
||||
const addEditLoading = ref(false);
|
||||
const filter = ref('');
|
||||
const statusFilter = ref('全データ');
|
||||
const rows = ref([]);
|
||||
const statusFilter = ref(statusFilterOptions[0]);
|
||||
const rows = computed(() => userStore.users.filter(statusFilter.value.filter));
|
||||
|
||||
const show = ref(false);
|
||||
const confirm = ref(false);
|
||||
const resetPsw = ref(false);
|
||||
@@ -233,50 +245,16 @@ const actionList = [
|
||||
{ label: '削除', icon: 'delete_outline', class: 'text-red', action: removeRow },
|
||||
];
|
||||
|
||||
const getUsers = async (filter = () => true) => {
|
||||
const getUsers = async () => {
|
||||
loading.value = true;
|
||||
const result = await api.get(`api/v1/users`);
|
||||
rows.value = result.data.data.map((item) => {
|
||||
return { id: item.id, firstName: item.first_name, lastName: item.last_name, email: item.email, isSuperuser: item.is_superuser, isActive: item.is_active, roles: item.roles.sort((a, b) => a.id - b.id) }
|
||||
}).filter(filter);
|
||||
await userStore.loadUsers();
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
// const getRoles = async () => {
|
||||
// const result = await api.get(`api/v1/roles`);
|
||||
// roleOptions.value.push(...result.data.data.map((item) => {
|
||||
// return { value: item.id, label: item.description }
|
||||
// }))
|
||||
// }
|
||||
|
||||
const updateStatusFilter = (status) => {
|
||||
switch (status) {
|
||||
case 'システム管理者のみ':
|
||||
getUsers((row) => row.isSuperuser)
|
||||
break;
|
||||
case '使用可能':
|
||||
getUsers((row) => row.isActive)
|
||||
break;
|
||||
case '使用不可':
|
||||
getUsers((row) => !row.isActive)
|
||||
break;
|
||||
default:
|
||||
getUsers()
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
// await Promise.all([
|
||||
// getRoles(),
|
||||
// getUsers()
|
||||
// ]);
|
||||
await getUsers();
|
||||
})
|
||||
|
||||
const options = ['全データ', 'システム管理者のみ', '使用可能', '使用不可']
|
||||
|
||||
// emulate fetching data from server
|
||||
const addRow = () => {
|
||||
// editId.value
|
||||
@@ -284,26 +262,24 @@ const addRow = () => {
|
||||
show.value = true;
|
||||
}
|
||||
|
||||
function removeRow(row) {
|
||||
function removeRow(row: IUserDisplay) {
|
||||
confirm.value = true;
|
||||
editId.value = row.id;
|
||||
}
|
||||
|
||||
const deleteUser = () => {
|
||||
api.delete(`api/v1/users/${editId.value}`).then(() => {
|
||||
getUsers();
|
||||
})
|
||||
const deleteUser = async () => {
|
||||
await userStore.deleteUser(editId.value);
|
||||
getUsers();
|
||||
editId.value = 0;
|
||||
};
|
||||
|
||||
function editRow(row) {
|
||||
function editRow(row: IUserDisplay) {
|
||||
isCreate.value = false
|
||||
editId.value = row.id;
|
||||
|
||||
firstName.value = row.firstName;
|
||||
lastName.value = row.lastName;
|
||||
email.value = row.email;
|
||||
pwd.value = row.password;
|
||||
|
||||
isSuperuser.value = row.isSuperuser;
|
||||
// roles.value = row.roles.map(item => item.id);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { api } from 'boot/axios';
|
||||
import { IAppDisplay, IAppVersion, IAppVersionDisplay, IManagedApp, IVersionSubmit } from 'src/types/AppTypes';
|
||||
import { IUser } from 'src/types/UserTypes';
|
||||
import { date, Notify } from 'quasar'
|
||||
import { userToUserDisplay } from './useUserStore';
|
||||
|
||||
|
||||
export const useAppStore = defineStore('app', {
|
||||
@@ -104,16 +104,3 @@ function appToAppDisplay(app: IManagedApp) {
|
||||
function formatDate(data: string) {
|
||||
return date.formatDate(data, 'YYYY/MM/DD HH:mm');
|
||||
}
|
||||
|
||||
function userToUserDisplay(user: IUser) {
|
||||
return {
|
||||
id: user.id,
|
||||
firstName: user.first_name,
|
||||
lastName: user.last_name,
|
||||
fullNameSearch: (user.last_name + user.first_name).toLowerCase(),
|
||||
fullName: user.last_name + ' ' + user.first_name,
|
||||
email: user.email,
|
||||
isActive: user.is_active,
|
||||
isSuperuser: user.is_superuser,
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { router } from 'src/router';
|
||||
import { IDomainInfo } from '../types/DomainTypes';
|
||||
import { jwtDecode } from 'jwt-decode';
|
||||
import { useAppStore } from './useAppStore';
|
||||
import { useUserStore } from './useUserStore';
|
||||
|
||||
interface UserInfo {
|
||||
firstName: string;
|
||||
@@ -92,6 +93,7 @@ export const useAuthStore = defineStore('auth', {
|
||||
this.token = '';
|
||||
this.currentDomain = {} as IDomainInfo; // 清空当前域
|
||||
useAppStore().reset();
|
||||
useUserStore().reset();
|
||||
router.push('/login');
|
||||
},
|
||||
async setCurrentDomain(domain?: IDomainInfo) {
|
||||
|
||||
112
frontend/src/stores/useUserStore.ts
Normal file
112
frontend/src/stores/useUserStore.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { api } from 'boot/axios';
|
||||
import { IRoles, IRolesDisplay, IUser, IUserDisplay, IUserRolesDisplay } from 'src/types/UserTypes';
|
||||
import { Notify } from 'quasar'
|
||||
import { IResponse } from 'src/types/BaseTypes';
|
||||
|
||||
|
||||
export const useUserStore = defineStore('user', {
|
||||
state: () => ({
|
||||
users: [] as IUserRolesDisplay[],
|
||||
userIds: new Set<number>(),
|
||||
roles: [] as IRolesDisplay[],
|
||||
}),
|
||||
actions: {
|
||||
async loadUsers() {
|
||||
this.reset('users');
|
||||
try {
|
||||
const { data } = await api.get<IResponse<IUser[]>>('api/v1/users');
|
||||
this.users = data.data.map((item) => {
|
||||
this.userIds.add(item.id);
|
||||
return userToUserRolesDisplay(item)
|
||||
}).sort((a, b) => a.id - b.id); // set default order
|
||||
} catch (error) {
|
||||
Notify.create({
|
||||
icon: 'error',
|
||||
color: 'negative',
|
||||
message: 'ユーザー一覧の読み込みに失敗しました'
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
async loadRoles() {
|
||||
this.reset('roles');
|
||||
try {
|
||||
const { data } = await api.get<IResponse<IRoles[]>>('api/v1/roles');
|
||||
this.roles = data.data.map((item) => {
|
||||
return roleToRoleDisplay(item)
|
||||
}).sort((a, b) => a.id - b.id); // set default order
|
||||
} catch (error) {
|
||||
Notify.create({
|
||||
icon: 'error',
|
||||
color: 'negative',
|
||||
message: 'ロール一覧の読み込みに失敗しました'
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
getUserById(id: number) {
|
||||
if (!this.userIds.has(id)) {
|
||||
return null;
|
||||
}
|
||||
return this.users.find((item: IUserDisplay) => item.id === id);
|
||||
},
|
||||
|
||||
async deleteUser(userId: number) {
|
||||
try {
|
||||
await api.delete(`api/v1/users/${userId}`)
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
Notify.create({
|
||||
icon: 'error',
|
||||
color: 'negative',
|
||||
message: 'ユーザーの削除に失敗しました'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
reset(target?: 'users'|'roles') {
|
||||
if (!target) {
|
||||
this.reset('users');
|
||||
this.reset('roles');
|
||||
return;
|
||||
}
|
||||
if (target == 'roles') {
|
||||
this.roles = [];
|
||||
} else if (target == 'users') {
|
||||
this.users = [];
|
||||
this.userIds.clear();
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export function userToUserDisplay(user: IUser): IUserDisplay {
|
||||
return {
|
||||
id: user.id,
|
||||
firstName: user.first_name,
|
||||
lastName: user.last_name,
|
||||
fullNameSearch: (user.last_name + user.first_name).toLowerCase(),
|
||||
fullName: user.last_name + ' ' + user.first_name,
|
||||
email: user.email,
|
||||
isActive: user.is_active,
|
||||
isSuperuser: user.is_superuser,
|
||||
};
|
||||
}
|
||||
|
||||
export function userToUserRolesDisplay(user: IUser): IUserRolesDisplay {
|
||||
const userRolesDisplay = userToUserDisplay(user) as IUserRolesDisplay;
|
||||
userRolesDisplay.roles = user.roles.map((role) => roleToRoleDisplay(role)).sort((a, b) => a.level - b.level);
|
||||
return userRolesDisplay;
|
||||
}
|
||||
|
||||
export function roleToRoleDisplay(roles: IRoles): IRolesDisplay {
|
||||
return {
|
||||
id: roles.id,
|
||||
name: roles.description,
|
||||
key: roles.name,
|
||||
level: roles.level
|
||||
};
|
||||
}
|
||||
12
frontend/src/types/BaseTypes.ts
Normal file
12
frontend/src/types/BaseTypes.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export interface IResponse<T = any> {
|
||||
code: number;
|
||||
data: T;
|
||||
msg: string;
|
||||
}
|
||||
|
||||
export interface IResponsePage<T = any> extends IResponse<T> {
|
||||
page: number;
|
||||
size: number;
|
||||
total: number;
|
||||
total_pages: number;
|
||||
}
|
||||
@@ -3,9 +3,9 @@ export interface IUser {
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
email: string;
|
||||
is_active: boolean,
|
||||
is_superuser: boolean,
|
||||
roles: object[]
|
||||
is_active: boolean;
|
||||
is_superuser: boolean;
|
||||
roles: IRoles[];
|
||||
}
|
||||
|
||||
export interface IUserDisplay {
|
||||
@@ -15,10 +15,24 @@ export interface IUserDisplay {
|
||||
fullName: string;
|
||||
fullNameSearch: string;
|
||||
email: string;
|
||||
isActive: boolean,
|
||||
isSuperuser: boolean,
|
||||
isActive: boolean;
|
||||
isSuperuser: boolean;
|
||||
}
|
||||
|
||||
export interface IUserRolesDisplay extends IUserDisplay {
|
||||
roles: object[]
|
||||
}
|
||||
roles: IRolesDisplay[];
|
||||
}
|
||||
|
||||
export interface IRoles {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
level: number;
|
||||
}
|
||||
|
||||
export interface IRolesDisplay {
|
||||
id: number;
|
||||
name: string;
|
||||
key: string;
|
||||
level: number;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user