czh-20260616-更改协作人选人组件
This commit is contained in:
@@ -0,0 +1,202 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a-row v-if="!hideTrigger" class="j-select-row" type="flex" :gutter="8">
|
||||||
|
<a-col class="left">
|
||||||
|
<a-select
|
||||||
|
:value="displayValue"
|
||||||
|
:placeholder="'请选择人员'"
|
||||||
|
:disabled="disabled"
|
||||||
|
:open="false"
|
||||||
|
mode="multiple"
|
||||||
|
:maxTagCount="3"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</a-col>
|
||||||
|
<a-col class="right">
|
||||||
|
<a-button type="primary" @click="open()" :disabled="disabled">选择</a-button>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
<a-modal
|
||||||
|
:width="1200"
|
||||||
|
:title="title"
|
||||||
|
v-model:open="visible"
|
||||||
|
centered
|
||||||
|
:confirm-loading="confirmLoading"
|
||||||
|
@ok="handleOk"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
:mask-closable="false"
|
||||||
|
>
|
||||||
|
<div v-if="dataLoading" style="height: 556px; display: flex; align-items: center; justify-content: center">
|
||||||
|
<a-spin tip="加载中..." />
|
||||||
|
</div>
|
||||||
|
<SzCollabUserSelect
|
||||||
|
v-show="!dataLoading"
|
||||||
|
ref="selectRef"
|
||||||
|
:secLevel="secLevel"
|
||||||
|
:departId="innerDepartId"
|
||||||
|
:role="role"
|
||||||
|
:isSpecial="isSpecial"
|
||||||
|
:editableUsers="innerEditable"
|
||||||
|
:readonlyUsers="innerReadonly"
|
||||||
|
:excludedUserIds="excludedUserIds"
|
||||||
|
@update:editableUsers="(v) => (tempEditable = v)"
|
||||||
|
@update:readonlyUsers="(v) => (tempReadonly = v)"
|
||||||
|
/>
|
||||||
|
<template #footer>
|
||||||
|
<a-button @click="handleCancel">取消</a-button>
|
||||||
|
<a-button type="primary" :loading="confirmLoading" @click="handleOk">确定</a-button>
|
||||||
|
</template>
|
||||||
|
</a-modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
import { defHttp } from '/@/utils/http/axios';
|
||||||
|
import { useUserStore } from '/@/store/modules/user';
|
||||||
|
import SzCollabUserSelect from './SzCollabUserSelect.vue';
|
||||||
|
import type { UserItem, CollabConfirmResult } from './types';
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
editableUserIds?: string;
|
||||||
|
readonlyUserIds?: string;
|
||||||
|
departId?: string;
|
||||||
|
secLevel?: any;
|
||||||
|
role?: string | number;
|
||||||
|
isSpecial?: number;
|
||||||
|
disabled?: boolean;
|
||||||
|
title?: string;
|
||||||
|
hideTrigger?: boolean;
|
||||||
|
excludedUserIds?: string;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
editableUserIds: '',
|
||||||
|
readonlyUserIds: '',
|
||||||
|
departId: '',
|
||||||
|
secLevel: 0,
|
||||||
|
role: 1,
|
||||||
|
isSpecial: 0,
|
||||||
|
disabled: false,
|
||||||
|
title: '选择协作人',
|
||||||
|
hideTrigger: false,
|
||||||
|
excludedUserIds: '',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const emit = defineEmits(['confirm']);
|
||||||
|
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const selectRef = ref<InstanceType<typeof SzCollabUserSelect> | null>(null);
|
||||||
|
const visible = ref(false);
|
||||||
|
const dataLoading = ref(false);
|
||||||
|
const confirmLoading = ref(false);
|
||||||
|
let loadSeq = 0;
|
||||||
|
const innerEditable = ref<UserItem[]>([]);
|
||||||
|
const innerReadonly = ref<UserItem[]>([]);
|
||||||
|
const tempEditable = ref<UserItem[]>([]);
|
||||||
|
const tempReadonly = ref<UserItem[]>([]);
|
||||||
|
|
||||||
|
const innerDepartId = computed(() => {
|
||||||
|
return props.departId || userStore.getUserInfo?.orgCode || '';
|
||||||
|
});
|
||||||
|
|
||||||
|
const displayValue = computed(() => {
|
||||||
|
const names: string[] = [];
|
||||||
|
for (const u of innerEditable.value) names.push(`[编]${u.realname}`);
|
||||||
|
for (const u of innerReadonly.value) names.push(`[阅]${u.realname}`);
|
||||||
|
return names;
|
||||||
|
});
|
||||||
|
|
||||||
|
async function loadUsers(valueStr: string, permission: '2' | '3') {
|
||||||
|
if (!valueStr) return [];
|
||||||
|
try {
|
||||||
|
const res: any = await defHttp.get({
|
||||||
|
url: '/sys/user/queryUserComponentData',
|
||||||
|
params: { id: valueStr, isMultiTranslate: 'true', pageNo: 1, pageSize: 9999 },
|
||||||
|
});
|
||||||
|
return (res.records || res) as UserItem[];
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadInitialUsers() {
|
||||||
|
const [editUsers, readUsers] = await Promise.all([
|
||||||
|
loadUsers(props.editableUserIds, '2'),
|
||||||
|
loadUsers(props.readonlyUserIds, '3'),
|
||||||
|
]);
|
||||||
|
innerEditable.value = editUsers;
|
||||||
|
innerReadonly.value = readUsers;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function open(editIds?: string, readIds?: string) {
|
||||||
|
if (props.disabled) return;
|
||||||
|
visible.value = true;
|
||||||
|
confirmLoading.value = false;
|
||||||
|
|
||||||
|
const hasExplicitData = editIds !== undefined || readIds !== undefined;
|
||||||
|
const hasPropsData = props.editableUserIds || props.readonlyUserIds;
|
||||||
|
|
||||||
|
if (!hasExplicitData && !hasPropsData) {
|
||||||
|
dataLoading.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dataLoading.value = true;
|
||||||
|
const seq = ++loadSeq;
|
||||||
|
|
||||||
|
const loadUsersTask = (async () => {
|
||||||
|
let editU: UserItem[] = [];
|
||||||
|
let readU: UserItem[] = [];
|
||||||
|
if (hasExplicitData) {
|
||||||
|
[editU, readU] = await Promise.all([loadUsers(editIds || ''), loadUsers(readIds || '')]);
|
||||||
|
} else {
|
||||||
|
[editU, readU] = await Promise.all([loadUsers(props.editableUserIds), loadUsers(props.readonlyUserIds)]);
|
||||||
|
}
|
||||||
|
if (seq !== loadSeq) return;
|
||||||
|
innerEditable.value = editU;
|
||||||
|
innerReadonly.value = readU;
|
||||||
|
tempEditable.value = [...editU];
|
||||||
|
tempReadonly.value = [...readU];
|
||||||
|
})();
|
||||||
|
|
||||||
|
const tableTask = selectRef.value?.resetSearch?.();
|
||||||
|
|
||||||
|
await Promise.all([loadUsersTask, tableTask]);
|
||||||
|
if (seq !== loadSeq) return;
|
||||||
|
dataLoading.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleOk() {
|
||||||
|
const result: CollabConfirmResult = selectRef.value?.getResult() || {
|
||||||
|
editableIds: '', editableNames: '', readonlyIds: '', readonlyNames: '',
|
||||||
|
};
|
||||||
|
emit('confirm', result);
|
||||||
|
visible.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCancel() {
|
||||||
|
visible.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open, loadInitialUsers });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.j-select-row {
|
||||||
|
@width: 82px;
|
||||||
|
|
||||||
|
.left {
|
||||||
|
width: calc(100% - @width - 8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
width: @width;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-select-search__field) {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,536 @@
|
|||||||
|
<template>
|
||||||
|
<a-row :gutter="0" style="height: 556px">
|
||||||
|
<!-- 部门树 -->
|
||||||
|
<a-col :span="6" style="height: 100%; border-right: 1px solid #e8e8e8; padding: 0 12px; display: flex; flex-direction: column">
|
||||||
|
<a-input-search v-model:value="searchDepartValue" style="margin-bottom: 12px; margin-top: 0" placeholder="请输入部门名称" />
|
||||||
|
<a-directory-tree
|
||||||
|
selectable
|
||||||
|
v-model:selectedKeys="selectedDepIds"
|
||||||
|
:check-strictly="true"
|
||||||
|
:dropdown-style="{ maxHeight: '200px', overflow: 'auto' }"
|
||||||
|
:tree-data="filterDepartTreeData"
|
||||||
|
:expand-action="false"
|
||||||
|
v-model:expandedKeys="expandedKeys"
|
||||||
|
@select="onDepSelect"
|
||||||
|
style="height: 500px; overflow: auto"
|
||||||
|
/>
|
||||||
|
</a-col>
|
||||||
|
<!-- 人员表格 -->
|
||||||
|
<a-col :span="10" style="height: 100%; padding: 0 12px; border-right: 1px solid #e8e8e8; display: flex; flex-direction: column">
|
||||||
|
<div style="margin-bottom: 12px; margin-top: 0; display: flex; align-items: center; gap: 8px; flex-wrap: wrap">
|
||||||
|
<a-input-search
|
||||||
|
style="flex: 1; min-width: 120px"
|
||||||
|
placeholder="输入姓名"
|
||||||
|
v-model:value="queryParam.realname"
|
||||||
|
@search="onSearch"
|
||||||
|
/>
|
||||||
|
<a-button @click="searchReset(1)">重置</a-button>
|
||||||
|
<a-button type="primary" @click="selectAll">全选</a-button>
|
||||||
|
<a-button type="danger" @click="unselectAll">清空</a-button>
|
||||||
|
</div>
|
||||||
|
<div style="margin-bottom: 8px; display: flex; align-items: center; gap: 6px">
|
||||||
|
<span style="font-size: 12px; color: #666">当前选择模式:</span>
|
||||||
|
<a-radio-group v-model:value="activePermission" size="small" button-style="solid">
|
||||||
|
<a-radio-button value="2" style="padding: 0 8px">编 可编辑</a-radio-button>
|
||||||
|
<a-radio-button value="3" style="padding: 0 8px">阅 可阅读</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
|
</div>
|
||||||
|
<div style="flex: 1; min-height: 0; overflow: auto">
|
||||||
|
<a-empty v-if="selectedDepIds.length === 0" description="请先选择部门!" />
|
||||||
|
<a-table
|
||||||
|
v-else
|
||||||
|
:scroll="{ y: 390 }"
|
||||||
|
size="middle"
|
||||||
|
row-key="id"
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="dataSource"
|
||||||
|
:pagination="ipagination"
|
||||||
|
:row-selection="{ selectedRowKeys: selectedRowKeys, onSelectAll: onSelectAll, onSelect: onSelect, type: 'checkbox', preserveSelectedRowKeys: true, getCheckboxProps: getCheckboxProps }"
|
||||||
|
:loading="loading"
|
||||||
|
@change="handleTableChange"
|
||||||
|
:custom-row="customRow"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
<!-- 已选列表 -->
|
||||||
|
<a-col :span="8" style="height: 100%; padding: 0 12px; display: flex; flex-direction: column">
|
||||||
|
<div style="margin-bottom: 12px; margin-top: 0; display: flex; align-items: center; gap: 8px; flex-wrap: wrap">
|
||||||
|
<span style="font-weight: 500">已选 ({{ selectedRows.length }})</span>
|
||||||
|
<a-button type="danger" @click="clearSelected">清空</a-button>
|
||||||
|
</div>
|
||||||
|
<div style="flex: 1; overflow: auto; min-height: 0">
|
||||||
|
<a-empty v-if="selectedRows.length === 0" description="暂无已选用户" :image-style="{ height: '40px' }" />
|
||||||
|
<div v-else class="selected-list">
|
||||||
|
<a-button-group v-for="(user) in selectedRows" :key="user.id" class="small-space selected-item">
|
||||||
|
<a-dropdown trigger="click">
|
||||||
|
<a-button size="small" :class="user.permission === '2' ? 'btn-edit' : 'btn-read'">
|
||||||
|
{{ user.permission === '2' ? '编' : '阅' }}
|
||||||
|
</a-button>
|
||||||
|
<template #overlay>
|
||||||
|
<a-menu @click="({ key }) => onMoveUserPermission(user, key as '2' | '3')">
|
||||||
|
<a-menu-item key="2" :class="{ 'active-menu-key': user.permission === '2' }">
|
||||||
|
<span>编 可编辑</span>
|
||||||
|
<CheckOutlined v-if="user.permission === '2'" class="check-icon" />
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="3" :class="{ 'active-menu-key': user.permission === '3' }">
|
||||||
|
<span>阅 可阅读</span>
|
||||||
|
<CheckOutlined v-if="user.permission === '3'" class="check-icon" />
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</template>
|
||||||
|
</a-dropdown>
|
||||||
|
<a-button size="small" :class="user.permission === '2' ? 'btn-edit' : 'btn-read'">
|
||||||
|
{{ user.realname }}
|
||||||
|
</a-button>
|
||||||
|
<a-button size="small" :class="user.permission === '2' ? 'btn-edit' : 'btn-read'" @click="removeSingle(user)">
|
||||||
|
<CloseOutlined />
|
||||||
|
</a-button>
|
||||||
|
</a-button-group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="selectedRows.length > 0" style="padding: 8px 0; font-size: 12px; color: #888; border-top: 1px solid #f0f0f0">
|
||||||
|
<span style="color: #1677ff">编辑 {{ editableCount }} 人</span>
|
||||||
|
<span style="margin: 0 8px">|</span>
|
||||||
|
<span style="color: #52c41a">阅读 {{ readonlyCount }} 人</span>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, reactive, computed, watch } from 'vue';
|
||||||
|
import { CheckOutlined, CloseOutlined } from '@ant-design/icons-vue';
|
||||||
|
import { queryTreeList } from '/@/api/common/api';
|
||||||
|
import { defHttp } from '/@/utils/http/axios';
|
||||||
|
import { filterObj } from '/@/utils/common/compUtils';
|
||||||
|
import { deleteIfExist, pushIfNotExist, filterDepartTree, recurTree } from './types';
|
||||||
|
import type { UserItem, DepartTreeNode } from './types';
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
secLevel: any;
|
||||||
|
editableUsers?: UserItem[];
|
||||||
|
readonlyUsers?: UserItem[];
|
||||||
|
departId: string;
|
||||||
|
role: string | number;
|
||||||
|
isSpecial?: number;
|
||||||
|
excludedUserIds?: string;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
editableUsers: () => [],
|
||||||
|
readonlyUsers: () => [],
|
||||||
|
isSpecial: 0,
|
||||||
|
excludedUserIds: '',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const excludedIdSet = computed(() => {
|
||||||
|
if (!props.excludedUserIds) return new Set<string>();
|
||||||
|
return new Set(props.excludedUserIds.split(',').filter(Boolean));
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:editableUsers', 'update:readonlyUsers']);
|
||||||
|
|
||||||
|
type Permission = '2' | '3';
|
||||||
|
const activePermission = ref<Permission>('2');
|
||||||
|
const searchDepartValue = ref('');
|
||||||
|
const queryParam = reactive<Record<string, any>>({ realname: '' });
|
||||||
|
const dataSource = ref<UserItem[]>([]);
|
||||||
|
const selectedDepIds = ref<string[]>([]);
|
||||||
|
const editableList = ref<UserItem[]>([]);
|
||||||
|
const readonlyList = ref<UserItem[]>([]);
|
||||||
|
const departTree = ref<DepartTreeNode[]>([]);
|
||||||
|
const fullDepartTree = ref<DepartTreeNode[]>([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const expandedKeys = ref<string[]>([]);
|
||||||
|
|
||||||
|
const selectedRows = computed<(UserItem & { permission: Permission })[]>(() => {
|
||||||
|
return [
|
||||||
|
...editableList.value.map((u) => ({ ...u, permission: '2' as Permission })),
|
||||||
|
...readonlyList.value.map((u) => ({ ...u, permission: '3' as Permission })),
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
const editableCount = computed(() => editableList.value.length);
|
||||||
|
const readonlyCount = computed(() => readonlyList.value.length);
|
||||||
|
|
||||||
|
const currentPermissionRows = computed(() => (activePermission.value === '2' ? editableList.value : readonlyList.value));
|
||||||
|
const otherPermissionIds = computed(() => {
|
||||||
|
const other = activePermission.value === '2' ? readonlyList.value : editableList.value;
|
||||||
|
return new Set(other.map((u) => u.id));
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedRowKeys = computed(() => currentPermissionRows.value.map((item) => item.id));
|
||||||
|
|
||||||
|
function findDepartIdByOrgCode(tree: DepartTreeNode[], orgCode: string): string | null {
|
||||||
|
for (const node of tree) {
|
||||||
|
if (node.orgCode === orgCode) return node.id;
|
||||||
|
if (node.children && node.children.length > 0) {
|
||||||
|
const found = findDepartIdByOrgCode(node.children, orgCode);
|
||||||
|
if (found) return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveDepartId(departIdOrOrgCode: string): string {
|
||||||
|
if (!departIdOrOrgCode) return departIdOrOrgCode;
|
||||||
|
const uuidPattern = /^[0-9a-f]{32}$/i;
|
||||||
|
if (uuidPattern.test(departIdOrOrgCode.replace(/-/g, ''))) return departIdOrOrgCode;
|
||||||
|
const realId = findDepartIdByOrgCode(fullDepartTree.value, departIdOrOrgCode);
|
||||||
|
if (realId) return realId;
|
||||||
|
return departIdOrOrgCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ title: '用户姓名', align: 'center' as const, dataIndex: 'realname' },
|
||||||
|
{ title: '人员密级', align: 'center' as const, dataIndex: 'userSecurityLevel_dictText' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const ipagination = reactive({
|
||||||
|
current: 1,
|
||||||
|
pageSize: 7,
|
||||||
|
showQuickJumper: true,
|
||||||
|
total: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const isorter = reactive({
|
||||||
|
column: 'sortno',
|
||||||
|
order: 'asc',
|
||||||
|
});
|
||||||
|
|
||||||
|
const url = {
|
||||||
|
pageUserDepart: '/sys/user/queryUserComponentData',
|
||||||
|
};
|
||||||
|
|
||||||
|
const filterDepartTreeData = computed(() => {
|
||||||
|
if (!searchDepartValue.value) return departTree.value;
|
||||||
|
return filterDepartTree(departTree.value, searchDepartValue.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
function getCheckboxProps(record: UserItem) {
|
||||||
|
return { disabled: otherPermissionIds.value.has(record.id) || excludedIdSet.value.has(record.id) };
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncFromProps() {
|
||||||
|
editableList.value = (props.editableUsers || []).filter((u) => !excludedIdSet.value.has(u.id));
|
||||||
|
readonlyList.value = (props.readonlyUsers || []).filter((u) => !excludedIdSet.value.has(u.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => props.editableUsers, syncFromProps);
|
||||||
|
watch(() => props.readonlyUsers, syncFromProps);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.departId,
|
||||||
|
() => { queryDepartTree(); },
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.role,
|
||||||
|
() => { queryDepartTree(); }
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.secLevel,
|
||||||
|
() => {
|
||||||
|
if (selectedDepIds.value.length > 0) {
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
function initData() {
|
||||||
|
const depId = props.departId;
|
||||||
|
if (depId) {
|
||||||
|
const resolvedId = resolveDepartId(depId);
|
||||||
|
selectedDepIds.value = [resolvedId];
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
|
syncFromProps();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadData(arg?: number) {
|
||||||
|
if (selectedDepIds.value.length > 0) {
|
||||||
|
if (arg === 1) {
|
||||||
|
ipagination.current = 1;
|
||||||
|
}
|
||||||
|
const params = getQueryParams();
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const res: any = await defHttp.get({ url: url.pageUserDepart, params });
|
||||||
|
dataSource.value = (res.records || []).filter((user: UserItem) => {
|
||||||
|
const userLevel = user.userSecurityLevel;
|
||||||
|
const docLevel = props.secLevel;
|
||||||
|
return userLevel !== null && userLevel > docLevel;
|
||||||
|
});
|
||||||
|
ipagination.total = res.total;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getQueryParams() {
|
||||||
|
const param = Object.assign({}, queryParam, isorter);
|
||||||
|
param.searchSecurityLevel = props.secLevel;
|
||||||
|
param.field = getQueryField();
|
||||||
|
param.pageNo = ipagination.current;
|
||||||
|
param.pageSize = ipagination.pageSize;
|
||||||
|
param.departId = selectedDepIds.value.join(',');
|
||||||
|
param.isSpecial = props.isSpecial;
|
||||||
|
return filterObj(param);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getQueryField() {
|
||||||
|
let str = 'id,';
|
||||||
|
for (let a = 0; a < columns.length; a++) {
|
||||||
|
str += ',' + columns[a].dataIndex;
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchReset(num?: number) {
|
||||||
|
if (num !== 0) {
|
||||||
|
queryParam.realname = '';
|
||||||
|
loadData(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTableChange(pagination: any, _filters: any, sorter: any) {
|
||||||
|
if (Object.keys(sorter).length > 0) {
|
||||||
|
isorter.column = sorter.field;
|
||||||
|
isorter.order = sorter.order === 'ascend' ? 'asc' : 'desc';
|
||||||
|
}
|
||||||
|
Object.assign(ipagination, pagination);
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDepSelect(keys: string[]) {
|
||||||
|
if (keys.length > 0 && keys[0] != null) {
|
||||||
|
selectedDepIds.value = [keys[0]];
|
||||||
|
loadData(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeFromPermission(userId: string, list: UserItem[]) {
|
||||||
|
const idx = list.findIndex((u) => u.id === userId);
|
||||||
|
if (idx >= 0) list.splice(idx, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addToPermission(user: UserItem, list: UserItem[]) {
|
||||||
|
if (!list.find((u) => u.id === user.id)) {
|
||||||
|
list.push(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveUser(user: UserItem, fromList: UserItem[], toList: UserItem[]) {
|
||||||
|
removeFromPermission(user.id, fromList);
|
||||||
|
addToPermission(user, toList);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMoveUserPermission(user: UserItem & { permission?: Permission }, newPermission: Permission) {
|
||||||
|
if (newPermission === '2') {
|
||||||
|
removeFromPermission(user.id, readonlyList.value);
|
||||||
|
addToPermission(user, editableList.value);
|
||||||
|
} else {
|
||||||
|
removeFromPermission(user.id, editableList.value);
|
||||||
|
addToPermission(user, readonlyList.value);
|
||||||
|
}
|
||||||
|
emitUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSelect(record: UserItem, selected: boolean) {
|
||||||
|
const currentList = activePermission.value === '2' ? editableList.value : readonlyList.value;
|
||||||
|
if (selected) {
|
||||||
|
addToPermission(record, currentList);
|
||||||
|
// Also remove from other list (move)
|
||||||
|
const otherList = activePermission.value === '2' ? readonlyList.value : editableList.value;
|
||||||
|
removeFromPermission(record.id, otherList);
|
||||||
|
} else {
|
||||||
|
removeFromPermission(record.id, currentList);
|
||||||
|
}
|
||||||
|
emitUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSelectAll(selected: boolean, _allRecords: UserItem[], changeRecords: UserItem[]) {
|
||||||
|
const changedRows = Array.isArray(changeRecords) ? changeRecords.filter(Boolean) : [];
|
||||||
|
const currentList = activePermission.value === '2' ? editableList.value : readonlyList.value;
|
||||||
|
const otherList = activePermission.value === '2' ? readonlyList.value : editableList.value;
|
||||||
|
if (selected) {
|
||||||
|
changedRows.forEach((item) => {
|
||||||
|
addToPermission(item, currentList);
|
||||||
|
removeFromPermission(item.id, otherList);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
changedRows.forEach((item) => {
|
||||||
|
removeFromPermission(item.id, currentList);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
emitUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSearch() {
|
||||||
|
loadData(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function queryDepartTree() {
|
||||||
|
expandedKeys.value = [];
|
||||||
|
departTree.value = [];
|
||||||
|
try {
|
||||||
|
const res: any = await queryTreeList();
|
||||||
|
const arr = [...res];
|
||||||
|
fullDepartTree.value = [...arr];
|
||||||
|
|
||||||
|
if (props.role === 1 || props.role === '1') {
|
||||||
|
departTree.value = [...arr];
|
||||||
|
} else {
|
||||||
|
const resolvedDepartId = resolveDepartId(props.departId);
|
||||||
|
const tree = recurTree(arr, (node) => {
|
||||||
|
if (node.id === resolvedDepartId) return [node];
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
departTree.value = [...tree];
|
||||||
|
}
|
||||||
|
departTree.value.forEach((item) => {
|
||||||
|
expandedKeys.value.push(item.key);
|
||||||
|
});
|
||||||
|
await initData();
|
||||||
|
} catch (e) {
|
||||||
|
console.error('queryDepartTree error:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectAll() {
|
||||||
|
const currentList = activePermission.value === '2' ? editableList.value : readonlyList.value;
|
||||||
|
const otherList = activePermission.value === '2' ? readonlyList.value : editableList.value;
|
||||||
|
dataSource.value.forEach((item) => {
|
||||||
|
if (excludedIdSet.value.has(item.id)) return;
|
||||||
|
addToPermission(item, currentList);
|
||||||
|
removeFromPermission(item.id, otherList);
|
||||||
|
});
|
||||||
|
emitUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function unselectAll() {
|
||||||
|
const currentList = activePermission.value === '2' ? editableList.value : readonlyList.value;
|
||||||
|
dataSource.value.forEach((item) => {
|
||||||
|
if (excludedIdSet.value.has(item.id)) return;
|
||||||
|
removeFromPermission(item.id, currentList);
|
||||||
|
});
|
||||||
|
emitUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearSelected() {
|
||||||
|
editableList.value = [];
|
||||||
|
readonlyList.value = [];
|
||||||
|
emitUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeSingle(user: { id: string; permission: Permission }) {
|
||||||
|
if (user.permission === '2') {
|
||||||
|
removeFromPermission(user.id, editableList.value);
|
||||||
|
} else {
|
||||||
|
removeFromPermission(user.id, readonlyList.value);
|
||||||
|
}
|
||||||
|
emitUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function emitUpdate() {
|
||||||
|
const editableIds = editableList.value.map((u) => u.id).join(',');
|
||||||
|
const editableNames = editableList.value.map((u) => u.realname).join(',');
|
||||||
|
const readonlyIds = readonlyList.value.map((u) => u.id).join(',');
|
||||||
|
const readonlyNames = readonlyList.value.map((u) => u.realname).join(',');
|
||||||
|
emit('update:editableUsers', editableList.value);
|
||||||
|
emit('update:readonlyUsers', readonlyList.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function customRow(record: UserItem) {
|
||||||
|
return {
|
||||||
|
onClick: () => {
|
||||||
|
if (excludedIdSet.value.has(record.id)) return;
|
||||||
|
if (otherPermissionIds.value.has(record.id)) {
|
||||||
|
onMoveUserPermission(record, activePermission.value);
|
||||||
|
} else if (currentPermissionRows.value.find((u) => u.id === record.id)) {
|
||||||
|
const list = activePermission.value === '2' ? editableList.value : readonlyList.value;
|
||||||
|
removeFromPermission(record.id, list);
|
||||||
|
emitUpdate();
|
||||||
|
} else {
|
||||||
|
const list = activePermission.value === '2' ? editableList.value : readonlyList.value;
|
||||||
|
addToPermission(record, list);
|
||||||
|
emitUpdate();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resetSearch() {
|
||||||
|
searchDepartValue.value = '';
|
||||||
|
queryParam.realname = '';
|
||||||
|
ipagination.current = 1;
|
||||||
|
const depId = props.departId;
|
||||||
|
if (depId && selectedDepIds.value.length === 0) {
|
||||||
|
selectedDepIds.value = [resolveDepartId(depId)];
|
||||||
|
}
|
||||||
|
syncFromProps();
|
||||||
|
await loadData(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
clearSelected,
|
||||||
|
resetSearch,
|
||||||
|
getResult: (): { editableIds: string; editableNames: string; readonlyIds: string; readonlyNames: string } => ({
|
||||||
|
editableIds: editableList.value.map((u) => u.id).join(','),
|
||||||
|
editableNames: editableList.value.map((u) => u.realname).join(','),
|
||||||
|
readonlyIds: readonlyList.value.map((u) => u.id).join(','),
|
||||||
|
readonlyNames: readonlyList.value.map((u) => u.realname).join(','),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.ant-table-tbody .ant-table-row td {
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small-space {
|
||||||
|
margin: 0 4px 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-edit {
|
||||||
|
color: #1677ff;
|
||||||
|
border-color: #1677ff;
|
||||||
|
background: #e6f4ff;
|
||||||
|
}
|
||||||
|
.btn-edit:hover {
|
||||||
|
color: #4096ff;
|
||||||
|
border-color: #4096ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-read {
|
||||||
|
color: #52c41a;
|
||||||
|
border-color: #52c41a;
|
||||||
|
background: #f6ffed;
|
||||||
|
}
|
||||||
|
.btn-read:hover {
|
||||||
|
color: #73d13d;
|
||||||
|
border-color: #73d13d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-menu-key {
|
||||||
|
color: #1677ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.check-icon {
|
||||||
|
margin-left: auto;
|
||||||
|
color: #1677ff;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
export interface UserItem {
|
||||||
|
id: string;
|
||||||
|
username: string;
|
||||||
|
realname: string;
|
||||||
|
orgCodeTxt?: string;
|
||||||
|
userSecurityLevel?: number;
|
||||||
|
userSecurityLevel_dictText?: string;
|
||||||
|
sex_dictText?: string;
|
||||||
|
sortno?: number;
|
||||||
|
departId?: string;
|
||||||
|
departName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CollabUserItem extends UserItem {
|
||||||
|
permission: '2' | '3';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DepartTreeNode {
|
||||||
|
id: string;
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
title: string;
|
||||||
|
isLeaf?: boolean;
|
||||||
|
departType?: string | number;
|
||||||
|
distributable?: number;
|
||||||
|
disableCheckbox?: boolean;
|
||||||
|
children?: DepartTreeNode[];
|
||||||
|
description?: string;
|
||||||
|
parentId?: string;
|
||||||
|
orgCode?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CollabConfirmResult {
|
||||||
|
editableIds: string;
|
||||||
|
editableNames: string;
|
||||||
|
readonlyIds: string;
|
||||||
|
readonlyNames: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteIfExist<T extends Record<string, any>>(array: T[], value: T, key: string): void {
|
||||||
|
const idx = array.findIndex((item) => item[key] === value[key]);
|
||||||
|
if (idx >= 0) {
|
||||||
|
array.splice(idx, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pushIfNotExist<T extends Record<string, any>>(array: T[], value: T, key: string): void {
|
||||||
|
const idx = array.findIndex((item) => item[key] === value[key]);
|
||||||
|
if (idx < 0) {
|
||||||
|
array.push(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function filterDepartTree<T extends Record<string, any>>(departTree: T[], searchValue: string): T[] {
|
||||||
|
let filtered: T[] = [];
|
||||||
|
departTree.forEach((item) => {
|
||||||
|
if (item.title && item.title.includes(searchValue)) {
|
||||||
|
filtered.push(Object.assign({}, item, { children: null, isLeaf: true }));
|
||||||
|
}
|
||||||
|
if (item.children) {
|
||||||
|
const result = filterDepartTree(item.children, searchValue);
|
||||||
|
if (result.length > 0) {
|
||||||
|
filtered = filtered.concat(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return filtered;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function recurTree<T extends Record<string, any>, R>(tree: T[], callback: (node: T) => R[]): R[] {
|
||||||
|
let res: R[] = [];
|
||||||
|
tree.forEach((item) => {
|
||||||
|
res = res.concat(callback(item));
|
||||||
|
if (item.children) {
|
||||||
|
res = res.concat(recurTree(item.children, callback));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
}
|
||||||
@@ -125,10 +125,11 @@ function openModal() {
|
|||||||
visible.value = true;
|
visible.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function open() {
|
async function open(valueStr?: string) {
|
||||||
if (props.disabled) return;
|
if (props.disabled) return;
|
||||||
if (props.value) {
|
const val = valueStr !== undefined ? valueStr : props.value;
|
||||||
await loadUsersByUsernames(props.value);
|
if (val) {
|
||||||
|
await loadUsersByUsernames(val);
|
||||||
} else {
|
} else {
|
||||||
innerSelectedUsers.value = [];
|
innerSelectedUsers.value = [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,116 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-modal
|
<SzCollabUserModal
|
||||||
v-model:open="visible"
|
ref="modalRef"
|
||||||
title="协作人管理"
|
hide-trigger
|
||||||
:footer="null"
|
title="选择协作人"
|
||||||
width="520px"
|
|
||||||
:bodyStyle="{ padding: '0' }"
|
|
||||||
:centered="true"
|
|
||||||
:destroyOnClose="true"
|
|
||||||
@cancel="onClose"
|
|
||||||
>
|
|
||||||
<div class="collab-modal">
|
|
||||||
<div class="collab-body">
|
|
||||||
<div class="member-list">
|
|
||||||
<div v-for="item in allMembers" :key="item.permissionId || item.userId" class="member-item" @mouseenter="hoverId = item.permissionId || item.userId" @mouseleave="hoverId = ''">
|
|
||||||
<div class="member-avatar" :style="{ background: getColor(item.username) }">
|
|
||||||
{{ (item.username || '?').charAt(0) }}
|
|
||||||
</div>
|
|
||||||
<div class="member-main">
|
|
||||||
<div class="member-name-row">
|
|
||||||
<span class="member-name">{{ item.username }}</span>
|
|
||||||
<span v-if="item.permission === '1'" class="role-badge is-owner">所有者</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="member-right">
|
|
||||||
<template v-if="item.permission === '1'">
|
|
||||||
<span class="role-label">所有者</span>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="isOwner && item.permissionId">
|
|
||||||
<a-dropdown :trigger="['click']" :overlayStyle="{ minWidth: '120px' }">
|
|
||||||
<span class="role-label clickable" @click.stop>
|
|
||||||
{{ item.permission === '2' ? '可编辑' : '可阅读' }}
|
|
||||||
<Icon icon="ant-design:down-outlined" style="font-size: 10px; margin-left: 2px" />
|
|
||||||
</span>
|
|
||||||
<template #overlay>
|
|
||||||
<a-menu @click="({ key }) => onPermissionChange(item.permissionId, key)">
|
|
||||||
<a-menu-item key="2" :class="{ 'active-key': item.permission === '2' }">
|
|
||||||
<span>可编辑</span>
|
|
||||||
<Icon v-if="item.permission === '2'" icon="ant-design:check-outlined" class="check-icon" />
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item key="3" :class="{ 'active-key': item.permission === '3' }">
|
|
||||||
<span>可阅读</span>
|
|
||||||
<Icon v-if="item.permission === '3'" icon="ant-design:check-outlined" class="check-icon" />
|
|
||||||
</a-menu-item>
|
|
||||||
</a-menu>
|
|
||||||
</template>
|
|
||||||
</a-dropdown>
|
|
||||||
<span v-show="hoverId === item.permissionId" class="remove-icon" @click="onRemove(item.permissionId)">
|
|
||||||
<Icon icon="ant-design:close-outlined" />
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<span class="role-label">{{ item.permission === '2' ? '可编辑' : '可阅读' }}</span>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="allMembers.length === 0" class="empty-state">
|
|
||||||
<Icon icon="ant-design:team-outlined" style="font-size: 36px; color: #d9d9d9; margin-bottom: 8px" />
|
|
||||||
<span>暂无协作人</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="showAddPanel && isOwner" class="add-panel">
|
|
||||||
<div class="add-panel-header">
|
|
||||||
<span>添加协作人</span>
|
|
||||||
<span class="add-panel-close" @click="showAddPanel = false">
|
|
||||||
<Icon icon="ant-design:close-outlined" />
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="add-panel-body">
|
|
||||||
<SzDeptUserModal
|
|
||||||
v-model:value="addForm.userIds"
|
|
||||||
valueKey="id"
|
|
||||||
:role="1"
|
:role="1"
|
||||||
:secLevel="secretLevel"
|
:sec-level="secretLevel"
|
||||||
|
:excluded-user-ids="ownerUserId"
|
||||||
|
@confirm="onConfirm"
|
||||||
/>
|
/>
|
||||||
<div class="add-panel-row">
|
|
||||||
<span class="add-panel-label">权限</span>
|
|
||||||
<a-radio-group v-model:value="addForm.permission" size="small">
|
|
||||||
<a-radio-button value="2">可编辑</a-radio-button>
|
|
||||||
<a-radio-button value="3">可阅读</a-radio-button>
|
|
||||||
</a-radio-group>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="add-panel-footer">
|
|
||||||
<a-button size="small" @click="showAddPanel = false">取消</a-button>
|
|
||||||
<a-button size="small" type="primary" :disabled="!addForm.userIds" @click="onConfirmAdd">
|
|
||||||
确认添加
|
|
||||||
</a-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="isOwner && !showAddPanel" class="collab-footer">
|
|
||||||
<a-button type="text" size="small" @click="showAddPanel = true">
|
|
||||||
<Icon icon="ant-design:user-add-outlined" style="margin-right: 4px" />
|
|
||||||
添加协作人
|
|
||||||
</a-button>
|
|
||||||
<a-button type="primary" size="small" @click="onDone">完成</a-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</a-modal>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, reactive, computed, nextTick } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { message } from 'ant-design-vue';
|
import SzCollabUserModal from '/@/components/semri/collabUserComponent/SzCollabUserModal.vue';
|
||||||
import Icon from '/@/components/Icon/index';
|
|
||||||
import SzDeptUserModal from '/@/components/semri/deptUserComponent/SzDeptUserModal.vue';
|
|
||||||
import { getCollaborators, addCollaborator, removeCollaborator, updateCollaboratorPermission } from '../TaskList.api';
|
import { getCollaborators, addCollaborator, removeCollaborator, updateCollaboratorPermission } from '../TaskList.api';
|
||||||
import { getAvatarColor } from '../types';
|
|
||||||
import { checkUserSecLevel } from '../utils/taskUtils';
|
|
||||||
import { useMessage } from '/@/hooks/web/useMessage';
|
import { useMessage } from '/@/hooks/web/useMessage';
|
||||||
|
import type { CollabConfirmResult } from '/@/components/semri/collabUserComponent/types';
|
||||||
|
|
||||||
const { createMessage } = useMessage();
|
const { createMessage } = useMessage();
|
||||||
|
|
||||||
@@ -121,419 +26,110 @@
|
|||||||
permission: string;
|
permission: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PendingAddItem {
|
|
||||||
userId: string;
|
|
||||||
username: string;
|
|
||||||
permission: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
taskListId: string;
|
taskListId: string;
|
||||||
isOwner: boolean;
|
|
||||||
secretLevel?: number;
|
secretLevel?: number;
|
||||||
secretText?: string;
|
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
changed: [collaboratorNames: string];
|
changed: [collaboratorNames: string];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const visible = ref(false);
|
const modalRef = ref<InstanceType<typeof SzCollabUserModal> | null>(null);
|
||||||
const collaborators = ref<CollaboratorItem[]>([]);
|
let cachedCollaborators: CollaboratorItem[] = [];
|
||||||
const hoverId = ref('');
|
const ownerUserId = ref('');
|
||||||
const showAddPanel = ref(false);
|
|
||||||
const addForm = reactive({
|
|
||||||
userIds: '',
|
|
||||||
permission: '2',
|
|
||||||
});
|
|
||||||
const pendingAddList = ref<PendingAddItem[]>([]);
|
|
||||||
const pendingRemoveIds = ref<Set<string>>(new Set());
|
|
||||||
const pendingPermissionMap = ref<Map<string, string>>(new Map());
|
|
||||||
|
|
||||||
const allMembers = computed(() => {
|
async function open(taskListId?: string) {
|
||||||
const pendingAsCollab: CollaboratorItem[] = pendingAddList.value.map((p) => ({
|
const id = taskListId || props.taskListId;
|
||||||
permissionId: '',
|
|
||||||
userId: p.userId,
|
|
||||||
username: p.username,
|
|
||||||
permission: p.permission,
|
|
||||||
}));
|
|
||||||
return [...collaborators.value, ...pendingAsCollab]
|
|
||||||
.filter((m) => (m.permission !== '1' ? !pendingRemoveIds.value.has(m.permissionId) : true))
|
|
||||||
.map((m) => {
|
|
||||||
if (m.permissionId && pendingPermissionMap.value.has(m.permissionId)) {
|
|
||||||
return { ...m, permission: pendingPermissionMap.value.get(m.permissionId)! };
|
|
||||||
}
|
|
||||||
return m;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const collaboratorNamesSnapshot = computed(() => {
|
// Show modal immediately with loading state
|
||||||
return allMembers.value
|
modalRef.value?.open();
|
||||||
.filter((m) => m.permission !== '1')
|
cachedCollaborators = [];
|
||||||
.map((m) => m.username)
|
ownerUserId.value = '';
|
||||||
.filter(Boolean)
|
|
||||||
.join(',');
|
|
||||||
});
|
|
||||||
|
|
||||||
function getColor(name: string): string {
|
|
||||||
return getAvatarColor(name || '');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function open() {
|
|
||||||
collaborators.value = [];
|
|
||||||
showAddPanel.value = false;
|
|
||||||
pendingAddList.value = [];
|
|
||||||
pendingRemoveIds.value.clear();
|
|
||||||
pendingPermissionMap.value.clear();
|
|
||||||
addForm.userIds = '';
|
|
||||||
addForm.permission = '2';
|
|
||||||
visible.value = true;
|
|
||||||
await nextTick();
|
|
||||||
await loadCollaborators();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onClose() {
|
|
||||||
visible.value = false;
|
|
||||||
showAddPanel.value = false;
|
|
||||||
addForm.userIds = '';
|
|
||||||
addForm.permission = '2';
|
|
||||||
pendingAddList.value = [];
|
|
||||||
pendingRemoveIds.value.clear();
|
|
||||||
pendingPermissionMap.value.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadCollaborators() {
|
|
||||||
try {
|
try {
|
||||||
const options = { successMessageMode: 'none' as const };
|
const options = { successMessageMode: 'none' as const };
|
||||||
const data: any[] = await getCollaborators({ taskListId: props.taskListId }, options);
|
const data: any[] = await getCollaborators({ taskListId: id }, options);
|
||||||
collaborators.value = data;
|
cachedCollaborators = data || [];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[CollaboratorModal] load failed', e);
|
console.error('[CollaboratorModal] load failed', e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const owner = cachedCollaborators.find((c) => c.permission === '1');
|
||||||
|
if (owner) ownerUserId.value = owner.userId;
|
||||||
|
|
||||||
|
const editIds = cachedCollaborators.filter((c) => c.permission === '2').map((c) => c.userId).join(',');
|
||||||
|
const readIds = cachedCollaborators.filter((c) => c.permission === '3').map((c) => c.userId).join(',');
|
||||||
|
modalRef.value?.open(editIds, readIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onConfirmAdd() {
|
function buildNamesFromResult(result: CollabConfirmResult): string {
|
||||||
if (!addForm.userIds) return;
|
const names: string[] = [];
|
||||||
const ids = addForm.userIds.split(',').filter(Boolean);
|
if (result.editableNames) names.push(...result.editableNames.split(',').filter(Boolean));
|
||||||
if (ids.length === 0) return;
|
if (result.readonlyNames) names.push(...result.readonlyNames.split(',').filter(Boolean));
|
||||||
|
return names.join(',');
|
||||||
const secretLevel = props.secretLevel || 1;
|
|
||||||
const { ok, invalidNames } = await checkUserSecLevel(addForm.userIds, secretLevel);
|
|
||||||
if (!ok) {
|
|
||||||
message.warning(`以下人员的密级不满足当前计划(${props.secretText || '非密'})要求:${invalidNames.join('、')}`);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const nameMap: Record<string, string> = {};
|
async function onConfirm(result: CollabConfirmResult) {
|
||||||
try {
|
|
||||||
const { getUserList } = await import('/@/api/common/api');
|
|
||||||
const res = await getUserList({ id: ids.join(','), pageNo: 1, pageSize: ids.length * 2 });
|
|
||||||
if (res.records && res.records.length > 0) {
|
|
||||||
res.records.forEach((u: any) => {
|
|
||||||
nameMap[u.id] = u.realname || u.username;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('获取用户名失败', e);
|
|
||||||
}
|
|
||||||
|
|
||||||
const existingIds = new Set(collaborators.value.map((c) => c.userId));
|
|
||||||
const pendingIds = new Set(pendingAddList.value.map((p) => p.userId));
|
|
||||||
|
|
||||||
for (const uid of ids) {
|
|
||||||
if (!existingIds.has(uid) && !pendingIds.has(uid)) {
|
|
||||||
pendingAddList.value.push({
|
|
||||||
userId: uid,
|
|
||||||
username: nameMap[uid] || uid,
|
|
||||||
permission: addForm.permission,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addForm.userIds = '';
|
|
||||||
addForm.permission = '2';
|
|
||||||
showAddPanel.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onDone() {
|
|
||||||
const hasChanges =
|
|
||||||
pendingAddList.value.length > 0 || pendingRemoveIds.value.size > 0 || pendingPermissionMap.value.size > 0;
|
|
||||||
|
|
||||||
const itemsToAdd = [...pendingAddList.value];
|
|
||||||
const idsToRemove = [...pendingRemoveIds.value];
|
|
||||||
const permsToChange = [...pendingPermissionMap.value];
|
|
||||||
const listId = props.taskListId;
|
|
||||||
|
|
||||||
pendingAddList.value = [];
|
|
||||||
pendingRemoveIds.value.clear();
|
|
||||||
pendingPermissionMap.value.clear();
|
|
||||||
|
|
||||||
visible.value = false;
|
|
||||||
showAddPanel.value = false;
|
|
||||||
addForm.userIds = '';
|
|
||||||
addForm.permission = '2';
|
|
||||||
|
|
||||||
if (!hasChanges) return;
|
|
||||||
|
|
||||||
const suppressOptions = { successMessageMode: 'none' as const };
|
const suppressOptions = { successMessageMode: 'none' as const };
|
||||||
|
const oldMap = new Map<string, { permissionId: string; permission: string }>();
|
||||||
|
|
||||||
|
for (const c of cachedCollaborators) {
|
||||||
|
if (c.permission !== '1') {
|
||||||
|
oldMap.set(c.userId, { permissionId: c.permissionId, permission: c.permission });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const newEditIds = new Set(result.editableIds ? result.editableIds.split(',').filter(Boolean) : []);
|
||||||
|
const newReadIds = new Set(result.readonlyIds ? result.readonlyIds.split(',').filter(Boolean) : []);
|
||||||
|
const allNewIds = new Set([...newEditIds, ...newReadIds]);
|
||||||
|
|
||||||
let errorMsg = '';
|
let errorMsg = '';
|
||||||
|
|
||||||
(async () => {
|
// 新增
|
||||||
for (const item of itemsToAdd) {
|
for (const uid of allNewIds) {
|
||||||
|
if (!oldMap.has(uid)) {
|
||||||
|
const perm = newEditIds.has(uid) ? '2' : '3';
|
||||||
try {
|
try {
|
||||||
await addCollaborator(
|
await addCollaborator({ taskListId: props.taskListId, userId: uid, permission: perm }, suppressOptions);
|
||||||
{ taskListId: listId, userId: item.userId, permission: item.permission },
|
|
||||||
suppressOptions,
|
|
||||||
);
|
|
||||||
} catch {
|
} catch {
|
||||||
errorMsg = '部分协作人添加失败';
|
errorMsg = '部分协作人添加失败';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const permissionId of idsToRemove) {
|
// 移除
|
||||||
|
for (const [uid, old] of oldMap) {
|
||||||
|
if (!allNewIds.has(uid)) {
|
||||||
try {
|
try {
|
||||||
await removeCollaborator({ permissionId }, suppressOptions);
|
await removeCollaborator({ permissionId: old.permissionId }, suppressOptions);
|
||||||
} catch {
|
} catch {
|
||||||
errorMsg = '部分协作人移除失败';
|
errorMsg = '部分协作人移除失败';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const [permissionId, newPermission] of permsToChange) {
|
// 权限变更
|
||||||
|
for (const [uid, old] of oldMap) {
|
||||||
|
const newPerm = newEditIds.has(uid) ? '2' : newReadIds.has(uid) ? '3' : null;
|
||||||
|
if (newPerm && newPerm !== old.permission && old.permissionId) {
|
||||||
try {
|
try {
|
||||||
await updateCollaboratorPermission({ permissionId, permission: newPermission }, suppressOptions);
|
await updateCollaboratorPermission({ permissionId: old.permissionId, permission: newPerm }, suppressOptions);
|
||||||
} catch {
|
} catch {
|
||||||
errorMsg = '部分权限修改失败';
|
errorMsg = '部分权限修改失败';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await loadCollaborators();
|
const names = buildNamesFromResult(result);
|
||||||
emit('changed', collaboratorNamesSnapshot.value);
|
emit('changed', names);
|
||||||
|
|
||||||
if (errorMsg) {
|
if (errorMsg) {
|
||||||
createMessage.warning(errorMsg);
|
createMessage.warning(errorMsg);
|
||||||
} else {
|
} else {
|
||||||
createMessage.success('编辑成功');
|
createMessage.success('更新成功');
|
||||||
}
|
}
|
||||||
})();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onRemove(permissionId: string) {
|
|
||||||
pendingRemoveIds.value.add(permissionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPermissionChange(permissionId: string, newPermission: string) {
|
|
||||||
pendingPermissionMap.value.set(permissionId, newPermission);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({ open });
|
defineExpose({ open });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
|
||||||
.collab-modal {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.collab-body {
|
|
||||||
position: relative;
|
|
||||||
min-height: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.member-list {
|
|
||||||
padding: 8px 0;
|
|
||||||
max-height: 400px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.member-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
padding: 8px 20px;
|
|
||||||
transition: background 0.12s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #f5f6f7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.member-avatar {
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
border-radius: 50%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-shrink: 0;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.member-main {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.member-name-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.member-name {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #1f2329;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.role-badge {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0 6px;
|
|
||||||
font-size: 11px;
|
|
||||||
line-height: 18px;
|
|
||||||
border-radius: 3px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
&.is-owner {
|
|
||||||
background: #e8f3ff;
|
|
||||||
color: #3370ff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.member-right {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
min-width: 70px;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.role-label {
|
|
||||||
font-size: 13px;
|
|
||||||
color: #8f959e;
|
|
||||||
|
|
||||||
&.clickable {
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 2px 6px;
|
|
||||||
border-radius: 4px;
|
|
||||||
transition: background 0.12s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #e8e8e8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.remove-icon {
|
|
||||||
width: 22px;
|
|
||||||
height: 22px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 50%;
|
|
||||||
cursor: pointer;
|
|
||||||
color: #8f959e;
|
|
||||||
font-size: 12px;
|
|
||||||
transition: all 0.12s;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #f0f0f0;
|
|
||||||
color: #f5222d;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.check-icon {
|
|
||||||
margin-left: auto;
|
|
||||||
color: #3370ff;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.active-key {
|
|
||||||
color: #3370ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-state {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 40px 0;
|
|
||||||
color: #bfbfbf;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-panel {
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
background: #fff;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-panel-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 16px 20px 12px;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #1f2329;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-panel-close {
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
color: #8f959e;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #f0f0f0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-panel-body {
|
|
||||||
padding: 0 20px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-panel-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-panel-label {
|
|
||||||
font-size: 13px;
|
|
||||||
color: #595959;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-panel-footer {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 16px 20px;
|
|
||||||
margin-top: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.collab-footer {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 10px 20px;
|
|
||||||
border-top: 1px solid #f0f0f0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1362,19 +1362,20 @@
|
|||||||
userSelectModalTaskId.value = taskId;
|
userSelectModalTaskId.value = taskId;
|
||||||
|
|
||||||
const task = findTaskById(taskId);
|
const task = findTaskById(taskId);
|
||||||
|
let ids = '';
|
||||||
if (type === 'assignee') {
|
if (type === 'assignee') {
|
||||||
userSelectModalCurrentIds.value = task?.assigneeId || '';
|
ids = task?.assigneeId || '';
|
||||||
userSelectModalCurrentNames.value = task?.assigneeName || '';
|
userSelectModalCurrentNames.value = task?.assigneeName || '';
|
||||||
} else if (type === 'participant') {
|
} else if (type === 'participant') {
|
||||||
userSelectModalCurrentIds.value = task?.participantId || '';
|
ids = task?.participantId || '';
|
||||||
userSelectModalCurrentNames.value = task?.participantName || '';
|
userSelectModalCurrentNames.value = task?.participantName || '';
|
||||||
} else {
|
} else {
|
||||||
userSelectModalCurrentIds.value = task?.followersId || '';
|
ids = task?.followersId || '';
|
||||||
userSelectModalCurrentNames.value = task?.followersName || '';
|
userSelectModalCurrentNames.value = task?.followersName || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
userSelectModalRef.value?.open();
|
userSelectModalRef.value?.open(ids);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1911,6 +1912,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-header-row {
|
.content-header-row {
|
||||||
@@ -1924,6 +1926,8 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-list-btn {
|
.add-list-btn {
|
||||||
@@ -1952,6 +1956,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-secret-badge {
|
.header-secret-badge {
|
||||||
@@ -1972,6 +1977,7 @@
|
|||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
border: 1px solid #e8e8e8;
|
border: 1px solid #e8e8e8;
|
||||||
transition: all 0.15s;
|
transition: all 0.15s;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border-color: #d0d0d0;
|
border-color: #d0d0d0;
|
||||||
@@ -2001,6 +2007,9 @@
|
|||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #1f1f1f;
|
color: #1f1f1f;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-more-btn {
|
.header-more-btn {
|
||||||
@@ -2017,6 +2026,8 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0;
|
gap: 0;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-header-rename-row {
|
.content-header-rename-row {
|
||||||
|
|||||||
@@ -668,19 +668,20 @@
|
|||||||
userSelectModalTaskId.value = taskId;
|
userSelectModalTaskId.value = taskId;
|
||||||
|
|
||||||
const task = props.tasks.find((t) => t.id === taskId);
|
const task = props.tasks.find((t) => t.id === taskId);
|
||||||
|
let ids = '';
|
||||||
if (type === 'assignee') {
|
if (type === 'assignee') {
|
||||||
userSelectModalCurrentIds.value = task?.assigneeId || '';
|
ids = task?.assigneeId || '';
|
||||||
userSelectModalCurrentNames.value = task?.assigneeName || '';
|
userSelectModalCurrentNames.value = task?.assigneeName || '';
|
||||||
} else if (type === 'participant') {
|
} else if (type === 'participant') {
|
||||||
userSelectModalCurrentIds.value = task?.participantId || '';
|
ids = task?.participantId || '';
|
||||||
userSelectModalCurrentNames.value = task?.participantName || '';
|
userSelectModalCurrentNames.value = task?.participantName || '';
|
||||||
} else {
|
} else {
|
||||||
userSelectModalCurrentIds.value = task?.followersId || '';
|
ids = task?.followersId || '';
|
||||||
userSelectModalCurrentNames.value = task?.followersName || '';
|
userSelectModalCurrentNames.value = task?.followersName || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
userSelectModalRef.value?.open();
|
userSelectModalRef.value?.open(ids);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -94,7 +94,7 @@
|
|||||||
@unfollow-task="onUnfollowTask"
|
@unfollow-task="onUnfollowTask"
|
||||||
@move-task="onMoveTask"
|
@move-task="onMoveTask"
|
||||||
/>
|
/>
|
||||||
<CollaboratorModal ref="collaboratorModalRef" :task-list-id="collaboratorTargetListId || currentListId" :is-owner="collaboratorTargetListId ? isOwnerOfTargetList : isCurrentOwner" :secret-level="collaboratorTargetList ? collaboratorTargetList.secretLevel || 1 : currentList?.secretLevel || 1" :secret-text="collaboratorTargetList ? collaboratorTargetList.secretText || '非密' : currentList?.secretText || '非密'" @changed="onCollaboratorsChanged" />
|
<CollaboratorModal ref="collaboratorModalRef" :task-list-id="collaboratorTargetListId || currentListId" :secret-level="collaboratorTargetList ? collaboratorTargetList.secretLevel || 1 : currentList?.secretLevel || 1" @changed="onCollaboratorsChanged" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -300,8 +300,6 @@
|
|||||||
return findListInGroups(taskListGroups.value, currentListId.value) || findListInGroups(quickAccessGroups.value, currentListId.value);
|
return findListInGroups(taskListGroups.value, currentListId.value) || findListInGroups(quickAccessGroups.value, currentListId.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
const isCurrentOwner = computed(() => currentList.value?.myPermission === '1');
|
|
||||||
|
|
||||||
async function loadPermissionForList(listId: string) {
|
async function loadPermissionForList(listId: string) {
|
||||||
const silentOpts = { successMessageMode: 'none' as const };
|
const silentOpts = { successMessageMode: 'none' as const };
|
||||||
try {
|
try {
|
||||||
@@ -497,7 +495,7 @@
|
|||||||
|
|
||||||
function onOpenCollaborator() {
|
function onOpenCollaborator() {
|
||||||
collaboratorTargetListId.value = '';
|
collaboratorTargetListId.value = '';
|
||||||
collaboratorModalRef.value?.open();
|
collaboratorModalRef.value?.open(currentListId.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
const collaboratorTargetList = computed(() => {
|
const collaboratorTargetList = computed(() => {
|
||||||
@@ -510,10 +508,6 @@
|
|||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
const isOwnerOfTargetList = computed(() => {
|
|
||||||
return collaboratorTargetList.value?.myPermission === '1';
|
|
||||||
});
|
|
||||||
|
|
||||||
function onOpenCollaboratorForList(listId: string) {
|
function onOpenCollaboratorForList(listId: string) {
|
||||||
const list = findListInQuickAccess(listId);
|
const list = findListInQuickAccess(listId);
|
||||||
if (list && list.myPermission !== '1') {
|
if (list && list.myPermission !== '1') {
|
||||||
@@ -521,7 +515,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
collaboratorTargetListId.value = listId;
|
collaboratorTargetListId.value = listId;
|
||||||
collaboratorModalRef.value?.open();
|
collaboratorModalRef.value?.open(listId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function findListInQuickAccess(listId: string): TaskList | null {
|
function findListInQuickAccess(listId: string): TaskList | null {
|
||||||
|
|||||||
Reference in New Issue
Block a user