Merge branch '20260518-long'

This commit is contained in:
2026-05-18 14:24:52 +08:00
4 changed files with 147 additions and 20 deletions
+39 -11
View File
@@ -22,16 +22,26 @@
<el-select <el-select
v-model="formData.media_channel_code" v-model="formData.media_channel_code"
placeholder="请选择自媒体渠道" placeholder="请选择自媒体渠道"
class="w-full" class="account-cost-channel-select w-full"
filterable filterable
:disabled="mode === 'edit'" :disabled="mode === 'edit'"
> >
<el-option <el-option-group
v-for="item in mediaChannelOptions" v-for="g in mediaChannelGroups"
:key="item.code" :key="g.group_name"
:label="item.name" :label="g.group_name"
:value="item.code" >
/> <el-option
v-for="ch in g.channels"
:key="ch.channel_code"
:label="ch.channel_name"
:value="ch.channel_code"
>
<div class="channel-opt-row">
<span class="opt-name">{{ ch.channel_name }}</span>
</div>
</el-option>
</el-option-group>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="部门" prop="dept_id"> <el-form-item label="部门" prop="dept_id">
@@ -78,9 +88,9 @@ import type { FormInstance } from 'element-plus'
import { accountCostAdd, accountCostDetail, accountCostEdit } from '@/api/finance' import { accountCostAdd, accountCostDetail, accountCostEdit } from '@/api/finance'
import Popup from '@/components/popup/index.vue' import Popup from '@/components/popup/index.vue'
interface MediaChannelOption { interface MediaChannelGroup {
code: string group_name: string
name: string channels: { channel_code: string; channel_name: string }[]
} }
interface DeptOption { interface DeptOption {
@@ -90,7 +100,7 @@ interface DeptOption {
} }
const props = defineProps<{ const props = defineProps<{
mediaChannelOptions: MediaChannelOption[] mediaChannelGroups: MediaChannelGroup[]
deptOptions: DeptOption[] deptOptions: DeptOption[]
defaultMediaChannelCode: string defaultMediaChannelCode: string
}>() }>()
@@ -197,3 +207,21 @@ defineExpose({
getDetail, getDetail,
}) })
</script> </script>
<style scoped lang="scss">
.account-cost-channel-select {
:deep(.channel-opt-row) {
display: flex;
align-items: center;
width: 100%;
min-width: 0;
padding-right: 4px;
}
:deep(.opt-name) {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
}
}
</style>
+63 -9
View File
@@ -27,14 +27,25 @@
placeholder="筛选全部渠道" placeholder="筛选全部渠道"
clearable clearable
filterable filterable
class="w-[220px]" class="account-cost-channel-select w-[220px]"
> >
<el-option <el-option label="全部" value="" />
v-for="item in mediaChannelOptions" <el-option-group
:key="item.code" v-for="g in mediaChannelGroups"
:label="item.name" :key="g.group_name"
:value="item.code" :label="g.group_name"
/> >
<el-option
v-for="ch in g.channels"
:key="ch.channel_code"
:label="ch.channel_name"
:value="ch.channel_code"
>
<div class="channel-opt-row">
<span class="opt-name">{{ ch.channel_name }}</span>
</div>
</el-option>
</el-option-group>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="部门"> <el-form-item label="部门">
@@ -117,7 +128,7 @@
<edit-popup <edit-popup
v-if="showEdit" v-if="showEdit"
ref="editRef" ref="editRef"
:media-channel-options="mediaChannelOptions" :media-channel-groups="mediaChannelGroups"
:dept-options="deptOptions" :dept-options="deptOptions"
:default-media-channel-code="defaultMediaChannelCode" :default-media-channel-code="defaultMediaChannelCode"
@success="getLists" @success="getLists"
@@ -133,6 +144,11 @@ import feedback from '@/utils/feedback'
import EditPopup from './edit.vue' import EditPopup from './edit.vue'
interface MediaChannelGroup {
group_name: string
channels: { channel_code: string; channel_name: string }[]
}
interface MediaChannelOption { interface MediaChannelOption {
code: string code: string
name: string name: string
@@ -160,7 +176,7 @@ const { pager, getLists, resetPage } = usePaging({
params: queryParams, params: queryParams,
}) })
const mediaChannelOptions = computed<MediaChannelOption[]>(() => { const mediaChannelOptionsFlat = computed<MediaChannelOption[]>(() => {
const options = pager.extend.media_channel_options const options = pager.extend.media_channel_options
if (!Array.isArray(options)) return [] if (!Array.isArray(options)) return []
@@ -170,6 +186,26 @@ const mediaChannelOptions = computed<MediaChannelOption[]>(() => {
})) }))
}) })
const mediaChannelGroups = computed<MediaChannelGroup[]>(() => {
const groups = pager.extend.media_channel_groups
if (Array.isArray(groups) && groups.length > 0) {
return groups as MediaChannelGroup[]
}
const flat = mediaChannelOptionsFlat.value
if (!flat.length) {
return []
}
return [
{
group_name: '渠道',
channels: flat.map(item => ({
channel_code: item.code,
channel_name: item.name,
})),
},
]
})
const deptOptions = computed<DeptOption[]>(() => { const deptOptions = computed<DeptOption[]>(() => {
const options = pager.extend.dept_options const options = pager.extend.dept_options
return Array.isArray(options) ? options : [] return Array.isArray(options) ? options : []
@@ -213,3 +249,21 @@ const handleReset = () => {
getLists() getLists()
</script> </script>
<style scoped lang="scss">
.account-cost-channel-select {
:deep(.channel-opt-row) {
display: flex;
align-items: center;
width: 100%;
min-width: 0;
padding-right: 4px;
}
:deep(.opt-name) {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
}
}
</style>
@@ -68,6 +68,7 @@ class AccountCostLists extends BaseAdminDataLists implements ListsSearchInterfac
'total_amount' => round((float) $this->baseQuery()->sum('amount'), 2), 'total_amount' => round((float) $this->baseQuery()->sum('amount'), 2),
'days_count' => (int) $this->baseQuery()->distinct(true)->count('cost_date'), 'days_count' => (int) $this->baseQuery()->distinct(true)->count('cost_date'),
'media_channel_options' => MediaChannelService::getOptions(), 'media_channel_options' => MediaChannelService::getOptions(),
'media_channel_groups' => MediaChannelService::getOptionGroups(),
'dept_options' => DeptLogic::getAllData(), 'dept_options' => DeptLogic::getAllData(),
'supports_dept_binding' => AccountCost::supportsDeptBinding(), 'supports_dept_binding' => AccountCost::supportsDeptBinding(),
'default_media_channel_code' => MediaChannelService::getDefaultCode(), 'default_media_channel_code' => MediaChannelService::getDefaultCode(),
@@ -11,6 +11,50 @@ use think\db\Query;
class MediaChannelService class MediaChannelService
{ {
/**
* 与业绩看板「渠道来源」相同的分组(按 source_group_name),不含客户数统计。
*
* @return array<int, array{
* group_name: string,
* channels: array<int, array{channel_code: string, channel_name: string}>
* }>
*/
public static function getOptionGroups(): array
{
$rows = QywxMediaChannel::where('status', 1)
->field('channel_code, channel_name, source_group_name')
->order('source_group_name asc, id asc')
->select()
->toArray();
if ($rows === []) {
return [];
}
$groups = [];
foreach ($rows as $r) {
$g = (string) (($r['source_group_name'] ?? '') !== '' ? $r['source_group_name'] : '其它');
$groups[$g] ??= ['group_name' => $g, 'channels' => []];
$groups[$g]['channels'][] = [
'channel_code' => (string) ($r['channel_code'] ?? ''),
'channel_name' => (string) ($r['channel_name'] ?? ''),
];
}
$sortedGroups = array_values($groups);
usort($sortedGroups, static function (array $a, array $b): int {
$aMedia = mb_strpos($a['group_name'], '自媒体') !== false ? 0 : 1;
$bMedia = mb_strpos($b['group_name'], '自媒体') !== false ? 0 : 1;
if ($aMedia !== $bMedia) {
return $aMedia <=> $bMedia;
}
return strcmp($a['group_name'], $b['group_name']);
});
return $sortedGroups;
}
/** /**
* @return array<int, array{code: string, name: string}> * @return array<int, array{code: string, name: string}>
*/ */