feat(account-manage): 添加表格视图并优化现有组件样式和逻辑
This commit is contained in:
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
@ -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>
|
||||
),
|
||||
|
||||
@ -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>
|
||||
Reference in New Issue
Block a user