Commit 30611bd1 authored by 张宇聪's avatar 张宇聪 👀

feat: 提交

parent 62919d05
...@@ -34,6 +34,8 @@ declare global { ...@@ -34,6 +34,8 @@ declare global {
const Row: typeof import('antd')['Row'] const Row: typeof import('antd')['Row']
const Rules: typeof import('./src/utils/REG')['Rules'] const Rules: typeof import('./src/utils/REG')['Rules']
const Select: typeof import('antd')['Select'] const Select: typeof import('antd')['Select']
const Selector: typeof import('./src/components/common/Selector')['default']
const SlotList: typeof import('./src/apis/media/mediaManage/slot/index')['SlotList']
const Space: typeof import('antd')['Space'] const Space: typeof import('antd')['Space']
const Spin: typeof import('antd')['Spin'] const Spin: typeof import('antd')['Spin']
const Switch: typeof import('antd')['Switch'] const Switch: typeof import('antd')['Switch']
...@@ -43,6 +45,7 @@ declare global { ...@@ -43,6 +45,7 @@ declare global {
const UserList: typeof import('./src/constants/userList')['UserList'] const UserList: typeof import('./src/constants/userList')['UserList']
const action: typeof import('mobx')['action'] const action: typeof import('mobx')['action']
const addMediaAccountAPI: typeof import('./src/apis/media/mediaAccount/index')['addMediaAccountAPI'] const addMediaAccountAPI: typeof import('./src/apis/media/mediaAccount/index')['addMediaAccountAPI']
const addSlot: typeof import('./src/apis/media/mediaManage/slot/index')['addSlot']
const autorun: typeof import('mobx')['autorun'] const autorun: typeof import('mobx')['autorun']
const axios: typeof import('axios')['default'] const axios: typeof import('axios')['default']
const buildREG: typeof import('./src/utils/REG')['buildREG'] const buildREG: typeof import('./src/utils/REG')['buildREG']
...@@ -51,6 +54,7 @@ declare global { ...@@ -51,6 +54,7 @@ declare global {
const changeDate: typeof import('./src/utils/date')['changeDate'] const changeDate: typeof import('./src/utils/date')['changeDate']
const changePEByIdAPI: typeof import('./src/apis/media/mediaAccount/index')['changePEByIdAPI'] const changePEByIdAPI: typeof import('./src/apis/media/mediaAccount/index')['changePEByIdAPI']
const changeStatueByIdAPI: typeof import('./src/apis/media/mediaAccount/index')['changeStatueByIdAPI'] const changeStatueByIdAPI: typeof import('./src/apis/media/mediaAccount/index')['changeStatueByIdAPI']
const checkEmpty: typeof import('./src/utils/index')['checkEmpty']
const checkIsImage: typeof import('./src/utils/upload')['checkIsImage'] const checkIsImage: typeof import('./src/utils/upload')['checkIsImage']
const checkIsVideo: typeof import('./src/utils/upload')['checkIsVideo'] const checkIsVideo: typeof import('./src/utils/upload')['checkIsVideo']
const computed: typeof import('mobx')['computed'] const computed: typeof import('mobx')['computed']
...@@ -59,6 +63,8 @@ declare global { ...@@ -59,6 +63,8 @@ declare global {
const createRef: typeof import('react')['createRef'] const createRef: typeof import('react')['createRef']
const createRequester: typeof import('./src/utils/request')['createRequester'] const createRequester: typeof import('./src/utils/request')['createRequester']
const digitUppercase: typeof import('./src/utils/money')['digitUppercase'] const digitUppercase: typeof import('./src/utils/money')['digitUppercase']
const editSlot: typeof import('./src/apis/media/mediaManage/slot/index')['editSlot']
const editSlotList: typeof import('./src/apis/media/mediaManage/slot/index')['editSlotList']
const extendObservable: typeof import('mobx')['extendObservable'] const extendObservable: typeof import('mobx')['extendObservable']
const fenToYuan: typeof import('./src/utils/money')['fenToYuan'] const fenToYuan: typeof import('./src/utils/money')['fenToYuan']
const flow: typeof import('mobx')['flow'] const flow: typeof import('mobx')['flow']
...@@ -72,9 +78,10 @@ declare global { ...@@ -72,9 +78,10 @@ declare global {
const getMediaAccountListAPI: typeof import('./src/apis/media/mediaAccount/index')['getMediaAccountListAPI'] const getMediaAccountListAPI: typeof import('./src/apis/media/mediaAccount/index')['getMediaAccountListAPI']
const getMediaAuditAPI: typeof import('./src/apis/audit/mediaAudit/index')['getMediaAuditAPI'] const getMediaAuditAPI: typeof import('./src/apis/audit/mediaAudit/index')['getMediaAuditAPI']
const getMediaAuditListAPI: typeof import('./src/apis/audit/mediaAudit/index')['getMediaAuditListAPI'] const getMediaAuditListAPI: typeof import('./src/apis/audit/mediaAudit/index')['getMediaAuditListAPI']
const getRealTime: typeof import('./src/apis/index')['getRealTime'] const getMediaList: typeof import('./src/apis/media/mediaManage/media/index')['getMediaList']
const getSlotDetail: typeof import('./src/apis/media/mediaManage/slot/index')['getSlotDetail']
const getSlotList: typeof import('./src/apis/media/mediaManage/slot/index')['getSlotList']
const getSuffix: typeof import('./src/utils/upload')['getSuffix'] const getSuffix: typeof import('./src/utils/upload')['getSuffix']
const getSvgaInfo: typeof import('./src/utils/upload')['getSvgaInfo']
const getTopOnlineApp: typeof import('./src/apis/index')['getTopOnlineApp'] const getTopOnlineApp: typeof import('./src/apis/index')['getTopOnlineApp']
const getUserById: typeof import('./src/apis/user/index')['getUserById'] const getUserById: typeof import('./src/apis/user/index')['getUserById']
const getUserList: typeof import('./src/apis/user/index')['getUserList'] const getUserList: typeof import('./src/apis/user/index')['getUserList']
...@@ -101,9 +108,10 @@ declare global { ...@@ -101,9 +108,10 @@ declare global {
const prefixYuan: typeof import('./src/utils/money')['prefixYuan'] const prefixYuan: typeof import('./src/utils/money')['prefixYuan']
const rangePresets: typeof import('./src/utils/date')['rangePresets'] const rangePresets: typeof import('./src/utils/date')['rangePresets']
const reaction: typeof import('mobx')['reaction'] const reaction: typeof import('mobx')['reaction']
const record2options: typeof import('./src/utils/index')['record2options']
const request: typeof import('./src/utils/request')['default'] const request: typeof import('./src/utils/request')['default']
const runInAction: typeof import('mobx')['runInAction'] const runInAction: typeof import('mobx')['runInAction']
const s: typeof import('./src/apis/media/mediaManage/slot/index')['s']
const saveSlot: typeof import('./src/apis/media/mediaManage/slot/index')['saveSlot']
const smartFenToYuan: typeof import('./src/utils/money')['smartFenToYuan'] const smartFenToYuan: typeof import('./src/utils/money')['smartFenToYuan']
const splitStrBy: typeof import('./src/utils/string')['splitStrBy'] const splitStrBy: typeof import('./src/utils/string')['splitStrBy']
const startTransition: typeof import('react')['startTransition'] const startTransition: typeof import('react')['startTransition']
...@@ -152,6 +160,15 @@ declare global { ...@@ -152,6 +160,15 @@ declare global {
export type { mediaSearchP, mediaAccountList, mediaAccount, changeParams } from './src/apis/media/mediaAccount/type' export type { mediaSearchP, mediaAccountList, mediaAccount, changeParams } from './src/apis/media/mediaAccount/type'
import('./src/apis/media/mediaAccount/type') import('./src/apis/media/mediaAccount/type')
// @ts-ignore // @ts-ignore
export type { MediaInfo, MediaInfoList, QueryMediaParams } from './src/apis/media/mediaManage/media/type'
import('./src/apis/media/mediaManage/media/type')
// @ts-ignore
export type { SlotInfo, SlotInfoList, QuerySlotParams, SlotModalData, QueryDetail } from './src/apis/media/mediaManage/slot/type'
import('./src/apis/media/mediaManage/slot/type')
// @ts-ignore
export type { ListBody, SelectOption, TabItem, List } from './src/apis/pages/index'
import('./src/apis/pages/index')
// @ts-ignore
export type { paginationType } from './src/apis/type' export type { paginationType } from './src/apis/type'
import('./src/apis/type') import('./src/apis/type')
// @ts-ignore // @ts-ignore
......
import jsConfig from '@tuia/eslint-config-common/global.js' import jsConfig from '@tuia/eslint-config-common/global.js'
import reactConfig from '@tuia/eslint-config-common/react.js' import reactConfig from '@tuia/eslint-config-common/react.js'
import tsConfig from '@tuia/eslint-config-common/typeScript.js' import tsConfig from '@tuia/eslint-config-common/typescript.js'
import reactRefresh from 'eslint-plugin-react-refresh' import reactRefresh from 'eslint-plugin-react-refresh'
export default [ export default [...jsConfig, ...reactConfig, ...tsConfig, { ignores: ['dist'] }, reactRefresh.configs.vite]
...jsConfig,
...reactConfig,
...tsConfig,
{ ignores: ['dist'] },
reactRefresh.configs.vite,
{
rules: {
'import/no-unresolved': 'off',
},
},
]
import { Suspense } from 'react' import { Suspense } from 'react'
import { StyleProvider } from '@ant-design/cssinjs' import { StyleProvider } from '@ant-design/cssinjs'
// eslint-disable-next-line import-x/no-unresolved
import routes from '~react-pages' import routes from '~react-pages'
import { ConfigProvider } from 'antd' import { ConfigProvider } from 'antd'
import zhCN from 'antd/lib/locale/zh_CN' import zhCN from 'antd/lib/locale/zh_CN'
import Layouts from './layouts/index.tsx' import Layouts from './layouts'
import 'dayjs/locale/zh-cn'
const App: React.FC = () => { const App: React.FC = () => {
return ( return (
<Suspense fallback={<Spin fullscreen />}> <Suspense fallback={<Spin fullscreen />}>
......
/**
* @description: 获取媒体列表
* @param {QueryMediaParams} params 请求参数
* @return {MediaInfoList} 媒体信息列表
*/
export const getMediaList = (data: QueryMediaParams) =>
request<QueryMediaParams, MediaInfoList>('/media/data/page', { method: 'post', data })
import { Dayjs } from 'dayjs'
/**
* 媒体列表
*/
export interface MediaInfo {
/** 媒体id */
id: number
/** 流量开关 */
status: number
/** 媒体名称 */
mediaName: string
/** 曝光 */
exposePv: number
/** 点击 */
clickPv: number
/** 点击率 */
clickRate: number
/** 消耗 */
consume: number
/** cpc */
cpc: number
auditStatus: number
/** 审核状态 */
auditStatusDesc: string
}
export type MediaInfoList = List<MediaInfo>
/**
* 查询参数
*/
export interface QueryMediaParams extends paginationType {
startDate?: string
endDate?: string
/** 媒体名称 */
mediaName?: string
/** 媒体id */
id?: number
date?: Dayjs[]
}
/**
* @description: 获取广告位列表
* @param {QuerySlotParams} params 请求参数
* @return {SlotInfoList} 广告位列表
*/
export const getSlotList = (data: QuerySlotParams) =>
request<QuerySlotParams, SlotInfoList>('/slot/getList', { method: 'post', data })
/**
* @description: 添加/修改广告位
* @param {SlotModalData} data 广告位修改表单
* @return {void} 广告位列表
*/
export const saveSlot = (data: SlotModalData) => request<SlotModalData, void>('/slot/save', { method: 'post', data })
/**
* @description: 获取广告位详情
* @param {QueryDetail} params 请求参数
* @return {SlotInfo} 广告位列表
*/
export const getSlotDetail = (data: QueryDetail) =>
request<QueryDetail, SlotInfo>('/slot/detail', { method: 'post', data })
import { Dayjs } from 'dayjs'
/**
* 广告位列表
*/
export interface SlotInfo {
/** 广告位状态 */
status?: string
/** 推啊侧广告位id */
id: number
/** 媒体样式id */
thirdStyle?: number
/** 广告位名称 */
thirdName?: string
/** 关联的媒体id或dspId */
relId?: number
/** 关联的媒体名称或dsp名称 */
relName?: string
exposePv?: number
clickPv?: number
clickRate?: number
cpc?: number
/** 媒体广告位id */
thirdId?: number
/** 广告位类型 */
slotType?: number
type?: number
}
export type SlotInfoList = List<SlotInfo>
/**
* 查询参数
*/
export interface QuerySlotParams extends paginationType {
startDate?: string
endDate?: string
/** 关联的媒体名称或dsp名称 */
relName?: string
/** 关联的媒体id或dspId */
relId?: number
/** 媒体广告位id或dsp广告位id */
thirdId?: number
/** 推啊广告位id */
id?: number
/** 广告位名称 */
thirdName?: string
/** 广告位类型 */
slotType?: number
/** 类型 1-媒体 2-DSP */
type?: number
date?: Dayjs[]
}
export interface SlotModalData {
relId?: number
relName?: string
slotType?: number
/** 素材样式 */
materialStyle?: string
/** 媒体广告位id或dsp广告位id */
thirdId?: number
/** 广告位名称 */
thirdName?: string
/** 媒体样式id */
thirdStyle?: number
type?: number
}
export interface QueryDetail {
id: number
}
export interface ListBody<T> {
currentPage: number
pageSize: number
data: T
}
export interface SelectOption {
label: string | number
value: string | number
}
export interface TabItem {
name: string
label: string
}
export interface List<T> {
pageNum: number
pageSize: number
total: number
pages: number
list: T[]
}
export interface paginationType { export interface paginationType {
pageSize: number pageSize: number
currentPage: number pageNum: number
} }
.search-form {
display: flex;
}
\ No newline at end of file
...@@ -9,8 +9,7 @@ interface ListProp { ...@@ -9,8 +9,7 @@ interface ListProp {
const List: React.FC<ListProp> = observer((prop: ListProp) => { const List: React.FC<ListProp> = observer((prop: ListProp) => {
const { data, searchRun } = prop const { data, searchRun } = prop
const { setModalOpen, searchParams, getMediaAccountFc, changeAE, changeAEList, changePE, changeStatue } = const { setModalOpen, searchParams, getMediaAccountFc } = mediaAccountStore
mediaAccountStore
const editAddModal = (id: number | string) => { const editAddModal = (id: number | string) => {
setModalOpen(true) setModalOpen(true)
getMediaAccountFc(id) getMediaAccountFc(id)
...@@ -25,6 +24,31 @@ const List: React.FC<ListProp> = observer((prop: ListProp) => { ...@@ -25,6 +24,31 @@ const List: React.FC<ListProp> = observer((prop: ListProp) => {
console.log('handlePageChange', param) console.log('handlePageChange', param)
} }
const changeStatue = (id: string | number, statue: boolean) => {
console.log(id, statue)
const l = changeStatueByIdAPI({ mediaId: id, statue })
console.log(l)
}
const [flag, changeFlag] = useState(false)
const changeAE = (id: string | number, name: string) => {
console.log(id, name)
const l = changeAEByIdAPI({ mediaId: id, AE: name })
console.log(l)
}
const changeAEList = (id: string | number, name: string[]) => {
console.log(id, name)
const l = changeAEListByIdAPI({ mediaId: id, AEList: name })
console.log(l)
}
const changePE = (id: string | number, name: string) => {
console.log(id, name)
const l = changePEByIdAPI({ mediaId: id, PE: name })
console.log(l)
}
const columns: TableColumnsType<mediaAccountList> = [ const columns: TableColumnsType<mediaAccountList> = [
{ {
title: '媒体ID', title: '媒体ID',
...@@ -57,16 +81,24 @@ const List: React.FC<ListProp> = observer((prop: ListProp) => { ...@@ -57,16 +81,24 @@ const List: React.FC<ListProp> = observer((prop: ListProp) => {
key: 'AE', key: 'AE',
render: (AE, record) => { render: (AE, record) => {
return ( return (
<TableSelect <Space size='middle'>
text={AE} {flag ? (
selectValue={record.AEId} <Select
options={[ defaultValue={AE}
{ label: 'text', value: 1 }, style={{ width: 120 }}
{ label: 'text2', value: 2 }, onChange={async (value) => {
{ label: 'text3', value: 3 }, await changeAE(record.mediaId, value)
]} changeFlag(false)
callback={(value) => changeAE(record.mediaId, value as number)} }}>
/> <Select.Option value='test'>test</Select.Option>
<Select.Option value='test2'>test2</Select.Option>
</Select>
) : AE ? (
<a onClick={() => changeFlag(true)}>{AE}</a>
) : (
<a onClick={() => changeFlag(true)}>-</a>
)}
</Space>
) )
}, },
}, },
...@@ -76,17 +108,26 @@ const List: React.FC<ListProp> = observer((prop: ListProp) => { ...@@ -76,17 +108,26 @@ const List: React.FC<ListProp> = observer((prop: ListProp) => {
key: 'AEName', key: 'AEName',
render: (AEName, record) => { render: (AEName, record) => {
return ( return (
<TableSelect <Space size='middle'>
text={AEName} {flag ? (
mode='multiple' <Select
selectValue={AEName} mode='multiple'
options={[ defaultValue={AEName?.split(',')}
{ label: 'text', value: 'text' }, style={{ width: 120 }}
{ label: 'text2', value: 'text2' }, onChange={async (value) => {
{ label: 'text3', value: 'text3' }, await changeAEList(record.mediaId, value)
]} changeFlag(false)
callback={(value) => changeAEList(record.mediaId, value as string[])} }}>
/> <Select.Option value='test'>test</Select.Option>
<Select.Option value='test1'>test1</Select.Option>
<Select.Option value='test2'>test2</Select.Option>
</Select>
) : AEName ? (
<a onClick={() => changeFlag(true)}>{AEName}</a>
) : (
<a onClick={() => changeFlag(true)}>-</a>
)}
</Space>
) )
}, },
}, },
...@@ -96,16 +137,24 @@ const List: React.FC<ListProp> = observer((prop: ListProp) => { ...@@ -96,16 +137,24 @@ const List: React.FC<ListProp> = observer((prop: ListProp) => {
key: 'PE', key: 'PE',
render: (PE, record) => { render: (PE, record) => {
return ( return (
<TableSelect <Space size='middle'>
text={PE} {flag ? (
selectValue={PE} <Select
options={[ defaultValue={PE}
{ label: 'text', value: 'text' }, style={{ width: 120 }}
{ label: 'text2', value: 'text2' }, onChange={async (value) => {
{ label: 'text3', value: 'text3' }, await changePE(record.mediaId, value)
]} changeFlag(false)
callback={(value) => changePE(record.mediaId, value as string)} }}>
/> <Select.Option value='test'>test</Select.Option>
<Select.Option value='test2'>test2</Select.Option>
</Select>
) : PE ? (
<a onClick={() => changeFlag(true)}>{PE}</a>
) : (
<a onClick={() => changeFlag(true)}>-</a>
)}
</Space>
) )
}, },
}, },
......
...@@ -10,6 +10,7 @@ class MediaAccountStore { ...@@ -10,6 +10,7 @@ class MediaAccountStore {
currentPage: 1, currentPage: 1,
} }
mediaAccount: mediaAccount | undefined = { mediaAccount: mediaAccount | undefined = {
mediaId: '',
mediaName: '', mediaName: '',
cName: '', cName: '',
pe: '', pe: '',
...@@ -49,6 +50,7 @@ class MediaAccountStore { ...@@ -49,6 +50,7 @@ class MediaAccountStore {
const res = getMediaAccountAPI(id) const res = getMediaAccountAPI(id)
console.log(res) console.log(res)
this.setMediaAccount({ this.setMediaAccount({
mediaId: 'text',
mediaName: 'text', mediaName: 'text',
cName: 'text', cName: 'text',
pe: 'text', pe: 'text',
......
export const statusColorMap: Record<string, string> = {
['待审核']: 'brown',
['审核通过']: 'green',
['审核拒绝']: 'red',
}
// 策略类型
export enum SLOT_TYPE {
/** 开屏 */
OPEN_SCREEN = 1,
/** 信息流 */
INFO = 2,
/** 插屏 */
INSERT_SCREEN = 3,
/** 激励视频 */
MOTIVATE = 4,
}
export const SLOT_TYPE_TEXT = {
[SLOT_TYPE.OPEN_SCREEN]: '开屏',
[SLOT_TYPE.INFO]: '信息流',
[SLOT_TYPE.INSERT_SCREEN]: '插屏',
[SLOT_TYPE.MOTIVATE]: '激励视频',
}
export const SLOT_TYPE_OPTIONS = [
{
label: SLOT_TYPE_TEXT[SLOT_TYPE.OPEN_SCREEN],
value: SLOT_TYPE.OPEN_SCREEN,
},
{
label: SLOT_TYPE_TEXT[SLOT_TYPE.INFO],
value: SLOT_TYPE.INFO,
},
{
label: SLOT_TYPE_TEXT[SLOT_TYPE.INSERT_SCREEN],
value: SLOT_TYPE.INSERT_SCREEN,
},
{
label: SLOT_TYPE_TEXT[SLOT_TYPE.MOTIVATE],
value: SLOT_TYPE.MOTIVATE,
},
]
export const enum MATERIAL_STYLE {
/** 竖图 */
VERTICAL_PHOTO = 1,
/** 横图 */
HORIZONTAL_PHOTO = 2,
/** 竖视频 */
VERTICAL_VIDEO = 3,
/** 横视频 */
HORIZONTAL_VIDEO = 4,
}
export const MATERIAL_STYLE_TEXT = {
[MATERIAL_STYLE.VERTICAL_PHOTO]: '竖图',
[MATERIAL_STYLE.HORIZONTAL_PHOTO]: '横图',
[MATERIAL_STYLE.VERTICAL_VIDEO]: '竖视频',
[MATERIAL_STYLE.HORIZONTAL_VIDEO]: '横视频',
}
export const MATERIAL_STYLE_OPTIONS = [
{
label: MATERIAL_STYLE_TEXT[MATERIAL_STYLE.VERTICAL_PHOTO],
value: MATERIAL_STYLE.VERTICAL_PHOTO,
},
{
label: MATERIAL_STYLE_TEXT[MATERIAL_STYLE.HORIZONTAL_PHOTO],
value: MATERIAL_STYLE.HORIZONTAL_PHOTO,
},
{
label: MATERIAL_STYLE_TEXT[MATERIAL_STYLE.VERTICAL_VIDEO],
value: MATERIAL_STYLE.VERTICAL_VIDEO,
},
{
label: MATERIAL_STYLE_TEXT[MATERIAL_STYLE.HORIZONTAL_VIDEO],
value: MATERIAL_STYLE.HORIZONTAL_VIDEO,
},
]
export const enum PARAMS_TYPE {
/** 媒体 */
MEDIA = 1,
/** DSP */
DSP = 2,
}
import type { TabsProps } from 'antd'
import { Tabs } from 'antd'
import Media from './media'
import Slot from './slot'
import store from './store'
const tabsItems: TabsProps['items'] = [
{
key: 'media',
label: '媒体',
children: <Media />,
},
{
key: 'slot',
label: '广告位',
children: <Slot />,
},
]
const MediaManage = observer(() => {
const { setTag, currentTag } = store
const onChange = (key: string) => {
setTag(key)
}
return (
<Space direction='vertical' style={{ display: 'flex' }} size='middle'>
<Tabs activeKey={currentTag} items={tabsItems} onChange={onChange} />
</Space>
)
})
export default MediaManage
import store from '../store/index.ts'
import List from './list.tsx'
import Search from './search.tsx'
const Media: React.FC = observer(() => {
const { mediaSearchParams } = store
const { data, run, loading } = useRequest(getMediaList, { defaultParams: [mediaSearchParams] })
return (
<Spin spinning={loading}>
<Search searchRun={run} />
<List data={data} searchRun={run} />
</Spin>
)
})
export default Media
import { type TableColumnsType } from 'antd'
import { statusColorMap } from '../common/const.ts'
import store from '../store/index.ts'
interface ListProp {
data: MediaInfoList | undefined
searchRun: (param: QueryMediaParams) => void
}
const List: React.FC<ListProp> = observer((prop: ListProp) => {
const { data, searchRun } = prop
const { mediaSearchParams, setTag, setSlotSearchParams, setModalOpen } = store
const columns: TableColumnsType<MediaInfo> = [
{
title: '流量开关',
dataIndex: 'status',
key: 'status',
render: (value: number) => <Switch checked={value === 1} />,
},
{
title: '媒体名称',
dataIndex: 'mediaName',
key: 'mediaName',
render: (text, record) => {
return (
<div className='flex flex-col items-center justify-center w-full h-[50px] group'>
<a
className='w-[120px] text-center'
onClick={() => {
setTag('slot')
setSlotSearchParams({ relId: record.id, pageSize: 10, pageNum: 1 })
}}>
{text}
</a>
<div className='max-h-0 overflow-hidden group-hover:max-h-[30px]'>
<a
onClick={() => {
setTag('slot')
setModalOpen(true, { relId: record.id, relName: text })
}}>
创建广告位
</a>
</div>
</div>
)
},
},
{
title: '媒体ID',
dataIndex: 'id',
key: 'id',
},
{
title: '审核状态',
dataIndex: 'auditStatusDesc',
key: 'auditStatusDesc',
render: (text: string) => {
const color = statusColorMap[text] || 'default'
return <span style={{ color }}>{text}</span>
},
},
{
title: '曝光',
dataIndex: 'exposePv',
key: 'exposePv',
render: (text) => {
return <div>{checkEmpty(text)}</div>
},
},
{
title: '点击',
dataIndex: 'clickPv',
key: 'clickPv',
render: (text) => {
return <div>{checkEmpty(text)}</div>
},
},
{
title: '点击率',
dataIndex: 'clickRate',
key: 'clickRate',
render: (text) => {
return <div>{checkEmpty(text) + '%'}</div>
},
},
{
title: '消耗',
dataIndex: 'consume',
key: 'consume',
render: (text) => {
return <div>{checkEmpty(text)}</div>
},
},
]
const handlePageChange = async (page: number, pageSize: number) => {
const param = {
...mediaSearchParams,
pageSize: pageSize,
currentPage: page,
}
searchRun(param)
}
return (
<Table<MediaInfo>
rowKey='id'
className='mt-5'
dataSource={data?.list}
columns={columns}
pagination={{
pageSizeOptions: [10, 20, 50],
showQuickJumper: true,
total: data?.total,
onChange: handlePageChange,
}}
/>
)
})
export default List
import { Form, DatePicker, ConfigProvider, Input, Button } from 'antd'
import locale from 'antd/es/locale/zh_CN' // Ant Design 的中文语言包
import { observer } from 'mobx-react-lite'
import store from '../store'
const { Item: FormItem } = Form
const { RangePicker } = DatePicker
interface SearchProp {
searchRun: (param: QueryMediaParams) => void
}
const Search: React.FC<SearchProp> = observer((prop: SearchProp) => {
const [form] = Form.useForm<QueryMediaParams>()
const { searchRun } = prop
const { setMediaSearchParams, mediaSearchParams } = store
useEffect(() => {
form.setFieldsValue(mediaSearchParams)
}, [form, mediaSearchParams])
const handleSearch = (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
e.preventDefault()
form.validateFields().then((values) => {
const [startDate, endDate] = values.date || []
const newParams = {
...mediaSearchParams,
...values,
startDate: startDate.format('YYYY-MM-DD'),
endDate: endDate.format('YYYY-MM-DD'),
id: Number(values.id),
}
setMediaSearchParams(newParams)
searchRun(newParams)
})
}
return (
<ConfigProvider locale={locale}>
<Form form={form} layout='inline'>
<FormItem name='date'>
<RangePicker format='YYYY-MM-DD' />
</FormItem>
<FormItem<QueryMediaParams> label='媒体ID' name='id' rules={[Rules.id]}>
<Input style={{ width: 200 }} placeholder='请输入媒体ID' />
</FormItem>
<FormItem<QueryMediaParams> label='媒体名称' name='mediaName' rules={[Rules.name]}>
<Input style={{ width: 200 }} placeholder='请输入媒体名称' />
</FormItem>
<FormItem>
<Button type='primary' onClick={handleSearch}>
搜索
</Button>
</FormItem>
</Form>
</ConfigProvider>
)
})
export default Search
import store from '../store/index.ts'
import List from './list'
import Search from './search'
import SlotModal from './slotModal.tsx'
const Media: React.FC = observer(() => {
const { slotSearchParams } = store
const { data, run, loading, refresh } = useRequest(getSlotList, { defaultParams: [slotSearchParams] })
return (
<Spin spinning={loading}>
<Search searchRun={run} />
<List data={data} searchRun={run} />
<SlotModal refresh={refresh} />
</Spin>
)
})
export default Media
import type { TableColumnsType } from 'antd'
import store from '../store/index.ts'
interface ListProp {
data: SlotInfoList | undefined
searchRun: (param: QuerySlotParams) => void
}
const List: React.FC<ListProp> = observer((prop: ListProp) => {
const { slotSearchParams, setModalOpen } = store
const { data, searchRun } = prop
const columns: TableColumnsType<SlotInfo> = [
{
title: '广告位状态',
dataIndex: 'status',
key: 'status',
render: (value: number) => <Switch checked={value === 1} />,
},
{
title: '推啊广告位ID',
dataIndex: 'id',
key: 'id',
},
{
title: '媒体样式ID',
dataIndex: 'thirdStyle',
key: 'thirdStyle',
},
{
title: '广告位名称',
dataIndex: 'thirdName',
key: 'thirdName',
render: (text, record) => {
return (
<div className='flex flex-col items-center justify-center w-full h-[50px] group'>
<a className='w-[120px] text-center'>{text}</a>
<div className='max-h-0 overflow-hidden group-hover:max-h-[30px]'>
<a
onClick={async () => {
const res = await getSlotDetail({ id: record.id })
setModalOpen(true, res)
}}>
编辑
</a>
</div>
</div>
)
},
},
{
title: '媒体ID',
dataIndex: 'relId',
key: 'relId',
render: (text) => {
return <div>{checkEmpty(text)}</div>
},
},
{
title: '媒体名称',
dataIndex: 'relName',
key: 'relName',
render: (text) => {
return <div>{checkEmpty(text)}</div>
},
},
{
title: '曝光',
dataIndex: 'exposePv',
key: 'exposePv',
render: (text) => {
return <div>{checkEmpty(text)}</div>
},
},
{
title: '点击',
dataIndex: 'clickPv',
key: 'clickPv',
render: (text) => {
return <div>{checkEmpty(text)}</div>
},
},
{
title: '点击率',
dataIndex: 'clickRate',
key: 'clickRate',
render: (text) => {
return <div>{checkEmpty(text) + '%'}</div>
},
},
{
title: 'CPC',
dataIndex: 'cpc',
key: 'cpc',
render: (text) => {
return <div>{checkEmpty(text)}</div>
},
},
]
const handlePageChange = async (page: number, pageSize: number) => {
const param = {
...slotSearchParams,
pageSize: pageSize,
currentPage: page,
}
searchRun(param)
}
return (
<Table<SlotInfo>
className='mt-5'
dataSource={data?.list}
columns={columns}
pagination={{
pageSizeOptions: [10, 20, 50],
showQuickJumper: true,
total: data?.total,
onChange: handlePageChange,
}}
/>
)
})
export default List
import { Form, DatePicker, Input, Button } from 'antd'
import { observer } from 'mobx-react-lite'
import { SLOT_TYPE_OPTIONS } from '../common/const'
import store from '../store'
const { Item: FormItem } = Form
const { RangePicker } = DatePicker
interface SearchProp {
searchRun: (param: QuerySlotParams) => void
}
const Search: React.FC<SearchProp> = observer((prop: SearchProp) => {
const [form] = Form.useForm<QuerySlotParams>()
const { searchRun } = prop
const { setSlotSearchParams, slotSearchParams } = store
useEffect(() => {
form.setFieldsValue(slotSearchParams)
}, [form, slotSearchParams])
const handleSearch = async (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
e.preventDefault()
const values = await form.validateFields()
// 安全地处理日期,防止 values.date 未定义
const [startDate, endDate] = values.date && Array.isArray(values.date) ? values.date : [null, null]
// 构造新的搜索参数
const newParams = {
...slotSearchParams,
...values,
startDate: startDate ? startDate.format('YYYY-MM-DD') : undefined,
endDate: endDate ? endDate.format('YYYY-MM-DD') : undefined,
}
setSlotSearchParams(newParams) // 更新搜索参数
searchRun(newParams) // 执行搜索
}
return (
<Form form={form} layout='inline'>
<FormItem<QuerySlotParams> name='date'>
<RangePicker format='YYYY-MM-DD' />
</FormItem>
<FormItem<QuerySlotParams> label='媒体ID' name='relId' rules={[Rules.id]}>
<Input style={{ width: 200 }} placeholder='请输入媒体ID' />
</FormItem>
<FormItem<QuerySlotParams> label='媒体名称' name='relName' rules={[Rules.name]}>
<Input style={{ width: 200 }} placeholder='请输入媒体名称' />
</FormItem>
<FormItem<QuerySlotParams> label='推啊广告位ID' name='id' rules={[Rules.id]}>
<Input style={{ width: 200 }} placeholder='请输入推啊广告位ID' />
</FormItem>
<FormItem<QuerySlotParams> label='推啊广告位名称' name='thirdName' rules={[Rules.name]}>
<Input style={{ width: 200 }} placeholder='请输入推啊广告位名称' />
</FormItem>
<FormItem<QuerySlotParams> label='媒体广告位ID' name='thirdId' rules={[Rules.id]}>
<Input style={{ width: 200 }} placeholder='请输入媒体广告位ID' />
</FormItem>
<FormItem<QuerySlotParams> label='广告位类型' name='slotType'>
<Select style={{ width: 200 }} placeholder='请选择广告位类型' options={SLOT_TYPE_OPTIONS} />
</FormItem>
<FormItem>
<Button type='primary' onClick={handleSearch}>
搜索
</Button>
</FormItem>
</Form>
)
})
export default Search
import { Form, Input } from 'antd'
import { observer } from 'mobx-react-lite'
import { SLOT_TYPE_OPTIONS, MATERIAL_STYLE_OPTIONS, PARAMS_TYPE } from '../common/const'
import store from '../store'
const { Item: FormItem } = Form
interface ModalProp {
refresh: () => void
}
const SlotModal: React.FC<ModalProp> = observer(({ refresh }) => {
const [form] = Form.useForm<SlotModalData>()
const { modalOpen, setModalOpen, idEdit, modalData } = store
useEffect(() => {
form.setFieldsValue(modalData)
}, [form, modalData])
const submit = async (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
e.preventDefault()
const values = await form.validateFields()
const params = { ...modalData, ...values, type: PARAMS_TYPE.MEDIA }
await saveSlot(params)
refresh()
close()
}
const close = () => {
setModalOpen(false, {})
form.resetFields()
}
return (
<Modal title={`${idEdit ? '修改' : '添加'}广告位`} open={modalOpen} okText='保存' onCancel={close} onOk={submit}>
<Form form={form}>
<FormItem<SlotModalData> label='媒体名称' name='relName' rules={[Rules.name]}>
<Input style={{ width: 200 }} disabled />
</FormItem>
<FormItem<SlotModalData> label='广告位类型' name='slotType' rules={[Rules.required]}>
<Select style={{ width: 200 }} placeholder='请选择广告位类型' options={SLOT_TYPE_OPTIONS} />
</FormItem>
<FormItem<SlotModalData> label='素材样式选择' name='materialStyle' rules={[Rules.required]}>
<Checkbox.Group options={MATERIAL_STYLE_OPTIONS} />
</FormItem>
<FormItem<SlotModalData> label='媒体广告位ID' name='thirdId' rules={[Rules.required, Rules.id]}>
<Input style={{ width: 200 }} placeholder='请输入媒体广告位ID' />
</FormItem>
<FormItem<SlotModalData> label='媒体样式ID' name='thirdStyle' rules={[Rules.required, Rules.id]}>
<Input style={{ width: 200 }} placeholder='请输入媒体样式ID' />
</FormItem>
</Form>
</Modal>
)
})
export default SlotModal
import dayjs from 'dayjs'
import { PARAMS_TYPE } from '../common/const'
class Store {
today = dayjs().format()
currentTag = 'media'
slotSearchParams: QuerySlotParams = {
startDate: this.today,
endDate: this.today,
date: [dayjs(this.today), dayjs(this.today)],
relName: '',
relId: void 0,
id: void 0,
thirdName: '',
thirdId: void 0,
slotType: void 0,
pageSize: 10,
pageNum: 1,
type: PARAMS_TYPE.MEDIA,
}
mediaSearchParams: QueryMediaParams = {
startDate: this.today,
endDate: this.today,
date: [dayjs(this.today), dayjs(this.today)],
mediaName: '',
id: void 0,
pageSize: 10,
pageNum: 1,
}
modalOpen = false
modalData: SlotModalData = {
materialStyle: void 0,
relName: '',
slotType: void 0,
thirdId: void 0,
thirdStyle: void 0,
type: PARAMS_TYPE.MEDIA,
}
constructor() {
makeAutoObservable(this) // 自动推断类型
}
setTag = (tag: string) => {
this.currentTag = tag
}
get idEdit() {
return !!this.modalData.thirdId
}
/**
* 设置媒体查询参数
*/
setMediaSearchParams = (value: QueryMediaParams) => {
this.mediaSearchParams = { ...this.mediaSearchParams, ...value }
}
/**
* 操作广告位弹窗
* @param openStatus 是否开启
* @param data 数据
*/
setModalOpen = (openStatus: boolean, data: SlotModalData) => {
this.modalOpen = openStatus
this.modalData = { ...this.modalData, ...data }
}
/**
* 设置广告位查询参数
*/
setSlotSearchParams = (value: QuerySlotParams) => {
this.slotSearchParams = { ...this.slotSearchParams, ...value }
}
}
const store = new Store()
export default store
import MediaManageIndex from '@components/Media/mediaManage/index.tsx'
const Index = observer(() => {
return <MediaManageIndex />
})
export default Index
export function isNothing(value: number | void | string | null | Array<string | number>) { import { ReactNode } from 'react'
export function isNothing(value: number | void | string | null | undefined): boolean {
return ( return (
value === '' || value === '' ||
value === undefined || value === undefined ||
...@@ -6,3 +7,7 @@ export function isNothing(value: number | void | string | null | Array<string | ...@@ -6,3 +7,7 @@ export function isNothing(value: number | void | string | null | Array<string |
(typeof value === 'number' && (isNaN(value) || !isFinite(value))) (typeof value === 'number' && (isNaN(value) || !isFinite(value)))
) )
} }
export function checkEmpty(data: number | string | null | undefined): ReactNode {
return isNothing(data) ? '-' : data
}
/**
* 常用字符串公用方法库
*/
...@@ -109,6 +109,7 @@ export default defineConfig(({ mode }) => ({ ...@@ -109,6 +109,7 @@ export default defineConfig(({ mode }) => ({
'@stores': '/src/stores', '@stores': '/src/stores',
'@apis': '/src/apis', '@apis': '/src/apis',
'@constants': '/src/constants', '@constants': '/src/constants',
'~react-pages': 'virtual:generated-pages-react',
}, },
}, },
})) }))
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment