feat: 媒体账号/投放账户增加【所属项目】相关逻辑

This commit is contained in:
rd
2025-07-23 14:30:44 +08:00
parent 9ffe5be94e
commit b90d0aba26
18 changed files with 174 additions and 186 deletions

View File

@ -19,7 +19,7 @@
<div class="filter-row-item flex items-center">
<span class="label">分组</span>
<a-space class="w-200px">
<GroupSelect v-model="query.group_ids" multiple :options="groups" @change="handleSearch" />
<CommonSelect v-model="query.group_ids" :options="groups" @change="handleSearch" />
</a-space>
</div>
<div class="filter-row-item flex items-center">
@ -31,7 +31,7 @@
<div class="filter-row-item flex items-center">
<span class="label">运营人员</span>
<a-space class="w-160px">
<OperatorSelect v-model="query.operator_id" :options="operators" @change="handleSearch" />
<CommonSelect v-model="query.operator_id" :multiple="false" :options="operators" @change="handleSearch" />
</a-space>
</div>
</div>
@ -66,9 +66,8 @@
<script setup>
import { reactive, defineEmits, defineProps } from 'vue';
import { fetchAccountGroups, fetchAccountOperators } from '@/api/all/propertyMarketing';
import GroupSelect from '@/views/property-marketing/media-account/components/group-select';
import OperatorSelect from '@/views/property-marketing/media-account/components/operator-select';
import StatusSelect from '@/views/property-marketing/media-account/components/status-select';
import CommonSelect from '@/components/common-select';
const props = defineProps({
query: {

View File

@ -46,6 +46,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">
<a-tooltip
v-if="item.projects.length > 2"
position="bottom"
:content="
item.projects
.slice(2)
.map((v) => v.name)
.join(',')
"
>
<div class="tag-box">
<span class="text">{{ `+${item.projects.length - 2}` }}</span>
</div>
</a-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 class="field-row">
<span class="label">分组</span>
<span class="cts">{{ item.group?.name || '-' }}</span>
@ -76,7 +100,7 @@
</div>
<div class="operate-row">
<a-dropdown trigger="hover">
<a-button class="w-52px mr-8px" type="outline" size="mini">
<a-button class="w-52px mr-8px" type="outline" size="mini">
<template #default>更多</template>
</a-button>
<template #content>
@ -89,7 +113,7 @@
>
<a-doption class="color-#F64B31" @click="openDelete(item)">删除</a-doption>
</template>
<a-button type="outline" size="mini" @click="onBtnClick(item)">
<a-button type="outline" size="mini" @click="onBtnClick(item)">
<template #default>{{ getBtnText(item) }}</template>
</a-button>
</a-dropdown>
@ -101,8 +125,8 @@
<span class="name !mb-0">更新数据失败</span>
</div>
<div class="flex items-center">
<a-button type="outline" class="mr-8px" size="mini" @click="onDeleteSyncStatus(item)">取消</a-button>
<a-button type="outline" class="" size="mini" @click="syncData(item)">重新更新</a-button>
<a-button type="outline" class="mr-8px" size="mini" @click="onDeleteSyncStatus(item)">取消</a-button>
<a-button type="outline" class="" size="mini" @click="syncData(item)">重新更新</a-button>
</div>
</div>
</a-spin>
@ -174,7 +198,7 @@ const isSyncing = (item) => {
if (!props.syncMediaAccounts.length) return false;
const target = props.syncMediaAccounts.find((v) => v.id === item.id);
if(target) {
if (target) {
return target?.status === 0;
}
return target?.status === 0;

View File

@ -1,9 +1,3 @@
@mixin ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.card-container {
flex: 1;
display: grid;

View File

@ -19,12 +19,11 @@ import {
Message as AMessage,
Textarea,
} from '@arco-design/web-vue';
import TagSelect from '@/views/property-marketing/media-account/components/tag-select';
import GroupSelect from '@/views/property-marketing/media-account/components/group-select';
import AuthorizedAccountModal from '../authorized-account-modal';
import ImportPromptModal from '../import-prompt-modal';
import StatusBox from '../status-box';
import SyncDataModal from '../sync-data-modal';
import CommonSelect from '@/components/common-select';
import icon1 from '@/assets/img/media-account/icon-download.png';
import icon2 from '@/assets/img/media-account/icon-delete.png';
@ -40,6 +39,7 @@ import {
putMediaAccounts,
getTemplateUrl,
batchMediaAccounts,
getProjectList,
} from '@/api/all/propertyMarketing';
const UploadStatus = {
@ -54,6 +54,7 @@ const INITIAL_FORM = {
platform: 1,
group_id: undefined,
tag_ids: [],
project_ids: [],
end_work_link: undefined,
cookie: undefined,
};
@ -62,6 +63,7 @@ export default {
setup(props, { emit, expose }) {
const groupOptions = ref([]);
const tagOptions = ref([]);
const projects = ref([]);
const visible = ref(false);
const uploadType = ref('manual');
const uploadStatus = ref(UploadStatus.DEFAULT);
@ -74,7 +76,7 @@ export default {
const importPromptModalRef = ref(null);
const uploadRef = ref(null);
const isCustomCookie = ref(false);
const form = ref({ ...INITIAL_FORM });
const form = ref(cloneDeep(INITIAL_FORM));
const syncDataModalRef = ref(null);
const importLoading = ref(false);
const CustomNotificationVisible = ref(false);
@ -123,6 +125,12 @@ export default {
tagOptions.value = data;
}
};
const getProjects = async () => {
const { code, data } = await getProjectList();
if (code === 200) {
projects.value = data;
}
};
function handleUpload(option) {
const { fileItem } = option;
uploadStatus.value = UploadStatus.WAITING;
@ -137,7 +145,10 @@ export default {
const reset = () => {
formRef.value?.resetFields();
formRef.value?.clearValidate();
form.value = { ...INITIAL_FORM };
groupOptions.value = [];
tagOptions.value = [];
projects.value = [];
form.value = cloneDeep(INITIAL_FORM);
fileName.value = '';
file.value = null;
isEdit.value = false;
@ -158,6 +169,7 @@ export default {
}
getGroups();
getTags();
getProjects();
visible.value = true;
};
const getAccountDetail = async () => {
@ -388,8 +400,16 @@ export default {
<FormItem label="号码持有人" field="holder_name">
<Input v-model={form.value.holder_name} placeholder="请输入..." class="w-240px" size="large" />
</FormItem>
<FormItem label="所属项目">
<CommonSelect
v-model={form.value.project_ids}
options={projects.value}
placeholder="请选择…"
size="large"
/>
</FormItem>
<FormItem label="选择分组">
<GroupSelect
<CommonSelect
v-model={form.value.group_id}
multiple={false}
options={groupOptions.value}
@ -397,8 +417,9 @@ export default {
size="large"
/>
</FormItem>
<FormItem label="选择标签">
<TagSelect v-model={form.value.tag_ids} options={tagOptions.value} placeholder="请选择…" size="large" />
<CommonSelect v-model={form.value.tag_ids} options={tagOptions.value} placeholder="请选择…" size="large" />
</FormItem>
<FormItem
label="笔记链接"

View File

@ -30,7 +30,7 @@
<a-form-item label="选择分组" required>
<template v-if="editType === 'all'">
<div class="flex items-center w-100%">
<GroupSelect v-model="form.group_id" :options="groupOptions" :multiple="false" class="flex-1" />
<CommonSelect v-model="form.group_id" :options="groupOptions" :multiple="false" class="flex-1" />
</div>
</template>
</a-form-item>
@ -47,7 +47,7 @@
<a-table-column title="选择分组" data-index="group_id">
<template #cell="{ record }">
<div class="flex items-center w-100%">
<GroupSelect v-model="record.group_id" :options="groupOptions" :multiple="false" />
<CommonSelect v-model="record.group_id" :options="groupOptions" :multiple="false" />
</div>
</template>
</a-table-column>
@ -65,7 +65,7 @@
<script setup>
import { ref, reactive } from 'vue';
import { fetchAccountGroups, batchPutGroup } from '@/api/all/propertyMarketing';
import GroupSelect from '@/views/property-marketing/media-account/components/group-select';
import CommonSelect from '@/components/common-select';
import icon1 from '@/assets/img/icon-question.png';

View File

@ -33,16 +33,20 @@
<span class="label">平台</span>
<a-space class="w-160px">
<a-select v-model="query.platform" size="medium" placeholder="全部" allow-clear @change="handleSearch">
<a-option v-for="(item, index) in MEDIA_ACCOUNT_PLATFORMS" :key="index" :value="item.value" :label="item.label">{{
item.label
}}</a-option>
<a-option
v-for="(item, index) in MEDIA_ACCOUNT_PLATFORMS"
:key="index"
:value="item.value"
:label="item.label"
>{{ item.label }}</a-option
>
</a-select>
</a-space>
</div>
<div class="filter-row-item flex items-center">
<span class="label">运营人员</span>
<a-space class="w-160px">
<OperatorSelect v-model="query.operator_id" :options="operators" @change="handleSearch" />
<CommonSelect v-model="query.operator_id" :multiple="false" :options="operators" @change="handleSearch" />
</a-space>
</div>
</div>
@ -50,16 +54,22 @@
<div class="filter-row-item flex items-center">
<span class="label">分组</span>
<a-space class="w-200px">
<GroupSelect v-model="query.group_ids" multiple :options="groups" @change="handleSearch" />
<CommonSelect v-model="query.group_ids" multiple :options="groups" @change="handleSearch" />
</a-space>
</div>
<div class="filter-row-item flex items-center">
<span class="label">所属项目</span>
<a-space class="w-200px">
<CommonSelect v-model="query.project_ids" :options="projects" @change="handleSearch" />
</a-space>
</div>
<div class="filter-row-item flex items-center">
<span class="label">标签</span>
<a-space class="w-320px">
<TagSelect v-model="query.tag_ids" :options="tags" @change="handleSearch" />
<CommonSelect v-model="query.tag_ids" :options="tags" @change="handleSearch" />
</a-space>
</div>
<a-button type="outline" class="w-84px mr-12px" size="medium" @click="handleSearch">
<a-button type="outline" class="w-84px mr-12px" size="medium" @click="handleSearch">
<template #icon>
<icon-search />
</template>
@ -77,11 +87,14 @@
<script setup>
import { reactive, defineEmits, defineProps } from 'vue';
import { fetchAccountTags, fetchAccountGroups, fetchAccountOperators } from '@/api/all/propertyMarketing';
import TagSelect from '@/views/property-marketing/media-account/components/tag-select';
import GroupSelect from '@/views/property-marketing/media-account/components/group-select';
import OperatorSelect from '@/views/property-marketing/media-account/components/operator-select';
import {
fetchAccountTags,
getProjectList,
fetchAccountGroups,
fetchAccountOperators,
} from '@/api/all/propertyMarketing';
import StatusSelect from '@/views/property-marketing/media-account/components/status-select';
import CommonSelect from '@/components/common-select';
import { INITIAL_QUERY } from '@/views/property-marketing/media-account/account-manage/constants';
import { MEDIA_ACCOUNT_PLATFORMS } from '@/utils/platform';
@ -98,6 +111,7 @@ const emits = defineEmits('onSearch', 'onReset', 'update:query');
const tags = ref([]);
const groups = ref([]);
const operators = ref([]);
const projects = ref([]);
const handleSearch = () => {
emits('update:query', props.query);
@ -128,11 +142,18 @@ const getOperators = async () => {
operators.value = data;
}
};
const getProjects = async () => {
const { code, data } = await getProjectList();
if (code === 200) {
projects.value = data;
}
};
onMounted(() => {
getTags();
getGroups();
getOperators();
getProjects();
});
defineExpose({

View File

@ -9,6 +9,7 @@ export const INITIAL_QUERY = {
operator_id: '',
group_ids: [],
tag_ids: [],
project_ids: [],
};
export const INITIAL_PAGE_INFO = {

View File

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

View File

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

View File

@ -32,7 +32,7 @@
<div class="filter-row-item flex items-center">
<span class="label">运营人员</span>
<a-space class="w-160px">
<OperatorSelect v-model="query.operator_id" :options="operators" />
<CommonSelect v-model="query.operator_id" :multiple="false" :options="operators" />
</a-space>
</div>
</div>
@ -80,7 +80,7 @@ import {
getPlacementAccountProjectsTrend,
fetchAccountOperators,
} from '@/api/all/propertyMarketing';
import OperatorSelect from '@/views/property-marketing/media-account/components/operator-select/index.vue';
import CommonSelect from '@/components/common-select';
import AccountSelect from '@/views/components/common/AccountSelect.vue';
import PlanSelect from '@/views/components/common/PlanSelect.vue';

View File

@ -19,7 +19,7 @@
<div v-if="!isAccountTab" class="filter-row-item flex items-center">
<span class="label">计划分组</span>
<a-space class="w-200px">
<group-select v-model="query.group_ids" multiple :options="groups" @change="handleSearch" />
<CommonSelect v-model="query.group_ids" multiple :options="groups" @change="handleSearch" />
</a-space>
</div>
<div class="filter-row-item flex items-center">
@ -31,7 +31,7 @@
<div class="filter-row-item flex items-center">
<span class="label">运营人员</span>
<a-space class="w-160px">
<OperatorSelect v-model="query.operator_id" :options="operators" @change="handleSearch" />
<CommonSelect v-model="query.operator_id" :multiple="false" :options="operators" @change="handleSearch" />
</a-space>
</div>
</div>
@ -78,9 +78,8 @@ import {
getPlacementAccountsList,
getPlacementAccountOperators,
} from '@/api/all/propertyMarketing';
import GroupSelect from '../group-select';
import OperatorSelect from '@/views/property-marketing/put-account/components/operator-select';
import CommonSelect from '@/components/common-select';
import StatusSelect from '@/views/property-marketing/put-account/components/status-select';
import AccountSelect from '@/views/property-marketing/put-account/components/account-select';

View File

@ -41,6 +41,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">
<a-tooltip
v-if="item.projects.length > 2"
position="bottom"
:content="
item.projects
.slice(2)
.map((v) => v.name)
.join(',')
"
>
<div class="tag-box">
<span class="text">{{ `+${item.projects.length - 2}` }}</span>
</div>
</a-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 class="field-row">
<span class="label">账户总消耗</span>
<span class="cts">{{ `${formatNumberShow({ value: item.total_use_amount, showExactValue: true })}` }}</span>

View File

@ -1,9 +1,3 @@
@mixin ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.card-container {
flex: 1;
display: grid;

View File

@ -111,6 +111,9 @@
</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="所属项目" field="project_ids">
<CommonSelect v-model="form.project_ids" :options="projects" placeholder="请选择…" size="large" />
</a-form-item>
<template v-if="isEdit">
<a-form-item label="账户总消耗" field="total_use_amount">
<a-input v-model="form.total_use_amount" placeholder="请输入..." size="large" disabled />
@ -148,6 +151,8 @@ import { ref, defineEmits } from 'vue';
import AuthorizedAccountModal from '../authorized-account-modal';
// import ImportPromptModal from '../import-prompt-modal';
import StatusBox from '../status-box';
import CommonSelect from '@/components/common-select';
import { PLATFORM_LIST, ENUM_PUT_ACCOUNT_PLATFORM } from '@/utils/platform';
import {
@ -156,6 +161,7 @@ import {
putPlacementAccounts,
getPlacementAccountsTemplateUrl,
batchPlacementAccounts,
getProjectList,
} from '@/api/all/propertyMarketing';
import icon1 from '@/assets/img/media-account/icon-download.png';
@ -174,6 +180,7 @@ const INITIAL_FORM = {
holder_name: '',
platform: 0,
is_sync_project: 1,
project_ids: [],
};
const visible = ref(false);
@ -188,6 +195,7 @@ const authorizedAccountModalRef = ref(null);
const uploadRef = ref(null);
const file = ref(null);
const form = ref(cloneDeep(INITIAL_FORM));
const projects = ref([]);
const rules = {
mobile: [
@ -240,6 +248,7 @@ const reset = () => {
fileName.value = '';
file.value = null;
isEdit.value = false;
projects.value = [];
uploadStatus.value = UploadStatus.DEFAULT;
uploadType.value = 'manual';
};
@ -252,12 +261,19 @@ const open = (accountId = '') => {
id.value = accountId;
isEdit.value = !!accountId;
getProjects();
if (accountId) {
getAccountDetail();
}
visible.value = true;
};
const getProjects = async () => {
const { code, data } = await getProjectList();
if (code === 200) {
projects.value = data;
}
};
const getAccountDetail = async () => {
const { code, data } = await getPlacementAccountsDetail(id.value);

View File

@ -42,11 +42,17 @@
<div class="filter-row-item flex items-center">
<span class="label">运营人员</span>
<a-space class="w-160px">
<OperatorSelect v-model="query.operator_id" :options="operators" @change="handleSearch" />
<CommonSelect v-model="query.operator_id" :multiple="false" :options="operators" @change="handleSearch" />
</a-space>
</div>
</div>
<div class="filter-row flex">
<div class="filter-row-item flex items-center">
<span class="label">所属项目</span>
<a-space class="w-200px">
<CommonSelect v-model="query.project_ids" :options="projects" @change="handleSearch" />
</a-space>
</div>
<a-button type="outline" class="mr-12px" size="medium" @click="handleSearch">
<template #icon>
<icon-search />
@ -65,10 +71,10 @@
<script setup>
import { defineEmits, defineProps } from 'vue';
import { getPlacementAccountOperators } from '@/api/all/propertyMarketing';
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 OperatorSelect from '@/views/property-marketing/put-account/components/operator-select';
import CommonSelect from '@/components/common-select';
const props = defineProps({
query: {
@ -80,6 +86,7 @@ const props = defineProps({
const emits = defineEmits('onSearch', 'onReset', 'update:query');
const operators = ref([]);
const projects = ref([]);
const handleSearch = () => {
emits('update:query', props.query);
@ -97,9 +104,16 @@ const getOperators = async () => {
operators.value = data;
}
};
const getProjects = async () => {
const { code, data } = await getProjectList();
if (code === 200) {
projects.value = data;
}
};
onMounted(() => {
getOperators();
getProjects();
});
</script>

View File

@ -8,4 +8,5 @@ export const INITIAL_QUERY = {
status: '',
platform: '',
operator_id: '',
project_ids: [],
};

View File

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