feat(account-manage): 添加表格视图并优化现有组件样式和逻辑

This commit is contained in:
rd
2025-09-19 17:31:07 +08:00
parent 7846e02447
commit a4ce67cd18
5 changed files with 401 additions and 123 deletions

View File

@ -3,7 +3,8 @@
* @Date: 2025-06-25 15:31:15
-->
<template>
<div class="card-container">
<NoData v-if="!dataSource.length" />
<div v-else class="card-container">
<Spin
v-for="(item, index) in dataSource"
:key="index"
@ -14,7 +15,12 @@
<template #icon>
<icon-sync size="24" />
</template>
<Checkbox :checked="isSelected(item)" :value="item.id" @change="toggleSelect(item)" class="relative top--2px"></Checkbox>
<Checkbox
:checked="isSelected(item)"
:value="item.id"
class="relative top--2px"
@change="toggleSelect(item)"
></Checkbox>
<div class="ml-8px flex-1">
<Tooltip title="点击查看账号详情">
<p class="name cursor-pointer hover:!color-#6d4cfe" @click="goDetail(item)">{{ item.name || '-' }}</p>
@ -47,30 +53,30 @@
<span class="label">运营人员</span>
<span class="cts">{{ item.operator?.name || '-' }}</span>
</div>
<div class="field-row">
<span class="label">所属项目</span>
<span v-if="!item.projects.length" class="cts">-</span>
<div v-else class="flex items-center">
<Tooltip
v-if="item.projects.length > 2"
placement="bottom"
:title="
item.projects
.slice(2)
.map((v) => v.name)
.join(',')
"
>
<div class="tag-box">
<span class="text">{{ `+${item.projects.length - 2}` }}</span>
</div>
</Tooltip>
<!-- <div class="field-row">-->
<!-- <span class="label">所属项目</span>-->
<!-- <span v-if="!item.projects.length" class="cts">-</span>-->
<!-- <div v-else class="flex items-center">-->
<!-- <Tooltip-->
<!-- v-if="item.projects.length > 2"-->
<!-- placement="bottom"-->
<!-- :title="-->
<!-- item.projects-->
<!-- .slice(2)-->
<!-- .map((v) => v.name)-->
<!-- .join(',')-->
<!-- "-->
<!-- >-->
<!-- <div class="tag-box">-->
<!-- <span class="text">{{ `+${item.projects.length - 2}` }}</span>-->
<!-- </div>-->
<!-- </Tooltip>-->
<div v-for="(project, index) in item.projects.slice(0, 2)" :key="index" class="tag-box">
<span class="text">{{ project.name }}</span>
</div>
</div>
</div>
<!-- <div v-for="(project, index) in item.projects.slice(0, 2)" :key="index" class="tag-box">-->
<!-- <span class="text">{{ project.name }}</span>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<div class="field-row">
<span class="label">分组</span>
<span class="cts">{{ item.group?.name || '-' }}</span>
@ -123,14 +129,11 @@
</div>
</div>
</Spin>
<PauseAccountPatchModal ref="pauseAccountPatchModalRef" @success="emits('update')" />
<ReauthorizeAccountModal ref="reauthorizeAccountModalRef" @update="emits('update')" />
<AuthorizedAccountModal ref="authorizedAccountModalRef" @update="emits('update')" />
</div>
</template>
<script setup>
import { defineProps, ref, computed, inject } from 'vue';
import { defineProps, inject } from 'vue';
import { Checkbox, Button, Tooltip, Spin } from 'ant-design-vue';
import { useRouter } from 'vue-router';
import { deleteSyncStatus } from '@/api/all/propertyMarketing';
@ -141,10 +144,7 @@ import {
EnumStatus,
} from '@/views/property-marketing/media-account/components/status-select/status-box';
import PauseAccountPatchModal from './pause-account-patch';
import StatusBox from '@/views/property-marketing/media-account/components/status-select/status-box.tsx';
import ReauthorizeAccountModal from '../reauthorize-account-modal';
import AuthorizedAccountModal from '../authorized-account-modal';
import FooterBtn from './footer-btn';
import icon1 from '@/assets/img/platform/icon-dy.png';
@ -160,7 +160,7 @@ const props = defineProps({
type: Array,
default: () => [],
},
selectedItems: {
selectedRows: {
type: Array,
default: () => [],
},
@ -170,28 +170,26 @@ const props = defineProps({
},
});
const emits = defineEmits(['openEdit', 'update', 'selectionChange', 'delete', 'updateSyncStatus']);
const emits = defineEmits([
'openEdit',
'update',
'delete',
'updateSyncStatus',
'pause',
'reauthorize',
'select',
'selectAll',
]);
const syncData = inject('handleSyncData');
const router = useRouter();
const pauseAccountPatchModalRef = ref(null);
const reauthorizeAccountModalRef = ref(null);
const authorizedAccountModalRef = ref(null);
// 判断当前 item 是否被选中
const isSelected = (item) => {
return props.selectedItems.some((i) => i.id === item.id);
return props.selectedRows.some((i) => i.id === item.id);
};
const toggleSelect = (item) => {
let newSelected;
if (isSelected(item)) {
newSelected = props.selectedItems.filter((i) => i.id !== item.id);
} else {
newSelected = [...props.selectedItems, item];
}
emits('selectionChange', newSelected);
emits('select', item, !isSelected(item));
};
const openEdit = (item) => {
@ -203,21 +201,11 @@ const openDelete = (item) => {
};
const handleReauthorize = (item) => {
const { id, platform, error_status } = item;
const isUnauthorized = isUnauthorizedStatus(error_status);
if (isUnauthorized) {
authorizedAccountModalRef.value?.open(id, platform);
} else {
reauthorizeAccountModalRef.value?.open(id, platform);
}
emits('reauthorize', item);
};
const handlePause = (item) => {
pauseAccountPatchModalRef.value?.open(item);
};
const isUnauthorizedStatus = (error_status) => {
return [EnumErrorStatus.UNAUTHORIZED].includes(error_status);
emits('pause', item);
};
const goDetail = (item) => {
@ -269,6 +257,7 @@ const handleConfirm = (item) => {
syncData(item);
}
if (error_status === EnumErrorStatus.LOGIN) {
emits('');
handleReauthorize(item);
}
};

View File

@ -3,9 +3,8 @@ import {
EnumErrorStatus,
getStatusInfo,
} from '@/views/property-marketing/media-account/components/status-select/status-box';
import { Dropdown, Menu } from 'ant-design-vue';
import { Dropdown, Menu, Tooltip, Button } from 'ant-design-vue';
const { Item: MenuItem } = Menu;
import { Tooltip, Button } from 'ant-design-vue';
export default defineComponent({
name: 'FooterBtn',
props: {
@ -44,7 +43,7 @@ export default defineComponent({
};
const renderUpdateBtn = () => {
return (
<Button type="primary" ghost size="small" onClick={() => emit('syncData', props.item)}>
<Button type="primary" class="!h-24px !px-12px" ghost size="small" onClick={() => emit('syncData', props.item)}>
</Button>
);
@ -63,7 +62,7 @@ export default defineComponent({
trigger="hover"
v-slots={{
default: () => (
<Button type="primary" ghost class="mr-8px" size="small">
<Button type="primary" ghost class="!h-24px !px-12px mr-8px" size="small">
</Button>
),
@ -88,7 +87,7 @@ export default defineComponent({
trigger="hover"
v-slots={{
default: () => (
<Button type="primary" ghost class="mr-8px" size="small">
<Button type="primary" ghost class="mr-8px !h-24px !px-12px" size="small">
</Button>
),
@ -100,7 +99,13 @@ export default defineComponent({
),
}}
></Dropdown>
<Button type="primary" ghost size="small" onClick={() => emit('handleReauthorize', props.item)}>
<Button
type="primary"
ghost
size="small"
class="!h-24px !px-12px"
onClick={() => emit('handleReauthorize', props.item)}
>
</Button>
</>
@ -118,14 +123,20 @@ export default defineComponent({
} else if ([EnumErrorStatus.REQUEST, EnumErrorStatus.FREEZE].includes(error_status)) {
return (
<Tooltip title={statusInfo.value.disabledBtnTooltip}>
<Button type="primary" ghost size="small" disabled>
<Button type="primary" ghost size="small" disabled class="!h-24px !px-12px">
</Button>
</Tooltip>
);
} else {
return (
<Button type="primary" ghost size="small" onClick={() => emit('handleReauthorize', props.item)}>
<Button
type="primary"
ghost
class="!h-24px !px-12px"
size="small"
onClick={() => emit('handleReauthorize', props.item)}
>
{isUnauthorized ? '去授权' : '重新授权'}
</Button>
);
@ -137,7 +148,7 @@ export default defineComponent({
trigger="hover"
v-slots={{
default: () => (
<Button type="primary" ghost class="mr-8px" size="small">
<Button type="primary" ghost size="small" class="mr-8px !h-24px !px-12px">
</Button>
),

View File

@ -0,0 +1,185 @@
<template>
<Table
ref="tableRef"
:dataSource="dataSource"
:pagination="false"
:rowSelection="rowSelection"
:scroll="{ x: '100%' }"
:showSorterTooltip="false"
bordered
class="flex-1 manuscript-table w-100%"
rowKey="id"
>
<template #emptyText>
<NoData text="暂无文件" />
</template>
<Column
v-for="column in tableColumns"
:key="column.dataIndex"
:align="column.align"
:dataIndex="column.dataIndex"
:ellipsis="true"
:fixed="column.fixed"
:minWidth="column.minWidth"
:sorter="column.sortable"
:width="column.width"
>
<template #title>
<span class="cts mr-4px">{{ column.title }}</span>
<Tooltip v-if="column.tooltip" :title="column.tooltip" placement="top">
<icon-question-circle class="tooltip-icon color-#737478" size="16" />
</Tooltip>
</template>
<template v-if="column.dataIndex === 'name'" #customRender="{ record }">
<TextOverTips
:context="record.name || '-'"
:line="1"
class="name cursor-pointer hover:!color-#6d4cfe"
@click="goDetail(record)"
/>
</template>
<template v-else-if="column.dataIndex === 'tags'" #customRender="{ record }">
<div v-if="record.tags.length > 0" class="flex flex-wrap gap-4px">
<Tag v-for="tag in record.tags" :key="tag.id" class="mr-0 rounded-4px bg-#F2F3F5 px-8px">
<Tooltip v-if="tag.name.length > 5" :title="tag.name">
<span class="cts !color-#55585F !lh-24px !text-14px"> {{ `${tag.name.slice(0, 5)}...` }} </span>
</Tooltip>
<span v-else class="cts !color-#55585F !lh-24px !text-14px"> {{ tag.name }} </span>
</Tag>
</div>
<template v-else> -</template>
</template>
<template v-else-if="column.dataIndex === 'status'" #customRender="{ record }">
<StatusBox :item="record" />
</template>
<template v-else-if="column.dataIndex === 'platform'" #customRender="{ record }">
<img :src="record.platform === 0 ? icon2 : icon3" height="16" width="16" />
</template>
<template v-else-if="column.dataIndex === 'last_synced_at'" #customRender="{ record }">
<span class="cts num">{{ getLastSyncedAt(record) }}</span>
</template>
<template v-else-if="column.dataIndex === 'last_authorized_at'" #customRender="{ record }">
<span class="cts num">{{ formatTime(record.last_authorized_at) }}</span>
</template>
<template v-else-if="column.dataIndex === 'operation'" #customRender="{ record }">
<div class="flex items-center">
<FooterBtn
:item="record"
@handlePause="handlePause"
@handleReauthorize="handleReauthorize"
@openDelete="openDelete"
@openEdit="openEdit"
@syncData="syncData"
/>
</div>
</template>
<template v-else #customRender="{ record }">
{{ formatTableField(column, record, true) }}
</template>
</Column>
</Table>
</template>
<script setup>
import { ref, computed, inject } from 'vue';
import { Tooltip, Table, Tag } from 'ant-design-vue';
import StatusBox from '@/views/property-marketing/media-account/components/status-select/status-box.tsx';
const { Column } = Table;
import { formatTableField, exactFormatTime } from '@/utils/tools';
import TextOverTips from '@/components/text-over-tips';
import FooterBtn from './footer-btn';
// import icon1 from '@/assets/img/media-account/icon-delete.png';
import icon2 from '@/assets/img/platform/icon-dy.png';
import icon3 from '@/assets/img/platform/icon-xhs.png';
const emits = defineEmits([
'openEdit',
'update',
'selectionChange',
'delete',
'updateSyncStatus',
'pause',
'reauthorize',
'select',
'selectAll',
]);
const syncData = inject('handleSyncData');
const router = useRouter();
const props = defineProps({
dataSource: {
type: Array,
default: () => [],
},
tableColumns: {
type: Array,
default: () => [],
},
selectedRowKeys: {
type: Array,
default: () => [],
},
syncMediaAccounts: {
type: Array,
default: () => [],
},
isLoadingTaskStatus: {
type: Boolean,
default: () => false,
},
});
const tableRef = ref(null);
const goDetail = (item) => {
router.push(`/media-account/detail/${item.id}`);
};
const handlePause = (item) => {
emits('pause', item);
};
const handleReauthorize = (item) => {
emits('reauthorize', item);
};
const openEdit = (item) => {
emits('openEdit', item);
};
const openDelete = (item) => {
emits('delete', item);
};
const formatTime = (time) => {
return exactFormatTime(time, 'YYYY-MM-DD HH:mm:ss', 'YYYY-MM-DD HH:mm:ss');
};
const getSyncMediaAccount = (item) => {
return props.syncMediaAccounts.find((v) => v.id === item.id);
};
const getLastSyncedAt = (item) => {
const target = getSyncMediaAccount(item);
if (props.isLoadingTaskStatus && target) {
if (target?.status !== 0) {
return formatTime(target.last_synced_at);
}
}
return formatTime(item.last_synced_at);
};
const rowSelection = {
selectedRowKeys: computed(() => props.selectedRowKeys),
onSelect: (record, selected) => {
emits('select', record, selected);
},
onSelectAll: (selected) => {
emits('selectAll', selected);
},
};
</script>
<style lang="scss" scoped>
@import './style.scss';
</style>