Merge branch 'dev3' of https://dev.azure.com/alicorn-dev/KintoneAppBuilder/_git/KintoneAppBuilder into dev3
This commit is contained in:
@@ -1,12 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<q-input
|
<q-input
|
||||||
|
ref="nameRef"
|
||||||
v-model="versionInfo.name"
|
v-model="versionInfo.name"
|
||||||
filled
|
filled
|
||||||
autofocus
|
autofocus
|
||||||
label="バージョン名"
|
label="バージョン名"
|
||||||
:rules="[(val) => !val || val.length <= 80 || '80字以内で入力ください']"
|
:rules="[
|
||||||
|
val => !!val || 'バージョン名を入力してください。',
|
||||||
|
(val) => !val || val.length <= 80 || '80字以内で入力ください'
|
||||||
|
]"
|
||||||
/>
|
/>
|
||||||
<q-input
|
<q-input
|
||||||
|
ref="commentRef"
|
||||||
v-model="versionInfo.comment"
|
v-model="versionInfo.comment"
|
||||||
filled
|
filled
|
||||||
type="textarea"
|
type="textarea"
|
||||||
@@ -19,6 +24,15 @@ import { ref, watch, defineProps, defineEmits } from 'vue';
|
|||||||
import { QInput } from 'quasar';
|
import { QInput } from 'quasar';
|
||||||
import { IVersionSubmit } from 'src/types/AppTypes';
|
import { IVersionSubmit } from 'src/types/AppTypes';
|
||||||
|
|
||||||
|
const nameRef = ref();
|
||||||
|
const commentRef = ref();
|
||||||
|
|
||||||
|
const isValid = () => {
|
||||||
|
const nameHasError = nameRef.value?.hasError ?? false;
|
||||||
|
const commentHasError = commentRef.value?.hasError ?? false;
|
||||||
|
return !nameHasError && !commentHasError;
|
||||||
|
};
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: IVersionSubmit;
|
modelValue: IVersionSubmit;
|
||||||
}>();
|
}>();
|
||||||
@@ -33,6 +47,10 @@ const versionInfo = ref({
|
|||||||
|
|
||||||
const emit = defineEmits(['update:modelValue']);
|
const emit = defineEmits(['update:modelValue']);
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
isValid
|
||||||
|
})
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
versionInfo,
|
versionInfo,
|
||||||
() => {
|
() => {
|
||||||
|
|||||||
@@ -31,7 +31,8 @@
|
|||||||
<template v-slot:body-cell-version="p">
|
<template v-slot:body-cell-version="p">
|
||||||
<q-td :props="p">
|
<q-td :props="p">
|
||||||
<div class="flex justify-between full-width" >
|
<div class="flex justify-between full-width" >
|
||||||
<span class="ellipsis" :title="p.row.versionName">{{ p.row.versionName || '未命名' }}</span>
|
<span v-if="p.row.version == 0"></span>
|
||||||
|
<span v-else class="ellipsis" :title="p.row.versionName">{{ p.row.versionName }}</span>
|
||||||
<q-badge v-if="isVersionEditing(p.row)" color="orange-7">変更あり</q-badge>
|
<q-badge v-if="isVersionEditing(p.row)" color="orange-7">変更あり</q-badge>
|
||||||
</div>
|
</div>
|
||||||
</q-td>
|
</q-td>
|
||||||
@@ -128,9 +129,8 @@ const getApps = async () => {
|
|||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isVersionEditing(app:IAppDisplay) {
|
function isVersionEditing(app: IAppDisplay) {
|
||||||
// TODO
|
return !!app.versionChanged;
|
||||||
return false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
<div class="">
|
<div class="">
|
||||||
<span>{{ p.row.id }}</span>
|
<span>{{ p.row.id }}</span>
|
||||||
<span class="q-ml-md" v-if="p.row.id === app.version">
|
<span class="q-ml-md" v-if="p.row.id === app.version">
|
||||||
<q-badge color="primary">適用中</q-badge>
|
<q-badge color="primary">適用中</q-badge>
|
||||||
<q-badge class="q-ml-xs" v-if="isVersionEditing()" color="orange-7">変更あり</q-badge>
|
<q-badge class="q-ml-xs" v-if="isVersionEditing()" color="orange-7">変更あり</q-badge>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -58,19 +58,17 @@
|
|||||||
</template>
|
</template>
|
||||||
</q-table>
|
</q-table>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<q-dialog v-model="confirmDialog" persistent>
|
<q-dialog v-model="confirmDialog" persistent>
|
||||||
<q-card>
|
<q-card>
|
||||||
<q-card-section class="q-pb-none">
|
<q-card-section class="q-pb-none">
|
||||||
<q-list>
|
<q-list>
|
||||||
<q-item>
|
<q-item class="q-px-none">
|
||||||
<q-item-section avatar>
|
<q-item-section avatar class="items-center">
|
||||||
<q-icon name="warning" color="warning" size="2em" />
|
<q-icon name="warning" color="warning" size="2em" />
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<div >現在のバージョンは未保存です。</div>
|
<div>現在のバージョンは未保存です。</div>
|
||||||
<div >プルすると、上書されますので、よろしいでしょうか?</div>
|
<div>プルすると、上書されますので、よろしいでしょうか?</div>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
</q-list>
|
</q-list>
|
||||||
@@ -90,7 +88,6 @@ import { useQuasar } from 'quasar';
|
|||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted } from 'vue';
|
||||||
import { useAppStore } from 'stores/useAppStore';
|
import { useAppStore } from 'stores/useAppStore';
|
||||||
import { useAuthStore } from 'stores/useAuthStore';
|
import { useAuthStore } from 'stores/useAuthStore';
|
||||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
|
||||||
import { router } from 'src/router';
|
import { router } from 'src/router';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { IAppDisplay, IAppVersionDisplay } from 'src/types/AppTypes';
|
import { IAppDisplay, IAppVersionDisplay } from 'src/types/AppTypes';
|
||||||
@@ -121,7 +118,6 @@ const filter = ref('');
|
|||||||
const versionLoading = ref(false);
|
const versionLoading = ref(false);
|
||||||
|
|
||||||
const target = ref<IAppVersionDisplay>();
|
const target = ref<IAppVersionDisplay>();
|
||||||
const store = useFlowEditorStore();
|
|
||||||
const confirmDialog = ref(false);
|
const confirmDialog = ref(false);
|
||||||
const deleteUserLoading = ref(false);
|
const deleteUserLoading = ref(false);
|
||||||
|
|
||||||
@@ -152,8 +148,7 @@ const getVersions = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isVersionEditing() {
|
function isVersionEditing() {
|
||||||
// TODO
|
return !!app.value.versionChanged;
|
||||||
return false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
@@ -195,18 +190,6 @@ async function doChangeVersion(version?: IAppVersionDisplay) {
|
|||||||
versionLoading.value = false;
|
versionLoading.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toEditFlowPage() {
|
|
||||||
store.setApp({
|
|
||||||
appId: app.value.id,
|
|
||||||
name: app.value.name
|
|
||||||
});
|
|
||||||
store.selectFlow(undefined);
|
|
||||||
try {
|
|
||||||
await router.push('/FlowChart/' + app.value.id);
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@@ -85,8 +85,8 @@
|
|||||||
<action-select ref="appDg" name="model" :filter="filter" type="single" @clearFilter="onClearFilter" ></action-select>
|
<action-select ref="appDg" name="model" :filter="filter" type="single" @clearFilter="onClearFilter" ></action-select>
|
||||||
</ShowDialog>
|
</ShowDialog>
|
||||||
<!-- save version dialog -->
|
<!-- save version dialog -->
|
||||||
<ShowDialog v-model:visible="saveVersionAction" name="保存して新バージョン" @close="closeSaveVersionDg" min-width="500px">
|
<ShowDialog v-model:visible="saveVersionAction" name="保存して新バージョン" @close="closeSaveVersionDg" :ok-btn-auto-close="false" min-width="500px">
|
||||||
<version-input v-model="versionSubmit" />
|
<version-input ref="versionInputRef" v-model="versionSubmit" />
|
||||||
</ShowDialog>
|
</ShowDialog>
|
||||||
<q-inner-loading
|
<q-inner-loading
|
||||||
:showing="initLoading"
|
:showing="initLoading"
|
||||||
@@ -135,6 +135,7 @@ const prevNodeIfo = ref({
|
|||||||
// const refFlow = ref<ActionFlow|null>(null);
|
// const refFlow = ref<ActionFlow|null>(null);
|
||||||
const showAddAction = ref(false);
|
const showAddAction = ref(false);
|
||||||
const saveVersionAction = ref(false);
|
const saveVersionAction = ref(false);
|
||||||
|
const versionInputRef = ref();
|
||||||
const drawerRight = ref(false);
|
const drawerRight = ref(false);
|
||||||
const filter=ref("");
|
const filter=ref("");
|
||||||
const model = ref("");
|
const model = ref("");
|
||||||
@@ -270,9 +271,17 @@ const onSaveVersion = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const closeSaveVersionDg = async (val: 'OK'|'CANCEL') => {
|
const closeSaveVersionDg = async (val: 'OK'|'CANCEL') => {
|
||||||
|
saveVersionAction.value = true;
|
||||||
if (val == 'OK') {
|
if (val == 'OK') {
|
||||||
await onSaveAllFlow();
|
if (versionInputRef?.value?.isValid()) {
|
||||||
await appStore.createVersion(versionSubmit.value);
|
saveVersionAction.value = false;
|
||||||
|
await onSaveAllFlow();
|
||||||
|
await appStore.createVersion(versionSubmit.value);
|
||||||
|
} else {
|
||||||
|
saveVersionAction.value = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
saveVersionAction.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row" style="min-height: 80vh;">
|
<div class="row" style="min-height: 80vh;">
|
||||||
|
<!-- left role panel -->
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<div class="q-pa-md" style="width: 250px">
|
<div class="q-pa-md" style="width: 250px">
|
||||||
<q-list bordered separator class="rounded-borders">
|
<q-list bordered separator class="rounded-borders">
|
||||||
@@ -15,20 +15,19 @@
|
|||||||
<q-item-section class="text-weight-bold"> 読み込み中... </q-item-section>
|
<q-item-section class="text-weight-bold"> 読み込み中... </q-item-section>
|
||||||
</q-item>
|
</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-section class="text-weight-bold"> {{ item.name }} </q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
</q-list>
|
</q-list>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- right table panel -->
|
||||||
|
|
||||||
<div class="col">
|
<div class="col">
|
||||||
|
|
||||||
<q-table title="ユーザーリスト" :rows="rows" :columns="columns" row-key="id" :filter="filter" :loading="loading"
|
<q-table title="ユーザーリスト" :rows="rows" :columns="columns" row-key="id" :filter="filter" :loading="allLoading || loading"
|
||||||
:pagination="pagination" >
|
:pagination="pagination" >
|
||||||
|
|
||||||
<template v-slot:top>
|
<template v-slot:top>
|
||||||
<q-btn color="primary" :disable="loading || selected?.id == -2" label="追加" @click="showAddRoleDialog" />
|
<q-btn color="primary" :disable="allLoading || loading || selected?.id == -2" label="追加" @click="showAddRoleDialog" />
|
||||||
<q-space />
|
<q-space />
|
||||||
<q-input borderless dense filled debounce="300" v-model="filter" placeholder="検索">
|
<q-input borderless dense filled debounce="300" v-model="filter" placeholder="検索">
|
||||||
<template v-slot:append>
|
<template v-slot:append>
|
||||||
@@ -57,7 +56,7 @@
|
|||||||
<q-th :props="p">
|
<q-th :props="p">
|
||||||
<div class="row items-center">
|
<div class="row items-center">
|
||||||
<label class="q-mr-md">{{ p.col.label }}</label>
|
<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;" />
|
dense options-dense style="font-size: 12px; padding-top: 1px;" />
|
||||||
</div>
|
</div>
|
||||||
</q-th>
|
</q-th>
|
||||||
@@ -92,7 +91,7 @@
|
|||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-section class="q-py-none">
|
<q-card-section class="q-py-none">
|
||||||
<!-- <span class="q-ml-sm">この役割を与えられたユーザーは、メンバー役に再配置されます。</span> -->
|
<!-- <span class="q-ml-sm">この役割を与えられたユーザーは、メンバー役に再配置されます。</span> -->
|
||||||
<div class="q-mx-sm">ユーザー「{{targetRow.email}}」を「{{selected.label}}」の役割から</div>
|
<div class="q-mx-sm">ユーザー「{{targetRow?.email}}」を「{{selected?.name}}」の役割から</div>
|
||||||
<div class="q-mx-sm">本当に外しますか?</div>
|
<div class="q-mx-sm">本当に外しますか?</div>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
|
||||||
@@ -106,12 +105,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, computed } from 'vue';
|
import { ref, onMounted, computed, watch } from 'vue';
|
||||||
import { IUser } from 'src/types/UserTypes';
|
import { IRolesDisplay, IUserRolesDisplay } from 'src/types/UserTypes';
|
||||||
|
import { useUserStore } from 'stores/useUserStore';
|
||||||
import ShowDialog from 'src/components/ShowDialog.vue';
|
import ShowDialog from 'src/components/ShowDialog.vue';
|
||||||
import UserSelectBox from 'src/components/dialog/UserSelectBox.vue';
|
import UserSelectBox from 'src/components/dialog/UserSelectBox.vue';
|
||||||
import TableActionMenu from 'components/TableActionMenu.vue';
|
import TableActionMenu from 'components/TableActionMenu.vue';
|
||||||
import { api } from 'boot/axios';
|
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{ name: 'id', label: 'ID', field: 'id', align: 'left', sortable: true },
|
{ name: 'id', label: 'ID', field: 'id', align: 'left', sortable: true },
|
||||||
@@ -119,21 +120,30 @@ const columns = [
|
|||||||
{ name: 'firstName', label: '苗字', field: 'firstName', align: 'left', sortable: true },
|
{ name: 'firstName', label: '苗字', field: 'firstName', align: 'left', sortable: true },
|
||||||
{ name: 'email', label: '電子メール', field: 'email', align: 'left', sortable: true },
|
{ name: 'email', label: '電子メール', field: 'email', align: 'left', sortable: true },
|
||||||
{ name: 'status', label: '状況', field: 'status', align: 'left' },
|
{ name: 'status', label: '状況', field: 'status', align: 'left' },
|
||||||
{ name: 'actions', label: '操作', field: 'actions' }
|
{ name: 'actions', label: '操作', field: 'actions', align: 'right' }
|
||||||
];
|
];
|
||||||
|
|
||||||
const roles = ref([])
|
const statusFilterOptions = [
|
||||||
const selected = ref();
|
{ 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 pagination = ref({ sortBy: 'id', descending: true, rowsPerPage: 20 });
|
||||||
|
|
||||||
|
const roles = computed(() => userStore.roles.concat(EMPTY_ROLE));
|
||||||
|
|
||||||
|
const selected = ref<IRolesDisplay>();
|
||||||
|
|
||||||
const allLoading = ref(true);
|
const allLoading = ref(true);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const filter = ref('');
|
const filter = ref('');
|
||||||
const statusFilter = ref('全データ');
|
const statusFilter = ref(statusFilterOptions[0]);
|
||||||
const dgFilter = ref('');
|
const dgFilter = ref('');
|
||||||
const allRows = ref([]);
|
|
||||||
const targetRow = ref<IUser>();
|
const allUsers = computed(() => userStore.users.filter(statusFilter.value.filter));
|
||||||
|
const targetRow = ref<IUserRolesDisplay>();
|
||||||
|
|
||||||
const userDialog = ref();
|
const userDialog = ref();
|
||||||
const showSelectUser=ref(false);
|
const showSelectUser=ref(false);
|
||||||
@@ -141,73 +151,57 @@ const isAdding = ref(false);
|
|||||||
const deleteDialog = ref(false);
|
const deleteDialog = ref(false);
|
||||||
const deleteUserRoleLoading = ref(false);
|
const deleteUserRoleLoading = ref(false);
|
||||||
|
|
||||||
|
const EMPTY_ROLE: IRolesDisplay = {
|
||||||
|
id: -2,
|
||||||
|
name: 'ロールなし',
|
||||||
|
key: 'dummy',
|
||||||
|
level: -1
|
||||||
|
}
|
||||||
|
|
||||||
const actionList = [
|
const actionList = [
|
||||||
// { label: '移動', icon: 'account_tree', action: toEditFlowPage },
|
// { label: '移動', icon: 'account_tree', action: toEditFlowPage },
|
||||||
{ separator: true },
|
// { separator: true },
|
||||||
{ label: '削除', icon: 'delete_outline', class: 'text-red', action: removeRow },
|
{ label: '削除', icon: 'delete_outline', class: 'text-red', action: removeRow },
|
||||||
];
|
];
|
||||||
|
|
||||||
const options = ['全データ', 'システム管理者のみ', '使用可能', '使用不可']
|
const rows = computed(() => allUsers.value.filter((item) => {
|
||||||
|
if (!selected.value) {
|
||||||
const roleClicked = async (role) => {
|
return false;
|
||||||
selected.value = role;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const rows = computed(() => allRows.value.filter((item) => {
|
|
||||||
if (selected.value.id == -2) {
|
if (selected.value.id == -2) {
|
||||||
return !item.roles || item.roles.length == 0;
|
return !item.roles || item.roles.length == 0;
|
||||||
}
|
}
|
||||||
return item.roles?.includes(selected.value.id);
|
return item.roleIds?.includes(selected.value.id);
|
||||||
}));
|
}));
|
||||||
const rowIds = computed(() => new Set(rows.value?.map((item) => item.id)));
|
const rowIds = computed(() => new Set(rows.value?.map((item) => item.id)));
|
||||||
|
|
||||||
const getUsers = async (filter = () => true) => {
|
const getUsers = async () => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
allRows.value = []
|
await userStore.loadUsers();
|
||||||
const result = await api.get(`api/v1/users`);
|
|
||||||
allRows.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.map(role => role.id) }
|
|
||||||
}).filter(filter);
|
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
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];
|
|
||||||
}
|
|
||||||
|
|
||||||
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 () => {
|
onMounted(async () => {
|
||||||
allLoading.value = true;
|
allLoading.value = true;
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
getRoles(),
|
userStore.loadRoles(),
|
||||||
getUsers()
|
getUsers()
|
||||||
]);
|
]);
|
||||||
allLoading.value = false;
|
allLoading.value = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
const filterInitRows = (row: {id: string}) => {
|
watch(roles, (newRoles) => {
|
||||||
return !rowIds.value.has(row.id);
|
if (newRoles.length > 0) {
|
||||||
|
selected.value = newRoles[0];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const roleClicked = async (role: IRolesDisplay) => {
|
||||||
|
selected.value = role;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filterInitRows = (user: IUserRolesDisplay) => {
|
||||||
|
return !rowIds.value.has(user.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
const showAddRoleDialog = () => {
|
const showAddRoleDialog = () => {
|
||||||
@@ -218,32 +212,24 @@ const showAddRoleDialog = () => {
|
|||||||
|
|
||||||
const closeSelectUserDialog = async (val: 'OK'|'Cancel') => {
|
const closeSelectUserDialog = async (val: 'OK'|'Cancel') => {
|
||||||
showSelectUser.value = true;
|
showSelectUser.value = true;
|
||||||
if (val == 'OK' && userDialog.value.selected[0]) {
|
if (val == 'OK' && userDialog.value.selected[0] && selected.value && selected.value.id >= 0) {
|
||||||
isAdding.value = true;
|
isAdding.value = true;
|
||||||
const user = userDialog.value.selected[0];
|
await userStore.addRole(userDialog.value.selected[0], selected.value);
|
||||||
const result = await api.post(`api/v1/userrole`, {
|
|
||||||
'userid': user.id,
|
|
||||||
'roleids': user.roles.concat(selected.value.id)
|
|
||||||
});
|
|
||||||
await getUsers();
|
await getUsers();
|
||||||
}
|
}
|
||||||
showSelectUser.value = false;
|
showSelectUser.value = false;
|
||||||
isAdding.value = false;
|
isAdding.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeRow(user:IUser) {
|
function removeRow(user: IUserRolesDisplay) {
|
||||||
targetRow.value = user;
|
targetRow.value = user;
|
||||||
deleteDialog.value = true;
|
deleteDialog.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteUserRole = async () => {
|
const deleteUserRole = async () => {
|
||||||
if (targetRow.value?.id) {
|
if (targetRow.value?.id && selected.value && selected.value.id >= 0) {
|
||||||
deleteUserRoleLoading.value = true;
|
deleteUserRoleLoading.value = true;
|
||||||
// TODO 返回不需要重新读
|
await userStore.removeRole(targetRow.value, selected.value);
|
||||||
const result = await api.post(`api/v1/userrole`, {
|
|
||||||
'userid': targetRow.value.id,
|
|
||||||
'roleids': targetRow.value.roles.filter(e => e !== selected.value.id)
|
|
||||||
});
|
|
||||||
await getUsers();
|
await getUsers();
|
||||||
deleteUserRoleLoading.value = false;
|
deleteUserRoleLoading.value = false;
|
||||||
deleteDialog.value = false;
|
deleteDialog.value = false;
|
||||||
|
|||||||
@@ -37,7 +37,9 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<q-chip v-if="(props.row as IUserRolesDisplay).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" />
|
label="システム管理者" size="sm" />
|
||||||
<span v-else>{{ (props.row as IUserRolesDisplay).roles.map(r => r.name).join('、') }}</span>
|
<template v-else>
|
||||||
|
<q-chip v-for="(item) in (props.row as IUserRolesDisplay).roles" square color="primary" text-color="white" :key="item.id" :label="item.name" size="sm" />
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</q-td>
|
</q-td>
|
||||||
</template>
|
</template>
|
||||||
@@ -95,35 +97,6 @@
|
|||||||
<q-toggle v-model="isSuperuser" />
|
<q-toggle v-model="isSuperuser" />
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<!--
|
|
||||||
<q-select
|
|
||||||
class="q-mt-xs"
|
|
||||||
filled
|
|
||||||
v-model="roles"
|
|
||||||
multiple
|
|
||||||
:options="roleOptions"
|
|
||||||
label="ロール" emit-value
|
|
||||||
map-options
|
|
||||||
:option-disable="opt => opt.value == -1 ? false : isSuperuser"
|
|
||||||
@add="(opt) => { if (opt.value.value == -1) { } }"
|
|
||||||
hint="各役割には固有の権限があります。">
|
|
||||||
<template v-slot:option="{ itemProps, opt, selected, toggleOption }">
|
|
||||||
<q-item v-bind="itemProps" v-if="opt.value == -1" >
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label>{{opt.label}}</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section side>
|
|
||||||
<q-toggle :model-value="selected" @update:model-value="() => { toggleOption(opt); }" />
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
<q-separator v-if="opt.value == -1"/>
|
|
||||||
<q-item v-else v-bind="itemProps">
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label>{{opt.label}}</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</template>
|
|
||||||
</q-select> -->
|
|
||||||
|
|
||||||
<q-item tag="label" class="q-pl-sm q-pr-none q-py-xs">
|
<q-item tag="label" class="q-pl-sm q-pr-none q-py-xs">
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
@@ -187,7 +160,6 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, computed } from 'vue';
|
import { ref, onMounted, computed } from 'vue';
|
||||||
import { api } from 'boot/axios';
|
|
||||||
import TableActionMenu from 'components/TableActionMenu.vue';
|
import TableActionMenu from 'components/TableActionMenu.vue';
|
||||||
import { useUserStore } from 'stores/useUserStore';
|
import { useUserStore } from 'stores/useUserStore';
|
||||||
import { IUserDisplay, IUserRolesDisplay } from 'src/types/UserTypes';
|
import { IUserDisplay, IUserRolesDisplay } from 'src/types/UserTypes';
|
||||||
@@ -231,18 +203,13 @@ const isActive = ref(true);
|
|||||||
|
|
||||||
const isPwd = ref(true);
|
const isPwd = ref(true);
|
||||||
const pwd = ref('');
|
const pwd = ref('');
|
||||||
// const roles = ref([]);
|
const editId = ref(0);
|
||||||
const isCreate = ref(true);
|
const isCreate = computed(() => editId.value <= 0);
|
||||||
let editId = ref(0);
|
|
||||||
|
|
||||||
// const roleOptions = ref([
|
|
||||||
// {'value': -1, 'label': 'システム管理者'}
|
|
||||||
// ]);
|
|
||||||
|
|
||||||
const actionList = [
|
const actionList = [
|
||||||
{ label: '編集', icon: 'edit_note', action: editRow },
|
{ label: '編集', icon: 'edit_note', action: editRow },
|
||||||
{ separator: true },
|
{ separator: true },
|
||||||
{ label: '削除', icon: 'delete_outline', class: 'text-red', action: removeRow },
|
{ label: '削除', icon: 'delete_outline', class: 'text-red', action: showDeleteUserConfirm },
|
||||||
];
|
];
|
||||||
|
|
||||||
const getUsers = async () => {
|
const getUsers = async () => {
|
||||||
@@ -255,14 +222,12 @@ onMounted(async () => {
|
|||||||
await getUsers();
|
await getUsers();
|
||||||
})
|
})
|
||||||
|
|
||||||
// emulate fetching data from server
|
|
||||||
const addRow = () => {
|
const addRow = () => {
|
||||||
// editId.value
|
|
||||||
onReset();
|
onReset();
|
||||||
show.value = true;
|
show.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeRow(row: IUserDisplay) {
|
function showDeleteUserConfirm(row: IUserDisplay) {
|
||||||
confirm.value = true;
|
confirm.value = true;
|
||||||
editId.value = row.id;
|
editId.value = row.id;
|
||||||
}
|
}
|
||||||
@@ -270,11 +235,10 @@ function removeRow(row: IUserDisplay) {
|
|||||||
const deleteUser = async () => {
|
const deleteUser = async () => {
|
||||||
await userStore.deleteUser(editId.value);
|
await userStore.deleteUser(editId.value);
|
||||||
getUsers();
|
getUsers();
|
||||||
editId.value = 0;
|
onReset();
|
||||||
};
|
};
|
||||||
|
|
||||||
function editRow(row: IUserDisplay) {
|
function editRow(row: IUserDisplay) {
|
||||||
isCreate.value = false
|
|
||||||
editId.value = row.id;
|
editId.value = row.id;
|
||||||
|
|
||||||
firstName.value = row.firstName;
|
firstName.value = row.firstName;
|
||||||
@@ -282,7 +246,6 @@ function editRow(row: IUserDisplay) {
|
|||||||
email.value = row.email;
|
email.value = row.email;
|
||||||
|
|
||||||
isSuperuser.value = row.isSuperuser;
|
isSuperuser.value = row.isSuperuser;
|
||||||
// roles.value = row.roles.map(item => item.id);
|
|
||||||
isActive.value = row.isActive;
|
isActive.value = row.isActive;
|
||||||
|
|
||||||
isPwd.value = true;
|
isPwd.value = true;
|
||||||
@@ -294,40 +257,25 @@ const closeDg = () => {
|
|||||||
onReset();
|
onReset();
|
||||||
}
|
}
|
||||||
|
|
||||||
const onSubmit = () => {
|
const onSubmit = async () => {
|
||||||
addEditLoading.value = true;
|
addEditLoading.value = true;
|
||||||
if (editId.value !== 0) {
|
const param = {
|
||||||
api.put(`api/v1/users/${editId.value}`, {
|
id: editId.value,
|
||||||
'first_name': firstName.value,
|
first_name: firstName.value,
|
||||||
'last_name': lastName.value,
|
last_name: lastName.value,
|
||||||
'is_superuser': isSuperuser.value,
|
is_superuser: isSuperuser.value,
|
||||||
'is_active': isActive.value,
|
is_active: isActive.value,
|
||||||
'email': email.value,
|
email: email.value,
|
||||||
// "roles": roles.value,
|
password: (isCreate.value || resetPsw.value) ? pwd.value : undefined
|
||||||
...(isCreate.value || resetPsw.value ? { password: pwd.value } : {})
|
|
||||||
}).then(() => {
|
|
||||||
getUsers();
|
|
||||||
closeDg();
|
|
||||||
onReset();
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
else {
|
if (isCreate.value) {
|
||||||
api.post(`api/v1/users`, {
|
await userStore.addUser(param);
|
||||||
'id': 0,
|
} else {
|
||||||
'first_name': firstName.value,
|
await userStore.editUser(param);
|
||||||
'last_name': lastName.value,
|
|
||||||
'is_superuser': isSuperuser.value,
|
|
||||||
'is_active': isActive.value,
|
|
||||||
'email': email.value,
|
|
||||||
// "roles": roles.value,
|
|
||||||
'password': pwd.value
|
|
||||||
}).then(() => {
|
|
||||||
getUsers();
|
|
||||||
closeDg();
|
|
||||||
onReset();
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
getUsers();
|
||||||
|
closeDg();
|
||||||
|
onReset();
|
||||||
}
|
}
|
||||||
|
|
||||||
const onReset = () => {
|
const onReset = () => {
|
||||||
@@ -339,8 +287,6 @@ const onReset = () => {
|
|||||||
isSuperuser.value = false;
|
isSuperuser.value = false;
|
||||||
isPwd.value = true;
|
isPwd.value = true;
|
||||||
editId.value = 0;
|
editId.value = 0;
|
||||||
isCreate.value = true;
|
|
||||||
// roles.value = [];
|
|
||||||
resetPsw.value = false;
|
resetPsw.value = false;
|
||||||
addEditLoading.value = false;
|
addEditLoading.value = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,7 +97,8 @@ function appToAppDisplay(app: IManagedApp) {
|
|||||||
version: app.version,
|
version: app.version,
|
||||||
versionName: app.versionname,
|
versionName: app.versionname,
|
||||||
updateTime: formatDate(app.update_time),
|
updateTime: formatDate(app.update_time),
|
||||||
updateUser: userToUserDisplay(app.updateuser)
|
updateUser: userToUserDisplay(app.updateuser),
|
||||||
|
versionChanged: app.is_saved
|
||||||
} as IAppDisplay
|
} as IAppDisplay
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { api } from 'boot/axios';
|
import { api } from 'boot/axios';
|
||||||
import { IRoles, IRolesDisplay, IUser, IUserDisplay, IUserRolesDisplay } from 'src/types/UserTypes';
|
import { IRoles, IRolesDisplay, IUser, IUserDisplay, IUserRolesDisplay, IUserSubmit } from 'src/types/UserTypes';
|
||||||
import { Notify } from 'quasar'
|
import { Notify } from 'quasar'
|
||||||
import { IResponse } from 'src/types/BaseTypes';
|
import { IResponse } from 'src/types/BaseTypes';
|
||||||
|
|
||||||
@@ -12,6 +12,7 @@ export const useUserStore = defineStore('user', {
|
|||||||
roles: [] as IRolesDisplay[],
|
roles: [] as IRolesDisplay[],
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
|
// -------------------------- users --------------------------
|
||||||
async loadUsers() {
|
async loadUsers() {
|
||||||
this.reset('users');
|
this.reset('users');
|
||||||
try {
|
try {
|
||||||
@@ -29,6 +30,39 @@ export const useUserStore = defineStore('user', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getUserById(id: number) {
|
||||||
|
if (!this.userIds.has(id)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.users.find((item: IUserDisplay) => item.id === id);
|
||||||
|
},
|
||||||
|
|
||||||
|
async addUser(user: IUserSubmit) {
|
||||||
|
return await api.post('api/v1/users', user);
|
||||||
|
},
|
||||||
|
|
||||||
|
async editUser(user: IUserSubmit) {
|
||||||
|
const id = user.id;
|
||||||
|
delete user['id']
|
||||||
|
return await api.put(`api/v1/users/${id}`, user);
|
||||||
|
},
|
||||||
|
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
|
||||||
|
// -------------------------- roles --------------------------
|
||||||
async loadRoles() {
|
async loadRoles() {
|
||||||
this.reset('roles');
|
this.reset('roles');
|
||||||
try {
|
try {
|
||||||
@@ -45,26 +79,19 @@ export const useUserStore = defineStore('user', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
getUserById(id: number) {
|
async addRole(user: IUserRolesDisplay, role: IRolesDisplay) {
|
||||||
if (!this.userIds.has(id)) {
|
return await this.updateUserRole(user, user.roleIds.concat(role.id));
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return this.users.find((item: IUserDisplay) => item.id === id);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async deleteUser(userId: number) {
|
async removeRole(user: IUserRolesDisplay, role: IRolesDisplay) {
|
||||||
try {
|
return await this.updateUserRole(user, user.roleIds.filter(e => e !== role.id));
|
||||||
await api.delete(`api/v1/users/${userId}`)
|
},
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
async updateUserRole(user: IUserRolesDisplay, roleIds: number[]) {
|
||||||
Notify.create({
|
return await api.post('api/v1/userrole', {
|
||||||
icon: 'error',
|
userid: user.id,
|
||||||
color: 'negative',
|
roleIds
|
||||||
message: 'ユーザーの削除に失敗しました'
|
});
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
reset(target?: 'users'|'roles') {
|
reset(target?: 'users'|'roles') {
|
||||||
@@ -98,7 +125,14 @@ export function userToUserDisplay(user: IUser): IUserDisplay {
|
|||||||
|
|
||||||
export function userToUserRolesDisplay(user: IUser): IUserRolesDisplay {
|
export function userToUserRolesDisplay(user: IUser): IUserRolesDisplay {
|
||||||
const userRolesDisplay = userToUserDisplay(user) as IUserRolesDisplay;
|
const userRolesDisplay = userToUserDisplay(user) as IUserRolesDisplay;
|
||||||
userRolesDisplay.roles = user.roles.map((role) => roleToRoleDisplay(role)).sort((a, b) => a.level - b.level);
|
const roles: IRolesDisplay[] = [];
|
||||||
|
const roleIds: number[] = [];
|
||||||
|
user.roles.sort((a, b) => a.level - b.level).forEach((role) => {
|
||||||
|
roles.push(roleToRoleDisplay(role));
|
||||||
|
roleIds.push(role.id);
|
||||||
|
});
|
||||||
|
userRolesDisplay.roles = roles;
|
||||||
|
userRolesDisplay.roleIds = roleIds;
|
||||||
return userRolesDisplay;
|
return userRolesDisplay;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export interface IManagedApp {
|
|||||||
updateuser: IUser;
|
updateuser: IUser;
|
||||||
create_time: string;
|
create_time: string;
|
||||||
update_time: string;
|
update_time: string;
|
||||||
|
is_saved: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAppDisplay{
|
export interface IAppDisplay{
|
||||||
@@ -21,6 +22,7 @@ export interface IAppDisplay{
|
|||||||
updateTime:string;
|
updateTime:string;
|
||||||
version:number;
|
version:number;
|
||||||
versionName?: string;
|
versionName?: string;
|
||||||
|
versionChanged: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IVersionSubmit {
|
export interface IVersionSubmit {
|
||||||
|
|||||||
@@ -8,6 +8,11 @@ export interface IUser {
|
|||||||
roles: IRoles[];
|
roles: IRoles[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IUserSubmit extends Omit<IUser, 'id' | 'roles'> {
|
||||||
|
id?: number;
|
||||||
|
password?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IUserDisplay {
|
export interface IUserDisplay {
|
||||||
id: number;
|
id: number;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
@@ -21,6 +26,7 @@ export interface IUserDisplay {
|
|||||||
|
|
||||||
export interface IUserRolesDisplay extends IUserDisplay {
|
export interface IUserRolesDisplay extends IUserDisplay {
|
||||||
roles: IRolesDisplay[];
|
roles: IRolesDisplay[];
|
||||||
|
roleIds: number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IRoles {
|
export interface IRoles {
|
||||||
|
|||||||
Reference in New Issue
Block a user