Merge remote-tracking branch 'origin/main' into feature/v1.2灵机空间-项目管理_rxd

# Conflicts:
#	src/api/all/common.ts
#	src/components/_base/navbar/index.vue
#	src/hooks/useTableSelectionWithPagination.ts
#	src/router/routes/modules/propertyMarketing.ts
#	src/stores/modules/side-bar/constants.ts
#	src/views/property-marketing/media-account/account-manage/components/account-table/index.vue
#	src/views/property-marketing/media-account/account-manage/components/add-account-modal/index.vue
#	src/views/property-marketing/put-account/account-data/components/filter-block/index.vue
#	src/views/property-marketing/put-account/account-manage/components/account-table/index.vue
#	src/views/property-marketing/put-account/account-manage/components/add-account-modal/index.vue
#	src/views/property-marketing/put-account/account-manage/components/filter-block/index.vue
#	src/views/property-marketing/put-account/components/status-select/constants.ts
This commit is contained in:
rd
2025-07-23 15:22:46 +08:00
66 changed files with 2144 additions and 673 deletions

View File

@ -135,7 +135,7 @@
<script setup>
import { ref, computed } from 'vue';
import { STATUS_LIST } from '@/views/property-marketing/put-account/components/status-select/constants';
import { STATUS_LIST } from '@/views/property-marketing/media-account/components/status-select/constants';
import { formatTableField, exactFormatTime } from '@/utils/tools';
import { TABLE_COLUMNS } from './constants';
import { useRouter } from 'vue-router';

View File

@ -80,7 +80,7 @@ import {
} from '@/api/all/propertyMarketing';
import CommonSelect from '@/components/common-select';
import StatusSelect from '@/views/property-marketing/put-account/components/status-select';
import StatusSelect from '@/views/property-marketing/media-account/components/status-select';
import AccountSelect from '@/views/property-marketing/put-account/components/account-select';
const props = defineProps({

View File

@ -133,7 +133,7 @@
<script setup>
import { ref, computed } from 'vue';
import { STATUS_LIST } from '@/views/property-marketing/put-account/components/status-select/constants';
import { STATUS_LIST } from '@/views/property-marketing/media-account/components/status-select/constants';
import { formatTableField } from '@/utils/tools';
import { TABLE_COLUMNS } from './constants';
import { useRouter } from 'vue-router';

View File

@ -68,8 +68,9 @@ import {
postPlacementAccountDataListExport,
} from '@/api/all/propertyMarketing';
import { showExportNotification } from '@/utils/arcoD';
import { INITIAL_QUERY, INITIAL_PAGE_INFO } from './constants';
import { downloadByUrl } from '@/utils/tools';
// import { downloadByUrl } from '@/utils/tools';
import icon2 from '@/assets/img/media-account/icon-group.png';
@ -151,7 +152,7 @@ const handleExport = () => {
}).then((res) => {
const { code, data } = res;
if (code === 200) {
downloadByUrl(data.download_url);
showExportNotification(`正在下载“${data.name}”,请稍后...`);
}
});
};

View File

@ -102,7 +102,7 @@
<script setup>
import { defineProps, ref, computed } from 'vue';
import { PLATFORM_LIST } from '@/utils/platform';
import { EnumStatus } from '@/views/property-marketing/put-account/components/status-select/constants';
import { EnumStatus } from '@/views/property-marketing/media-account/components/status-select/constants';
import { formatNumberShow, exactFormatTime } from '@/utils/tools';

View File

