feat: table 表格合并&第一行操作

This commit is contained in:
RubyLiu 2023-08-18 18:18:53 +08:00 committed by 刘瑞斌
parent 33ef309848
commit 75a2c40ace
15 changed files with 150 additions and 50 deletions

View File

@ -38,7 +38,7 @@
"@7polo/kity": "2.0.8", "@7polo/kity": "2.0.8",
"@7polo/kityminder-core": "1.4.53", "@7polo/kityminder-core": "1.4.53",
"@arco-design/web-vue": "^2.49.2", "@arco-design/web-vue": "^2.49.2",
"@arco-themes/vue-ms-theme-default": "^0.0.25", "@arco-themes/vue-ms-theme-default": "^0.0.28",
"@form-create/arco-design": "^3.1.22", "@form-create/arco-design": "^3.1.22",
"@types/color": "^3.0.3", "@types/color": "^3.0.3",
"@vueuse/core": "^10.2.1", "@vueuse/core": "^10.2.1",

View File

@ -2,7 +2,10 @@ import MSR from '@/api/http/index';
import * as orgUrl from '@/api/requrls/setting/system/organizationAndProject'; import * as orgUrl from '@/api/requrls/setting/system/organizationAndProject';
import { TableQueryParams } from '@/models/common'; import { TableQueryParams } from '@/models/common';
import { AddUserToOrgOrProjectParams } from '@/models/setting/systemOrg'; import { AddUserToOrgOrProjectParams } from '@/models/setting/systemOrg';
import { CreateOrUpdateSystemOrgParams } from '@/models/setting/system/orgAndProject'; import {
CreateOrUpdateSystemOrgParams,
CreateOrUpdateSystemProjectParams,
} from '@/models/setting/system/orgAndProject';
// 获取组织列表 // 获取组织列表
export function postOrgTable(data: TableQueryParams) { export function postOrgTable(data: TableQueryParams) {
@ -10,7 +13,7 @@ export function postOrgTable(data: TableQueryParams) {
} }
// 创建或修改组织 // 创建或修改组织
export function createOrUpdateOrg(data: CreateOrUpdateSystemOrgParams) { export function createOrUpdateOrg(data: CreateOrUpdateSystemOrgParams | CreateOrUpdateSystemProjectParams) {
return MSR.post({ url: data.id ? orgUrl.postModifyOrgUrl : orgUrl.postAddOrgUrl, data }); return MSR.post({ url: data.id ? orgUrl.postModifyOrgUrl : orgUrl.postAddOrgUrl, data });
} }

View File

@ -11,8 +11,12 @@
v-bind="$attrs" v-bind="$attrs"
:row-class="getRowClass" :row-class="getRowClass"
:selected-keys="props.selectedKeys" :selected-keys="props.selectedKeys"
:span-method="spanMethod"
@selection-change="(e) => selectionChange(e, true)" @selection-change="(e) => selectionChange(e, true)"
> >
<template #optional="{ rowIndex, record }">
<slot name="optional" v-bind="{ rowIndex, record }" />
</template>
<template #columns> <template #columns>
<a-table-column <a-table-column
v-for="(item, idx) in currentColumns" v-for="(item, idx) in currentColumns"
@ -133,6 +137,7 @@
noDisable?: boolean; noDisable?: boolean;
showSetting?: boolean; showSetting?: boolean;
columns: MsTableColumn; columns: MsTableColumn;
spanMethod?: (params: { record: TableData; rowIndex: number; columnIndex: number }) => void;
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'selectedChange', value: (string | number)[]): void; (e: 'selectedChange', value: (string | number)[]): void;
@ -152,6 +157,31 @@
// inputRef // inputRef
const currentInputRef = ref(null); const currentInputRef = ref(null);
const { rowKey, editKey }: Partial<MsTableProps<any>> = attrs; const { rowKey, editKey }: Partial<MsTableProps<any>> = attrs;
//
const currentSpanMethod = ({
rowIndex,
columnIndex,
}: {
record: TableData;
rowIndex: number;
columnIndex: number;
}) => {
if (rowIndex === 0 && columnIndex === 0) {
return {
rowspan: 1,
colspan: currentColumns.value.length,
};
}
};
const spanMethod = computed(() => {
if (props.spanMethod) {
return props.spanMethod;
}
if (attrs.showFirstOperation) {
return currentSpanMethod;
}
return undefined;
});
const setSelectAllTotal = (isAll: boolean) => { const setSelectAllTotal = (isAll: boolean) => {
const { data, msPagination }: Partial<MsTableProps<any>> = attrs; const { data, msPagination }: Partial<MsTableProps<any>> = attrs;

View File

@ -77,6 +77,8 @@ export interface MsTableProps<T> {
tableErrorStatus?: MsTableErrorStatus; tableErrorStatus?: MsTableErrorStatus;
// debug模式开启后会打印表格所有state // debug模式开启后会打印表格所有state
debug?: boolean; debug?: boolean;
// 是否展示第一行的操作
showFirstOperation?: boolean;
[key: string]: any; [key: string]: any;
} }

View File

@ -56,6 +56,7 @@ export default function useTableProps<T>(
// 表格的错误状态 // 表格的错误状态
tableErrorStatus: false, tableErrorStatus: false,
debug: false, debug: false,
showFirstOperation: false,
...props, ...props,
}; };

