This commit is contained in:
2024-12-01 11:19:58 +09:00
10 changed files with 154 additions and 60 deletions

View File

@@ -115,7 +115,8 @@ module.exports = configure(function (/* ctx */) {
// Quasar plugins // Quasar plugins
plugins: [ plugins: [
'Notify' 'Notify',
'Dialog'
] ]
}, },

View File

@@ -1,37 +1,68 @@
<template> <template>
<div class="q-pa-md"> <div class="q-pa-md">
<q-table :title="name+'一覧'" :selection="type" v-model:selected="selected" :columns="columns" :rows="rows" /> <q-table :loading="loading" :title="name+'一覧'" :selection="type" v-model:selected="selected" :columns="columns" :rows="rows">
<template v-slot:body-cell-name="p">
<q-td class="content-box flex justify-between items-center" :props="p">
{{ p.row.name }}
<q-badge v-if="!p.row.domainActive" color="grey">未启用</q-badge>
<q-badge v-if="p.row.id == currentDomainId" color="primary">現在</q-badge>
</q-td>
</template>
</q-table>
</div> </div>
</template> </template>
<script> <script>
import { ref,onMounted,reactive } from 'vue' import { ref,onMounted,reactive, computed } from 'vue'
import { api } from 'boot/axios'; import { api } from 'boot/axios';
import { useAuthStore } from 'src/stores/useAuthStore';
export default { export default {
name: 'DomainSelect', name: 'DomainSelect',
props: { props: {
name: String, name: String,
type: String type: String,
filterInitRowsFunc: {
type: Function,
},
}, },
setup() { setup(props) {
const columns = [ const authStore = useAuthStore();
{ name: 'id'}, const currentDomainId = computed(() => authStore.currentDomain.id);
{ name: 'tenantid', required: true,label: 'テナント',align: 'left',field: 'tenantid',sortable: true}, const loading = ref(true);
{ name: 'name', align: 'center', label: 'ドメイン', field: 'name', sortable: true }, const inactiveRowClass = (row) => row.domainActive ? '' : 'inactive-row';
{ name: 'url', label: 'URL', field: 'url', sortable: true },
{ name: 'kintoneuser', label: 'アカウント', field: 'kintoneuser' } const columns = [
] { name: 'id', label: 'ID', field: 'id', align: 'left', sortable: true, classes: inactiveRowClass },
const rows = reactive([]) { name: 'tenantid', required: true,label: 'テナント',align: 'left',field: 'tenantid',sortable: true, classes: inactiveRowClass},
onMounted( () => { { name: 'name', align: 'center', label: 'ドメイン', field: 'name', sortable: true, classes: inactiveRowClass },
{ name: 'url', label: 'URL', field: 'url', sortable: true, classes: inactiveRowClass },
{ name: 'user', label: 'アカウント', field: 'user', classes: inactiveRowClass }
]
const rows = reactive([]);
onMounted(() => {
loading.value = true;
api.get(`api/domains`).then(res =>{ api.get(`api/domains`).then(res =>{
res.data.data.forEach((item) => res.data.data.forEach((data) => {
{ const item = {
rows.push({id:item.id,tenantid:item.tenantid,name:item.name,url:item.url,kintoneuser:item.kintoneuser}); id: data.id,
} tenantid: data.tenantid,
) domainActive: data.is_active,
}); name: data.name,
url: data.url,
user: data.kintoneuser,
}
if (props.filterInitRowsFunc && !props.filterInitRowsFunc(item)) {
return;
}
rows.push(item);
})
loading.value = false;
}); });
});
return { return {
loading,
currentDomainId,
columns, columns,
rows, rows,
selected: ref([]), selected: ref([]),
@@ -40,3 +71,11 @@ export default {
} }
</script> </script>
<style lang="scss">
.q-table td.inactive-row {
color: #aaa;
}
.q-table .content-box {
box-sizing: content-box;
}
</style>

View File

@@ -7,9 +7,9 @@
icon="share" icon="share"
size="md" size="md"
:label="userStore.currentDomain.domainName" :label="userStore.currentDomain.domainName"
:disable-dropdown="isUnclickable" :disable-dropdown="true"
:dropdown-icon="isUnclickable ? 'none' : ''" dropdown-icon='none'
:disable="isUnclickable" :disable="true"
> >
<q-list> <q-list>
<q-item :active="isCurrentDomain(domain)" active-class="active-domain-item" v-for="domain in domains" :key="domain.domainName" <q-item :active="isCurrentDomain(domain)" active-class="active-domain-item" v-for="domain in domains" :key="domain.domainName"

View File

@@ -4,6 +4,7 @@
tag="a" tag="a"
:target="target?target:'_blank'" :target="target?target:'_blank'"
:href="link" :href="link"
:disable="disable"
v-if="!isSeparator" v-if="!isSeparator"
> >
<q-item-section <q-item-section
@@ -33,6 +34,7 @@ export interface EssentialLinkProps {
icon?: string; icon?: string;
isSeparator?: boolean; isSeparator?: boolean;
target?:string; target?:string;
disable?:boolean;
} }
withDefaults(defineProps<EssentialLinkProps>(), { withDefaults(defineProps<EssentialLinkProps>(), {
caption: '', caption: '',

View File

@@ -34,20 +34,22 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onMounted } from 'vue'; import { computed, onMounted, reactive } from 'vue';
import EssentialLink, { EssentialLinkProps } from 'components/EssentialLink.vue'; import EssentialLink, { EssentialLinkProps } from 'components/EssentialLink.vue';
import DomainSelector from 'components/DomainSelector.vue'; import DomainSelector from 'components/DomainSelector.vue';
import { useAuthStore } from 'stores/useAuthStore'; import { useAuthStore } from 'stores/useAuthStore';
const authStore = useAuthStore(); const authStore = useAuthStore();
const noDomain = computed(() => !authStore.hasDomain);
const essentialLinks: EssentialLinkProps[] = [ const essentialLinks: EssentialLinkProps[] = reactive([
{ {
title: 'ホーム', title: 'ホーム',
caption: '設計書から導入する', caption: '設計書から導入する',
icon: 'home', icon: 'home',
link: '/', link: '/',
target: '_self' target: '_self',
disable: noDomain
}, },
// { // {
// title: 'フローエディター', // title: 'フローエディター',
@@ -61,7 +63,8 @@ const essentialLinks: EssentialLinkProps[] = [
caption: 'アプリを管理する', caption: 'アプリを管理する',
icon: 'widgets', icon: 'widgets',
link: '/#/app', link: '/#/app',
target: '_self' target: '_self',
disable: noDomain
}, },
// { // {
// title: '条件エディター', // title: '条件エディター',
@@ -92,9 +95,9 @@ const essentialLinks: EssentialLinkProps[] = [
// link:'https://cybozu.dev/ja/kintone/docs/', // link:'https://cybozu.dev/ja/kintone/docs/',
// icon:'help_outline' // icon:'help_outline'
// }, // },
]; ]);
const domainLinks: EssentialLinkProps[] = [ const domainLinks: EssentialLinkProps[] = reactive([
{ {
title: 'ドメイン管理', title: 'ドメイン管理',
caption: 'kintoneのドメイン設定', caption: 'kintoneのドメイン設定',
@@ -109,9 +112,9 @@ const domainLinks: EssentialLinkProps[] = [
link: '/#/userDomain', link: '/#/userDomain',
target: '_self' target: '_self'
}, },
]; ]);
const adminLinks: EssentialLinkProps[] = [ const adminLinks: EssentialLinkProps[] = reactive([
{ {
title: 'ユーザー管理', title: 'ユーザー管理',
caption: 'ユーザーを管理する', caption: 'ユーザーを管理する',
@@ -119,7 +122,7 @@ const adminLinks: EssentialLinkProps[] = [
link: '/#/user', link: '/#/user',
target: '_self' target: '_self'
}, },
] ])
const version = process.env.version; const version = process.env.version;
const productName = process.env.productName; const productName = process.env.productName;

View File

@@ -5,7 +5,7 @@
<q-breadcrumbs-el icon="domain" label="ドメイン管理" /> <q-breadcrumbs-el icon="domain" label="ドメイン管理" />
</q-breadcrumbs> </q-breadcrumbs>
</div> </div>
<q-table title="Treats" :rows="rows" :columns="columns" row-key="id" :filter="filter" :loading="loading" :pagination="pagination"> <q-table :rows="rows" :columns="columns" row-key="id" :filter="filter" :loading="loading" :pagination="pagination">
<template v-slot:top> <template v-slot:top>
<q-btn color="primary" :disable="loading" label="新規" @click="addRow" /> <q-btn color="primary" :disable="loading" label="新規" @click="addRow" />
@@ -21,7 +21,7 @@
<q-td class="flex justify-between items-center" :props="p"> <q-td class="flex justify-between items-center" :props="p">
{{ p.row.name }} {{ p.row.name }}
<q-badge v-if="!p.row.domainActive" color="grey">未启用</q-badge> <q-badge v-if="!p.row.domainActive" color="grey">未启用</q-badge>
<q-badge v-if="p.row.id == currendDomainId" color="primary">現在</q-badge> <q-badge v-if="p.row.id == currentDomainId" color="primary">現在</q-badge>
</q-td> </q-td>
</template> </template>
@@ -145,8 +145,7 @@ const columns = [
name: 'tenantid', name: 'tenantid',
required: true, required: true,
label: 'テナントID', label: 'テナントID',
field: row => row.tenantid, field: 'tenantid',
format: val => `${val}`,
align: 'left', align: 'left',
sortable: true, sortable: true,
classes: inactiveRowClass classes: inactiveRowClass
@@ -165,7 +164,7 @@ const show = ref(false);
const confirm = ref(false); const confirm = ref(false);
const resetPsw = ref(false); const resetPsw = ref(false);
const currendDomainId = computed(() => authStore.currentDomain.id); const currentDomainId = computed(() => authStore.currentDomain.id);
const tenantid = ref(authStore.currentDomain.id); const tenantid = ref(authStore.currentDomain.id);
const name = ref(''); const name = ref('');
const url = ref(''); const url = ref('');
@@ -180,9 +179,8 @@ let ownerid = ref('');
const getDomain = async () => { const getDomain = async () => {
loading.value = true; loading.value = true;
const userId = authStore.userId; const { data } = await api.get<{data:IDomain[]}>(`api/domains`);
const result = await api.get<IDomain[]>(`api/domains`); rows.value = data.data.map((item) => {
rows.value = result.data.data.map((item) => {
return { return {
id: item.id, id: item.id,
tenantid: item.tenantid, tenantid: item.tenantid,
@@ -215,6 +213,7 @@ const removeRow = (row: IDomainDisplay) => {
const deleteDomain = () => { const deleteDomain = () => {
api.delete(`api/domain/${editId.value}`).then(() => { api.delete(`api/domain/${editId.value}`).then(() => {
getDomain(); getDomain();
// authStore.setCurrentDomain();
}) })
editId.value = 0; // set in removeRow() editId.value = 0; // set in removeRow()
}; };

View File

@@ -14,14 +14,14 @@
<q-space /> <q-space />
<div class="row q-gutter-md"> <div class="row q-gutter-md">
<q-item v-if="authStore.permissions === 'admin'" tag="label" dense @click="clickSwitchUser()"> <!-- <q-item v-if="authStore.permissions === 'admin'" tag="label" dense @click="clickSwitchUser()">
<q-item-section> <q-item-section>
<q-item-label>適用するユーザ : </q-item-label> <q-item-label>適用するユーザ : </q-item-label>
</q-item-section> </q-item-section>
<q-item-section avatar> <q-item-section avatar>
{{ currentUserName }} {{ currentUserName }}
</q-item-section> </q-item-section>
</q-item> </q-item> -->
<q-input borderless dense filled debounce="300" v-model="userDomainTableFilter" placeholder="Search"> <q-input borderless dense filled debounce="300" v-model="userDomainTableFilter" placeholder="Search">
<template v-slot:append> <template v-slot:append>
@@ -62,7 +62,7 @@
isActive(props.row.id)?'既定':'' }}</div> isActive(props.row.id)?'既定':'' }}</div>
<div class="col-auto"> <div class="col-auto">
<q-btn v-if="!isActive(props.row.id)" flat <q-btn v-if="!isActive(props.row.id)" flat
@click="activeDomain(props.row.id)">既定にする</q-btn> @click="activeDomain(props.row)">既定にする</q-btn>
<q-btn flat @click="clickDeleteConfirm(props.row)">削除</q-btn> <q-btn flat @click="clickDeleteConfirm(props.row)">削除</q-btn>
</div> </div>
</div> </div>
@@ -74,7 +74,7 @@
</q-table> </q-table>
<show-dialog v-model:visible="showAddDomainDg" name="ドメイン" @close="addUserDomainFinished"> <show-dialog v-model:visible="showAddDomainDg" name="ドメイン" @close="addUserDomainFinished">
<domain-select ref="addDomainRef" name="ドメイン" type="multiple"></domain-select> <domain-select ref="addDomainRef" name="ドメイン" type="single" :filterInitRowsFunc="filterAddDgInitRows"></domain-select>
</show-dialog> </show-dialog>
<show-dialog v-model:visible="showSwitchUserDd" name="ドメイン" minWidth="35vw" @close="switchUserFinished"> <show-dialog v-model:visible="showSwitchUserDd" name="ドメイン" minWidth="35vw" @close="switchUserFinished">
@@ -123,14 +123,17 @@
import { ref, onMounted, computed } from 'vue' import { ref, onMounted, computed } from 'vue'
import { api } from 'boot/axios'; import { api } from 'boot/axios';
import { useAuthStore } from 'stores/useAuthStore'; import { useAuthStore } from 'stores/useAuthStore';
import { useDomainStore } from 'stores/useDomainStore';
import ShowDialog from 'components/ShowDialog.vue'; import ShowDialog from 'components/ShowDialog.vue';
import DomainSelect from 'components/DomainSelect.vue'; import DomainSelect from 'components/DomainSelect.vue';
import UserList from 'components/UserList.vue'; import UserList from 'components/UserList.vue';
const authStore = useAuthStore(); const authStore = useAuthStore();
const domainStore = useDomainStore();
const pagination = ref({ sortBy: 'id', rowsPerPage: 0 }); const pagination = ref({ sortBy: 'id', rowsPerPage: 0 });
const rows = ref([] as any[]); const rows = ref([] as any[]);
const rowIds = new Set<string>();
const loading = ref(true); const loading = ref(true);
const columns = [ const columns = [
@@ -152,20 +155,30 @@ let editId = ref(0);
const showAddDomainDg = ref(false); const showAddDomainDg = ref(false);
const addDomainRef = ref(); const addDomainRef = ref();
const filterAddDgInitRows = (row: {domainActive: boolean, id: string}) => {
return row.domainActive && !rowIds.has(row.id);
}
const clickAddDomain = () => { const clickAddDomain = () => {
editId.value = 0; editId.value = 0;
showAddDomainDg.value = true; showAddDomainDg.value = true;
}; };
const addUserDomainFinished = (val: string) => { const addUserDomainFinished = (val: string) => {
if (val == 'OK') { const selected = addDomainRef.value.selected;
let dodmainids = []; if (val == 'OK' && selected.length > 0) {
let domains = JSON.parse(JSON.stringify(addDomainRef.value.selected)); api.post(`api/domain/${useOtherUser.value ? otherUserId.value : authStore.userId}?domainid=${selected[0].id}`)
for (var key in domains) { .then(async ({ data }) => {
dodmainids.push(domains[key].id); if (rows.value.length === 0 && data.data) {
} const domain = data.data;
api.post(`api/domain/${useOtherUser.value ? otherUserId.value : authStore.userId}`, dodmainids) await authStore.setCurrentDomain({
.then(() => { getDomain(useOtherUser.value ? otherUserId.value : undefined); }); id: domain.id,
kintoneUrl: domain.url,
domainName: domain.name
});
}
getDomain(useOtherUser.value ? otherUserId.value : undefined);
});
} }
}; };
@@ -176,16 +189,27 @@ const clickDeleteConfirm = (row: any) => {
editId.value = row.id; editId.value = row.id;
}; };
const deleteDomainFinished = () => { const deleteDomainFinished = async () => {
api.delete(`api/domain/${editId.value}/${useOtherUser.value ? otherUserId.value : authStore.userId}`).then(() => { await api.delete(`api/domain/${editId.value}/${useOtherUser.value ? otherUserId.value : authStore.userId}`).then(({ data }) => {
if (data.msg == 'OK' && authStore.currentDomain.id === editId.value) {
authStore.setCurrentDomain();
}
getDomain(useOtherUser.value ? otherUserId.value : undefined); getDomain(useOtherUser.value ? otherUserId.value : undefined);
}) })
editId.value = 0; editId.value = 0;
}; };
const activeDomain = (id: number) => { const activeDomain = async (domain: any) => {
api.put(`api/activedomain/${id}${useOtherUser.value ? `?userId=${otherUserId.value}` : ''}`) if (useOtherUser.value) {
.then(() => { getDomain(useOtherUser.value ? otherUserId.value : undefined); }) // TODO
return;
}
await authStore.setCurrentDomain({
id: domain.id,
kintoneUrl: domain.url,
domainName: domain.name
});
getDomain();
}; };
let activeDomainId = ref(0); let activeDomainId = ref(0);
@@ -221,11 +245,17 @@ const switchUserFinished = async (val: string) => {
const getDomain = async (userId? : string) => { const getDomain = async (userId? : string) => {
loading.value = true; loading.value = true;
const resp = await api.get(`api/activedomain${useOtherUser.value ? `?userId=${otherUserId.value}` : ''}`); rowIds.clear();
if (useOtherUser.value) {
// TODO
return;
}
const resp = await api.get(`api/activedomain`);
activeDomainId.value = resp?.data?.data?.id; activeDomainId.value = resp?.data?.data?.id;
const domainResult = userId ? await api.get(`api/domain?userId=${userId}`) : await api.get(`api/domain`); const domainResult = userId ? await api.get(`api/domain?userId=${userId}`) : await api.get(`api/domain`);
const domains = domainResult.data as any[]; const domains = domainResult.data as any[];
rows.value = domains.map((item) => { rows.value = domains.map((item) => {
rowIds.add(item.id);
return { id: item.id, name: item.name, url: item.url, kintoneuser: item.kintoneuser, kintonepwd: item.kintonepwd } return { id: item.id, name: item.name, url: item.url, kintoneuser: item.kintoneuser, kintonepwd: item.kintonepwd }
}); });
loading.value = false; loading.value = false;

View File

@@ -5,6 +5,7 @@ import {
createWebHashHistory, createWebHashHistory,
createWebHistory, createWebHistory,
} from 'vue-router'; } from 'vue-router';
import { Dialog } from 'quasar'
import routes from './routes'; import routes from './routes';
import { useAuthStore } from 'stores/useAuthStore'; import { useAuthStore } from 'stores/useAuthStore';
@@ -47,6 +48,18 @@ export default route(function (/* { store, ssrContext } */) {
authStore.returnUrl = to.fullPath; authStore.returnUrl = to.fullPath;
return '/login'; return '/login';
} }
// redirect to domain setting page if no domain exist
const domainPages = [...publicPages, '/domain', '/userDomain', '/user'];
if (!authStore.hasDomain && !domainPages.includes(to.path)) {
Dialog.create({
title: '注意',
message: '既定/利用可能なドメインはありません。<br>ドメイン管理ページに遷移して処理します。',
html: true,
persistent: true,
})
return '/domain';
}
}); });
return routerInstance; return routerInstance;
}); });

View File

@@ -35,6 +35,9 @@ export const useAuthStore = defineStore('auth', {
toggleLeftDrawer(): boolean { toggleLeftDrawer(): boolean {
return this.LeftDrawer; return this.LeftDrawer;
}, },
hasDomain(): boolean {
return this.currentDomain.id !== undefined;
}
}, },
actions: { actions: {
setLeftMenu(value:boolean){ setLeftMenu(value:boolean){
@@ -86,7 +89,11 @@ export const useAuthStore = defineStore('auth', {
this.currentDomain = {} as IDomainInfo; // 清空当前域 this.currentDomain = {} as IDomainInfo; // 清空当前域
router.push('/login'); router.push('/login');
}, },
async setCurrentDomain(domain: IDomainInfo) { async setCurrentDomain(domain?: IDomainInfo) {
if (!domain) {
this.currentDomain = {} as IDomainInfo;
return;
}
if (domain.id === this.currentDomain.id) { if (domain.id === this.currentDomain.id) {
return; return;
} }

View File

@@ -30,6 +30,6 @@ export interface IDomainDisplay {
name: string; name: string;
url: string; url: string;
user: string; user: string;
password: string; password?: string;
domainActive: boolean; domainActive: boolean;
} }