@ -129,13 +129,13 @@
<icon-question-circle size="14" class="ml-4px color-#737478" />
</a-tooltip>
</template>
<a-switch v-model="form.is_sync_project" size="medium" :checked-value="1" :un-checked-value="0" />
<a-switch v-model="form.is_sync_project" size="medium" :checked-value="1" :unchecked-value="0" />
</a-form-item>
</template>
</a-form>
<template #footer>
<a-button size="large" class="cancel-btn" @click="onClose">取消</a-button>
<a-button type="primary" size="large" @click="onSubmit">
<a-button type="primary" size="large" @click="onSubmit" :loading="importLoading">
{{ confirmBtnText }}
</a-button>
</template>
@ -155,6 +155,8 @@ import CommonSelect from '@/components/common-select';
import { PLATFORM_LIST, ENUM_PUT_ACCOUNT_PLATFORM } from '@/utils/platform';
import { showExportNotification } from '@/utils/arcoD';
import { genRandomId } from '@/utils/tools';
import {
postPlacementAccounts,
getPlacementAccountsDetail,
@ -168,6 +170,7 @@ import icon1 from '@/assets/img/media-account/icon-download.png';
import icon2 from '@/assets/img/media-account/icon-delete.png';
const update = inject('update');
const emits = defineEmits(['startQueryTaskStatus']);
const UploadStatus = {
DEFAULT: 'default',
@ -196,6 +199,7 @@ const uploadRef = ref(null);
const file = ref(null);
const form = ref(cloneDeep(INITIAL_FORM));
const projects = ref([]);
const importLoading = ref(false);
const rules = {
mobile: [
@ -221,7 +225,7 @@ const rules = {
const isBatchImport = computed(() => uploadType.value === 'batch');
const confirmBtnText = computed(() => {
if (isBatchImport.value) return '确定导入';
if (isBatchImport.value) return importLoading.value ? '导入中' : '确定导入';
return isEdit.value ? '确定' : '下一步';
});
@ -237,6 +241,7 @@ const handleUpload = async (option) => {
function removeFile() {
fileName.value = '';
file.value = null;
importLoading.value = false;
uploadStatus.value = UploadStatus.DEFAULT;
}
@ -249,6 +254,7 @@ const reset = () => {
file.value = null;
isEdit.value = false;
projects.value = [];
importLoading.value = false;
uploadStatus.value = UploadStatus.DEFAULT;
uploadType.value = 'manual';
};
@ -284,23 +290,33 @@ const getAccountDetail = async () => {
const handleBatchImport = async () => {
try {
if (!file.value) {
AMessage.warning('请上传要导入的文件');
return;
}
importLoading.value = true;
const formData = new FormData();
formData.append('file', file.value);
const { code } = await batchPlacementAccounts(formData, {
const { code, data } = await batchPlacementAccounts(formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
if (code === 200) {
AMessage.success('导入成功');
update();
const id = genRandomId();
showExportNotification(`正在导入“${file.value.name}”,请稍后...`, { id });
emit('startQueryTaskStatus', data.id, id);
onClose();
} else {
uploadStatus.value = UploadStatus.ERROR;
}
} catch (error) {
uploadStatus.value = UploadStatus.ERROR;
} finally {
importLoading.value = false;
}
// importPromptModalRef.value.open();

View File

@ -12,7 +12,7 @@
:footer="!isLoading"
@close="close"
>
<div v-if="showSyncTip">
<!-- <div v-if="showSyncTip">
<div class="flex items-center mb-20px">
<img :src="icon1" width="16" height="16" class="mr-16px" />
<p class="s2">
@ -25,8 +25,8 @@
<a-radio value="sync" class="mb-16px">立即同步遗漏数据 - 获取完整的最新数据 推荐</a-radio>
<a-radio value="no_sync">仅授权不更新 - 继续使用当前不完全的数据</a-radio>
</a-radio-group>
</div>
<div v-else class="flex flex-col items-center">
</div> -->
<div class="flex flex-col items-center">
<template v-if="isLoading">
<a-progress
:percent="progress"
@ -80,7 +80,7 @@ import icon2 from '@/assets/img/media-account/icon-feedback-success.png';
import icon3 from '@/assets/img/media-account/icon-feedback-fail.png';
const update = inject('update');
const INITIAL_SYNC_TYPE = 'sync';
// const INITIAL_SYNC_TYPE = 'sync';
const visible = ref(false);
const isLoading = ref(false);
const isCompleted = ref(false);
@ -91,10 +91,10 @@ const progress = ref(0);
const id = ref('');
const selectSubAccountModalRef = ref(null);
const lastSyncedAt = ref(null); // 上次同步时间戳
const showSyncTip = ref(false);
// const lastSyncedAt = ref(null); // 上次同步时间戳
// const showSyncTip = ref(false);
const shouldSelectSubAccount = ref(false);
const syncType = ref(INITIAL_SYNC_TYPE); // sync no_sync
// const syncType = ref(INITIAL_SYNC_TYPE); // sync no_sync
const addAccountFormData = ref(null); // 添加账户表单数据
const INITIAL_FORM = {
@ -123,20 +123,20 @@ const confirmBtnText = computed(() => {
return isSuccess.value ? '继续添加' : '重试';
});
const getDaysDiffText = (lastSyncedAt) => {
if (!lastSyncedAt) return '0天';
// const getDaysDiffText = (lastSyncedAt) => {
// if (!lastSyncedAt) return '0天';
const daysDiff = dayjs().diff(dayjs(lastSyncedAt * 1000), 'day');
// const daysDiff = dayjs().diff(dayjs(lastSyncedAt * 1000), 'day');
if (daysDiff === 0) return '不到1天';
return `${daysDiff}`;
};
// if (daysDiff === 0) return '不到1天';
// return `${daysDiff}天`;
// };
const open = ({ accountId, last_synced_at = null, form = null, needSelectSubAccount = false }) => {
reset();
id.value = accountId;
lastSyncedAt.value = last_synced_at;
// lastSyncedAt.value = last_synced_at;
addAccountFormData.value = form;
shouldSelectSubAccount.value = needSelectSubAccount;
visible.value = true;
@ -153,9 +153,9 @@ const reset = () => {
failReason.value = '';
progress.value = 0;
id.value = '';
lastSyncedAt.value = null;
syncType.value = INITIAL_SYNC_TYPE;
showSyncTip.value = false;
// lastSyncedAt.value = null;
// syncType.value = INITIAL_SYNC_TYPE;
// showSyncTip.value = false;
shouldSelectSubAccount.value = false;
addAccountFormData.value = null;
clearFakeProgressTimer();
@ -190,6 +190,8 @@ const startStatusPolling = () => {
clearFakeProgressTimer();
clearStatusPollingTimer();
isLoading.value = false;
isSuccess.value && postPlacementAccountsSync(id.value);
}
}
}, 2000);
@ -236,34 +238,34 @@ const clearFakeProgressTimer = () => {
}
};
const handleSyncData = () => {
if (!showSyncTip.value) {
formRef.value.validate(async (errors) => {
if (!errors) {
showSyncTip.value = true;
}
});
return;
}
// const handleSyncData = () => {
// if (!showSyncTip.value) {
// formRef.value.validate(async (errors) => {
// if (!errors) {
// showSyncTip.value = true;
// }
// });
// return;
// }
if (syncType.value === INITIAL_SYNC_TYPE) {
postPlacementAccountsSync(id.value).then((res) => {
if (res.code === 200) {
update();
close();
}
});
} else {
close();
}
};
// if (syncType.value === INITIAL_SYNC_TYPE) {
// postPlacementAccountsSync(id.value).then((res) => {
// if (res.code === 200) {
// update();
// close();
// }
// });
// } else {
// close();
// }
// };
const handleOk = () => {
// n天未同步更新
if (lastSyncedAt.value && lastSyncedAt.value < dayjs().startOf('day').valueOf()) {
handleSyncData();
return;
}
// // n天未同步更新
// if (lastSyncedAt.value && lastSyncedAt.value < dayjs().startOf('day').valueOf()) {
// handleSyncData();
// return;
// }
// 已完成
if (isCompleted.value) {

View File

@ -73,7 +73,7 @@
import { defineEmits, defineProps } from 'vue';
import { getPlacementAccountOperators, getProjectList } from '@/api/all/propertyMarketing';
import { PLATFORM_LIST } from '@/utils/platform';
import StatusSelect from '@/views/property-marketing/put-account/components/status-select';
import StatusSelect from '@/views/property-marketing/media-account/components/status-select';
import CommonSelect from '@/components/common-select';
const props = defineProps({

View File

@ -100,7 +100,7 @@
</div>
<template #footer>
<span class="cts color-#3C4043 s1"
>已选<span class="color-#6D4CFE num mx-3px">{{ selectedAccounts.length }}</span>账户</span
>已选<span class="color-#6D4CFE num mx-3px">{{ selectedRows.length }}</span>账户</span
>
<div class="flex items-center">
<a-button class="cancel-btn" size="large" @click="onClose">取消</a-button>
@ -109,7 +109,7 @@
class="ml-16px"
status="danger"
size="large"
:disabled="!selectedAccounts.length"
:disabled="!selectedRows.length"
@click="onConfirm"
>添加已选账户</a-button
>
@ -136,7 +136,7 @@ const form = ref(null);
const ROW_KEY = 'account_id';
const {
selectedRowKeys,
selectedRows: selectedAccounts,
selectedRows,
dataSource,
pageInfo,
onPageChange,
@ -170,7 +170,8 @@ const open = (formData) => {
const onClose = () => {
form.value = null;
selectedAccounts.value = [];
selectedRows.value = [];
dataSource.value = [];
selectedRowKeys.value = [];
query.value = cloneDeep(INITIAL_FORM);
pageInfo.value = cloneDeep(INITIAL_PAGE_INFO);
@ -178,7 +179,7 @@ const onClose = () => {
};
const onConfirm = async () => {
const { code } = await postAddSubAccount({ ...form.value, subaccounts: selectedAccounts.value });
const { code } = await postAddSubAccount({ ...form.value, subaccounts: selectedRows.value });
if (code === 200) {
visible.value = false;
update();

View File

@ -13,7 +13,7 @@
<script setup>
import { computed } from 'vue';
import { STATUS_LIST, EnumStatus } from '@/views/property-marketing/put-account/components/status-select/constants';
import { STATUS_LIST, EnumStatus } from '@/views/property-marketing/media-account/components/status-select/constants';
import iconWarn1 from '@/assets/img/media-account/icon-warn-1.png';
import iconWarn2 from '@/assets/img/media-account/icon-warn-2.png';

View File

@ -53,7 +53,7 @@
</template>
<div v-else>
<a-space v-if="isAbNormalStatus" class="flex items-center">
<a-button class="w-96px err-btn" size="mini" @click="handleOpenAbnormalAccount">
<a-button type="primary" status="danger" size="mini" @click="handleOpenAbnormalAccount">
<template #default>查看异常账号</template>
</a-button>
</a-space>
@ -86,7 +86,7 @@
</div>
</div>
<AddAccountModal ref="addAccountModalRef" />
<AddPutAccountModal ref="addAccountModalRef" @startQueryTaskStatus="handleGetImportTaskStatus" />
<DeleteAccountModal ref="deleteAccountRef" @update="onDeleteSuccess" />
</div>
</template>
@ -96,11 +96,13 @@ import { ref } from 'vue';
import FilterBlock from './components/filter-block';
import AccountTable from './components/account-table';
import AddAccountModal from './components/add-account-modal';
import AddPutAccountModal from './components/add-account-modal';
import DeleteAccountModal from './components/account-table/delete-account';
import { INITIAL_QUERY } from './constants';
import { getPlacementAccounts, getPlacementAccountsHealth } from '@/api/all/propertyMarketing';
import { getTaskStatus } from '@/api/all/common';
import { showImportResultNotification } from '@/utils/arcoD';
import icon1 from '@/assets/img/media-account/icon-add.png';
import icon4 from '@/assets/img/media-account/icon-success.png';
@ -111,6 +113,7 @@ const groupManageModalRef = ref(null);
const tagsManageModalRef = ref(null);
const addAccountModalRef = ref(null);
const deleteAccountRef = ref(null);
let queryTaskTimer = null;
const pageInfo = ref({
page: 1,
@ -158,10 +161,6 @@ const tipLabel = computed(() => {
return `共有 ${total_abnormal_number} 个账号存在授权异常,其中:${abnormalLabels.join('')}`;
});
onMounted(() => {
getData();
});
const getData = () => {
getHealthData();
getAccountData();
@ -257,6 +256,37 @@ const handleOpenAbnormalAccount = () => {
reload();
};
// 查询导入账号任务状态
const getSyncTaskStatus = async (id, notificationId) => {
const { code, data } = await getTaskStatus(id);
if (code === 200) {
if (data?.status !== 0) {
clearQueryTaskTimer();
notificationId && Notification.remove(notificationId);
showImportResultNotification(data);
getData();
}
}
};
const handleGetImportTaskStatus = (id, notificationId) => {
clearQueryTaskTimer();
getSyncTaskStatus(id, notificationId);
queryTaskTimer = setInterval(() => getSyncTaskStatus(id, notificationId), 3000);
};
const clearQueryTaskTimer = () => {
if (queryTaskTimer) {
clearInterval(queryTaskTimer);
queryTaskTimer = null;
}
};
onMounted(() => {
getData();
});
onUnmounted(() => {
clearQueryTaskTimer();
});
provide('update', getData);
</script>

View File

@ -31,15 +31,6 @@
color: #211f24;
}
}
.err-btn {
background-color: #f64b31 !important;
color: var(--BG-white, #fff);
font-family: 'PingFang SC';
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 166.667% */
}
.operation-btn {
padding: 0;
cursor: pointer;

View File

@ -1,60 +0,0 @@
/*
* @Author: RenXiaoDong
* @Date: 2025-07-04 11:18:11
*/
export enum EnumStatus {
UNAUTHORIZED = 0,
NORMAL = 1,
ABNORMAL = 3,
PAUSE = 2,
ABNORMAL_LOGIN = 4,
ABNORMAL_REQUEST = 5,
ABNORMAL_FREEZE = 6,
ABNORMAL_MISSING = 7,
}
export const STATUS_LIST = [
{
text: '正常',
label: '正常',
value: EnumStatus.NORMAL,
},
{
text: '暂停同步',
label: '暂停同步',
value: EnumStatus.PAUSE,
},
{
text: '未授权',
label: '未授权',
value: EnumStatus.UNAUTHORIZED,
},
{
text: '异常',
label: '异常',
value: EnumStatus.ABNORMAL,
},
{
text: '数据缺失',
label: '数据缺失',
value: EnumStatus.ABNORMAL_MISSING,
},
{
text: '异常-登录状态失效',
label: '异常',
value: EnumStatus.ABNORMAL_LOGIN,
tooltip: '登录状态失效,需重新扫码授权',
},
{
text: '异常-请求过于频繁',
label: '异常',
value: EnumStatus.ABNORMAL_REQUEST,
tooltip: '请求过于频繁需等待24小时后重试',
},
{
text: '异常-账号被冻结/封禁',
label: '异常',
value: EnumStatus.ABNORMAL_FREEZE,
tooltip: '账号被冻结/封禁',
},
];

View File

@ -1,60 +0,0 @@
<!--
* @Author: RenXiaoDong
* @Date: 2025-06-25 14:02:40
-->
<template>
<a-select
v-model="selectedStatus"
:multiple="multiple"
size="medium"
:placeholder="placeholder"
allow-clear
@change="handleChange"
>
<a-option v-for="(item, index) in STATUS_LIST" :key="index" :value="item.value" :label="item.text">
{{ item.text }}
</a-option>
</a-select>
</template>
<script setup>
import { ref, watch } from 'vue';
import { STATUS_LIST } from './constants';
const props = defineProps({
modelValue: {
type: [Array, String, Number],
default: () => [],
},
multiple: {
type: Boolean,
default: false,
},
placeholder: {
type: String,
default: '全部',
},
});
const emits = defineEmits(['update:modelValue', 'change']);
const selectedStatus = ref(props.multiple ? [] : '');
// 监听外部传入的值变化
watch(
() => props.modelValue,
(newVal) => {
selectedStatus.value = newVal;
},
{ immediate: true },
);
// 监听内部值变化,向外部发送更新
watch(selectedStatus, (newVal) => {
emits('update:modelValue', newVal);
});
const handleChange = (value) => {
selectedStatus.value = value;
emits('change', value);
};
</script>