View File

@ -4,3 +4,15 @@ export interface CreateOrUpdateSystemOrgParams {
description: string; description: string;
memberIds: string[]; memberIds: string[];
} }
export interface CreateOrUpdateSystemProjectParams {
id?: string;
name: string;
description: string;
enable: boolean;
userIds: string[];
// 模块配置 后端需要的字段 JSON string
moduleSetting?: string;
// 前端展示的模块配置 string[]
module?: string[];
organizationId?: string;
}

View File

@ -7,4 +7,5 @@ export interface UserGroupState {
currentType: string; currentType: string;
// 菜单开启关闭 // 菜单开启关闭
collapse: boolean; collapse: boolean;
currentInternal: boolean;
} }

View File

@ -7,6 +7,7 @@ const useUserGroupStore = defineStore('userGroup', {
currentTitle: '', currentTitle: '',
currentId: '', currentId: '',
currentType: '', currentType: '',
currentInternal: false,
collapse: true, collapse: true,
}), }),
getters: { getters: {

View File

@ -3,17 +3,17 @@
v-model:visible="currentVisible" v-model:visible="currentVisible"
width="680px" width="680px"
class="ms-modal-form ms-modal-medium" class="ms-modal-form ms-modal-medium"
:ok-text="t('system.organization.create')" :ok-text="isEdit ? t('common.update') : t('common.create')"
unmount-on-close unmount-on-close
@cancel="handleCancel" @cancel="handleCancel"
> >
<template #title> <template #title>
<span v-if="isEdit"> <span v-if="isEdit">
{{ t('system.organization.updateOrganization') }} {{ t('system.project.updateProject') }}
<span class="text-[var(--color-text-4)]">({{ props.currentOrganization?.name }})</span> <span class="text-[var(--color-text-4)]">({{ props.currentProject?.name }})</span>
</span> </span>
<span v-else> <span v-else>
{{ t('system.organization.createOrganization') }} {{ t('system.project.create') }}
</span> </span>
</template> </template>
<div class="form"> <div class="form">
@ -21,20 +21,23 @@
<a-form-item <a-form-item
field="name" field="name"
required required
:label="t('system.organization.organizationName')" :label="t('system.project.name')"
:rules="[{ required: true, message: t('system.organization.organizationNameRequired') }]" :rules="[{ required: true, message: t('system.project.projectNameRequired') }]"
> >
<a-input v-model="form.name" :placeholder="t('system.organization.organizationNamePlaceholder')" /> <a-input v-model="form.name" :placeholder="t('system.project.projectNamePlaceholder')" />
</a-form-item> </a-form-item>
<a-form-item field="name" :label="t('system.organization.organizationAdmin')"> <a-form-item field="organizationId" :label="t('system.project.affiliatedOrg')">
<MsUserSelector <a-input v-model="form.organizationId" :placeholder="t('system.project.affiliatedOrgPlaceholder')" />
v-model:value="form.memberIds" </a-form-item>
placeholder="system.organization.organizationAdminPlaceholder" <a-form-item field="userIds" :label="t('system.project.projectAdmin')">
/> <MsUserSelector v-model:value="form.userIds" placeholder="system.project.projectAdminPlaceholder" />
</a-form-item> </a-form-item>
<a-form-item field="description" :label="t('system.organization.description')"> <a-form-item field="description" :label="t('system.organization.description')">
<a-input v-model="form.description" :placeholder="t('system.organization.descriptionPlaceholder')" /> <a-input v-model="form.description" :placeholder="t('system.organization.descriptionPlaceholder')" />
</a-form-item> </a-form-item>
<a-form-item field="enable" :label="t('system.organization.description')">
<a-switch v-model="form.enable" :placeholder="t('system.organization.descriptionPlaceholder')" />
</a-form-item>
</a-form> </a-form>
</div> </div>
<template #footer> <template #footer>
@ -55,26 +58,30 @@
import MsUserSelector from '@/components/bussiness/ms-user-selector/index.vue'; import MsUserSelector from '@/components/bussiness/ms-user-selector/index.vue';
import { createOrUpdateOrg } from '@/api/modules/setting/system/organizationAndProject'; import { createOrUpdateOrg } from '@/api/modules/setting/system/organizationAndProject';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import { CreateOrUpdateSystemOrgParams } from '@/models/setting/system/orgAndProject'; import { CreateOrUpdateSystemProjectParams } from '@/models/setting/system/orgAndProject';
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps<{ const props = defineProps<{
visible: boolean; visible: boolean;
currentOrganization?: CreateOrUpdateSystemOrgParams; currentProject?: CreateOrUpdateSystemProjectParams;
}>(); }>();
const formRef = ref<FormInstance>(); const formRef = ref<FormInstance>();
const loading = ref(false); const loading = ref(false);
const isEdit = computed(() => !!props.currentProject?.id);
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'cancel'): void; (e: 'cancel'): void;
}>(); }>();
const form = reactive<{ name: string; memberIds: string[]; description: string }>({ const form = reactive<CreateOrUpdateSystemProjectParams>({
name: '', name: '',
memberIds: [], userIds: [],
organizationId: '',
description: '', description: '',
enable: true,
module: [],
}); });
const currentVisible = ref(props.visible); const currentVisible = ref(props.visible);
@ -93,9 +100,9 @@
} }
try { try {
loading.value = true; loading.value = true;
await createOrUpdateOrg({ id: props.currentOrganization?.id, ...form }); await createOrUpdateOrg({ id: props.currentProject?.id, ...form });
Message.success( Message.success(
props.currentOrganization?.id isEdit.value
? t('system.organization.updateOrganizationSuccess') ? t('system.organization.updateOrganizationSuccess')
: t('system.organization.createOrganizationSuccess') : t('system.organization.createOrganizationSuccess')
); );
@ -111,11 +118,12 @@
}); });
}; };
watchEffect(() => { watchEffect(() => {
if (props.currentOrganization) { if (props.currentProject) {
form.name = props.currentOrganization.name; form.name = props.currentProject.name;
form.memberIds = props.currentOrganization.memberIds; form.userIds = props.currentProject.userIds;
form.description = props.currentOrganization.description; form.description = props.currentProject.description;
form.organizationId = props.currentProject.organizationId;
form.enable = props.currentProject.enable;
} }
}); });
const isEdit = computed(() => !!props.currentOrganization?.id);
</script> </script>

View File

@ -282,6 +282,9 @@
}, },
{ immediate: true } { immediate: true }
); );
defineExpose({
fetchData,
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -278,6 +278,9 @@
}, },
{ immediate: true } { immediate: true }
); );
defineExpose({
fetchData,
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -24,11 +24,12 @@
</div> </div>
</div> </div>
<div> <div>
<SystemOrganization v-if="currentTable === 'organization'" :keyword="currentKeyword" /> <SystemOrganization v-if="currentTable === 'organization'" ref="orgTableRef" :keyword="currentKeyword" />
<SystemProject v-if="currentTable === 'project'" :keyword="currentKeyword" /> <SystemProject v-if="currentTable === 'project'" ref="projectTabeRef" :keyword="currentKeyword" />
</div> </div>
</MsCard> </MsCard>
<AddOrganizationModal :visible="organizationVisible" @cancel="handleAddOrganizationCancel" /> <AddOrganizationModal :visible="organizationVisible" @cancel="handleAddOrganizationCancel" />
<AddProjectModal :visible="projectVisible" @cancel="handleAddProjectCancel" />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -38,6 +39,7 @@
import AddOrganizationModal from './components/addOrganizationModal.vue'; import AddOrganizationModal from './components/addOrganizationModal.vue';
import SystemOrganization from './components/systemOrganization.vue'; import SystemOrganization from './components/systemOrganization.vue';
import SystemProject from './components/systemProject.vue'; import SystemProject from './components/systemProject.vue';
import AddProjectModal from './components/addProjectModal.vue';
const { t } = useI18n(); const { t } = useI18n();
const currentTable = ref('project'); const currentTable = ref('project');
@ -45,6 +47,9 @@
const organizationCount = ref(0); const organizationCount = ref(0);
const projectCount = ref(0); const projectCount = ref(0);
const currentKeyword = ref(''); const currentKeyword = ref('');
const orgTableRef = ref();
const projectTabeRef = ref();
const projectVisible = ref(false);
const handleSearch = (value: string) => { const handleSearch = (value: string) => {
currentKeyword.value = value; currentKeyword.value = value;
@ -54,9 +59,22 @@
}; };
const handleAddOrganization = () => { const handleAddOrganization = () => {
organizationVisible.value = true; if (currentTable.value === 'organization') {
organizationVisible.value = true;
} else {
projectVisible.value = true;
}
};
const handleAddProjectCancel = () => {
projectVisible.value = false;
}; };
const handleAddOrganizationCancel = () => { const handleAddOrganizationCancel = () => {
if (currentTable.value === 'organization') {
orgTableRef.value?.fetchData();
} else {
projectTabeRef.value?.fetchData();
}
organizationVisible.value = false; organizationVisible.value = false;
}; };
</script> </script>

View File

@ -15,9 +15,13 @@
<a-table-column :title="t('system.userGroup.auth')"> <a-table-column :title="t('system.userGroup.auth')">
<template #cell="{ record, rowIndex }"> <template #cell="{ record, rowIndex }">
<a-checkbox-group v-model="record.perChecked" @change="(v) => handleAuthChange(v, rowIndex)"> <a-checkbox-group v-model="record.perChecked" @change="(v) => handleAuthChange(v, rowIndex)">
<a-checkbox v-for="item in record.permissions" :key="item.id" :disabled="item.license" :value="item.id">{{ <a-checkbox
t(item.name) v-for="item in record.permissions"
}}</a-checkbox> :key="item.id"
:disabled="item.license || currentInternal"
:value="item.id"
>{{ t(item.name) }}</a-checkbox
>
</a-checkbox-group> </a-checkbox-group>
</template> </template>
</a-table-column> </a-table-column>
@ -27,6 +31,7 @@
v-if="tableData && tableData?.length > 0" v-if="tableData && tableData?.length > 0"
:model-value="allChecked" :model-value="allChecked"
:indeterminate="allIndeterminate" :indeterminate="allIndeterminate"
:disabled="currentInternal"
@change="handleAllChangeByCheckbox" @change="handleAllChangeByCheckbox"
></a-checkbox> ></a-checkbox>
</template> </template>
@ -34,6 +39,7 @@
<a-checkbox <a-checkbox
:model-value="record.enable" :model-value="record.enable"
:indeterminate="record.indeterminate" :indeterminate="record.indeterminate"
:disabled="currentInternal"
@change="(value) => handleActionChangeAll(value, rowIndex)" @change="(value) => handleActionChangeAll(value, rowIndex)"
/> />
</template> </template>
@ -45,7 +51,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { RenderFunction, VNodeChild, ref, watchEffect } from 'vue'; import { RenderFunction, VNodeChild, ref, watchEffect, computed } from 'vue';
import { type TableColumnData, type TableData } from '@arco-design/web-vue'; import { type TableColumnData, type TableData } from '@arco-design/web-vue';
import useUserGroupStore from '@/store/modules/setting/usergroup'; import useUserGroupStore from '@/store/modules/setting/usergroup';
import { getGlobalUSetting, saveGlobalUSetting } from '@/api/modules/setting/usergroup'; import { getGlobalUSetting, saveGlobalUSetting } from '@/api/modules/setting/usergroup';
@ -75,6 +81,10 @@
const tableData = ref<AuthTableItem[]>(); const tableData = ref<AuthTableItem[]>();
// //
const canSave = ref(false); const canSave = ref(false);
//
const currentInternal = computed(() => {
return store.userGroupInfo.currentInternal;
});
const dataSpanMethod = (data: { const dataSpanMethod = (data: {
record: TableData; record: TableData;

View File

@ -38,7 +38,7 @@
<span class="n1">{{ element.name }}</span> <span class="n1">{{ element.name }}</span>
<span v-if="element.type" class="n4">{{ t(`system.userGroup.${element.type}`) }}</span> <span v-if="element.type" class="n4">{{ t(`system.userGroup.${element.type}`) }}</span>
</div> </div>
<div v-if="element.id === currentId"> <div v-if="element.id === currentId && !element.internal">
<MsTableMoreAction :list="customAction" @select="(value) => handleMoreAction(value, element.id)" /> <MsTableMoreAction :list="customAction" @select="(value) => handleMoreAction(value, element.id)" />
</div> </div>
</div> </div>
@ -112,9 +112,15 @@
// //
const handleListItemClick = (element: UserGroupItem) => { const handleListItemClick = (element: UserGroupItem) => {
const { id, name, type } = element; const { id, name, type, internal } = element;
currentId.value = id; currentId.value = id;
store.setInfo({ currentName: name, currentTitle: type, currentId: id, currentType: type }); store.setInfo({
currentName: name,
currentTitle: type,
currentId: id,
currentType: type,
currentInternal: internal,
});
}; };
// //

View File

@ -19,15 +19,15 @@
@press-enter="handleEnter" @press-enter="handleEnter"
@search="handleSearch" @search="handleSearch"
></a-input-search> ></a-input-search>
<a-radio-group v-if="couldShowUser" v-model="currentTable" class="ml-[14px]" type="button"> <a-radio-group v-if="couldShowUser && couldShowAuth" v-model="currentTable" class="ml-[14px]" type="button">
<a-radio value="auth">{{ t('system.userGroup.auth') }}</a-radio> <a-radio v-if="couldShowAuth" value="auth">{{ t('system.userGroup.auth') }}</a-radio>
<a-radio value="user">{{ t('system.userGroup.user') }}</a-radio> <a-radio v-if="couldShowUser" value="user">{{ t('system.userGroup.user') }}</a-radio>
</a-radio-group> </a-radio-group>
</div> </div>
</div> </div>
<div class="mt-[16px]"> <div class="mt-[16px]">
<user-table v-if="currentTable === 'user'" :keyword="currentKeyword" /> <user-table v-if="currentTable === 'user' && couldShowUser" :keyword="currentKeyword" />
<auth-table v-if="currentTable === 'auth'" ref="authRef" /> <auth-table v-if="currentTable === 'auth' && couldShowAuth" ref="authRef" />
</div> </div>
</div> </div>
</div> </div>
@ -45,7 +45,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, computed, watch } from 'vue'; import { ref, computed, watchEffect } from 'vue';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import MsCard from '@/components/pure/ms-card/index.vue'; import MsCard from '@/components/pure/ms-card/index.vue';
import useUserGroupStore from '@/store/modules/setting/usergroup'; import useUserGroupStore from '@/store/modules/setting/usergroup';
@ -76,18 +76,11 @@
const store = useUserGroupStore(); const store = useUserGroupStore();
const couldShowUser = computed(() => store.userGroupInfo.currentType === 'SYSTEM'); const couldShowUser = computed(() => store.userGroupInfo.currentType === 'SYSTEM');
const couldShowAuth = computed(() => store.userGroupInfo.currentId !== 'admin');
const handleCollapse = () => { const handleCollapse = () => {
store.setCollapse(!store.collapse); store.setCollapse(!store.collapse);
}; };
const collapse = computed(() => store.collapse); const collapse = computed(() => store.collapse);
watch(
() => couldShowUser.value,
(val) => {
if (!val) {
currentTable.value = 'auth';
}
}
);
const menuWidth = computed(() => { const menuWidth = computed(() => {
const width = appStore.menuCollapse ? 86 : appStore.menuWidth; const width = appStore.menuCollapse ? 86 : appStore.menuWidth;
if (store.collapse) { if (store.collapse) {
@ -108,6 +101,15 @@
} }
return true; return true;
}); });
watchEffect(() => {
if (!couldShowAuth.value) {
currentTable.value = 'user';
} else if (!couldShowUser.value) {
currentTable.value = 'auth';
} else {
currentTable.value = 'auth';
}
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>