Commit 8874e05e authored by 杨贺晨吉's avatar 杨贺晨吉

feat: 开发1

parent eeb7db42
......@@ -9,7 +9,10 @@ declare global {
const Button: typeof import('antd')['Button']
const Card: typeof import('antd')['Card']
const Checkbox: typeof import('antd')['Checkbox']
const Col: typeof import('antd')['Col']
const DatePicker: typeof import('antd')['DatePicker']
const DraggerUpload: typeof import('./src/components/common/DraggerUpload')['default']
const Flex: typeof import('antd')['Flex']
const Form: typeof import('antd')['Form']
const Input: typeof import('antd')['Input']
const Layout: typeof import('antd')['Layout']
......@@ -26,6 +29,7 @@ declare global {
const Role: typeof import('./src/constants/enums/index')['Role']
const Route: typeof import('react-router-dom')['Route']
const Routes: typeof import('react-router-dom')['Routes']
const Row: typeof import('antd')['Row']
const Rules: typeof import('./src/utils/REG')['Rules']
const Select: typeof import('antd')['Select']
const Space: typeof import('antd')['Space']
......@@ -35,10 +39,17 @@ declare global {
const Upload: typeof import('antd')['Upload']
const UserList: typeof import('./src/constants/userList')['UserList']
const action: typeof import('mobx')['action']
const addMediaAccountAPI: typeof import('./src/apis/mediaAccount/index')['addMediaAccountAPI']
const autorun: typeof import('mobx')['autorun']
const axios: typeof import('axios')['default']
const buildREG: typeof import('./src/utils/REG')['buildREG']
const changeAEByIdAPI: typeof import('./src/apis/mediaAccount/index')['changeAEByIdAPI']
const changeAEListByIdAPI: typeof import('./src/apis/mediaAccount/index')['changeAEListByIdAPI']
const changeDate: typeof import('./src/utils/date')['changeDate']
const changePEByIdAPI: typeof import('./src/apis/mediaAccount/index')['changePEByIdAPI']
const changeStatueByIdAPI: typeof import('./src/apis/mediaAccount/index')['changeStatueByIdAPI']
const checkIsImage: typeof import('./src/utils/upload')['checkIsImage']
const checkIsVideo: typeof import('./src/utils/upload')['checkIsVideo']
const computed: typeof import('mobx')['computed']
const cookie: typeof import('./src/utils/cookie')['default']
const counterStore: typeof import('./src/stores/counterStore')['counterStore']
......@@ -52,10 +63,17 @@ declare global {
const forwardRef: typeof import('react')['forwardRef']
const getCustomList: typeof import('./src/apis/index')['getCustomList']
const getFailedRecords: typeof import('./src/apis/index')['getFailedRecords']
const getFileUrl: typeof import('./src/utils/upload')['getFileUrl']
const getImgInfo: typeof import('./src/utils/upload')['getImgInfo']
const getMediaAccountAPI: typeof import('./src/apis/mediaAccount/index')['getMediaAccountAPI']
const getMediaAccountListAPI: typeof import('./src/apis/mediaAccount/index')['getMediaAccountListAPI']
const getRealTime: typeof import('./src/apis/index')['getRealTime']
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 getUserById: typeof import('./src/apis/user/index')['getUserById']
const getUserList: typeof import('./src/apis/user/index')['getUserList']
const getVideoInfo: typeof import('./src/utils/upload')['getVideoInfo']
const intercept: typeof import('mobx')['intercept']
const isEmail: typeof import('./src/utils/REG')['isEmail']
const isNothing: typeof import('./src/utils/index')['isNothing']
......@@ -81,6 +99,7 @@ declare global {
const request: typeof import('./src/utils/request')['default']
const runInAction: typeof import('mobx')['runInAction']
const smartFenToYuan: typeof import('./src/utils/money')['smartFenToYuan']
const splitStrBy: typeof import('./src/utils/string')['splitStrBy']
const startTransition: typeof import('react')['startTransition']
const theme: typeof import('antd')['theme']
const toJS: typeof import('mobx')['toJS']
......@@ -120,6 +139,12 @@ declare global {
}
// for type re-export
declare global {
// @ts-ignore
export type { mediaSearchP, mediaAccountList, mediaAccount, changeParams } from './src/apis/mediaAccount/type'
import('./src/apis/mediaAccount/type')
// @ts-ignore
export type { paginationType } from './src/apis/type'
import('./src/apis/type')
// @ts-ignore
export type { UserEntity } from './src/apis/user/type'
import('./src/apis/user/type')
......
......@@ -3,6 +3,7 @@ import { Suspense } from 'react'
import { StyleProvider } from '@ant-design/cssinjs'
import routes from '~react-pages'
import { ConfigProvider } from 'antd'
import zhCN from 'antd/lib/locale/zh_CN'
import Layouts from './layouts/index.tsx'
......@@ -10,7 +11,7 @@ const App: React.FC = () => {
return (
<Suspense fallback={<Spin fullscreen />}>
<StyleProvider layer>
<ConfigProvider>
<ConfigProvider locale={zhCN}>
<Layouts>{useRoutes(routes)}</Layouts>
</ConfigProvider>
</StyleProvider>
......
export const getMediaAccountListAPI = (params: mediaSearchP) => {
return request.post<mediaSearchP, mediaAccountList[]>('/ab', params)
}
export const addMediaAccountAPI = (params: mediaAccount) => {
return request.post<mediaAccount, boolean>('/ab3', params)
}
export const getMediaAccountAPI = (id: number | string) => {
return request.post<number, mediaAccount>('/ab3', { id })
}
export const changeStatueByIdAPI = (params: changeParams) => {
return request.post<changeParams, boolean>('/ac', params)
}
export const changeAEByIdAPI = (params: changeParams) => {
return request.post<changeParams, boolean>('/abc', params)
}
export const changePEByIdAPI = (params: changeParams) => {
return request.post<changeParams, boolean>('/abcd', params)
}
export const changeAEListByIdAPI = (params: changeParams) => {
return request.post<changeParams, boolean>('/abcde', params)
}
export interface mediaSearchP extends paginationType {
mediaId: number | string
mediaName: string
AE: string
PE: string
}
export interface mediaAccountList {
mediaId: number | string
mediaName: string
mediaType: string
mediaC: string
mediaTime: string
AE: string
AEName: string
PE: string
statue: boolean
}
export interface mediaAccount {
mediaName: string
cName: string
pe: string
phone: string
email: string
account: string
pic1: string
pic2: string
}
export interface changeParams {
mediaId: number | string
statue?: boolean
AE?: string
PE?: string
AEList?: string[]
}
export interface paginationType {
pageSize: number
currentPage: number
}
import type { GetProp, UploadProps } from 'antd'
import { LoadingOutlined, PlusOutlined } from '@ant-design/icons'
import numeral from 'numeral'
type FileType = Parameters<GetProp<UploadProps, 'beforeUpload'>>[0]
const XCsrfToken = cookie.get('csrf_token') || ''
const defaultHeaders = {
'X-Csrf-Token': XCsrfToken,
}
interface isLoading {
loading: boolean
}
const UploadButton = (load: isLoading) => (
<button style={{ border: 0, background: 'none' }} type='button'>
{load.loading ? <LoadingOutlined /> : <PlusOutlined />}
{!load.loading && <div className='ant-upload-text'>点击上传</div>}
</button>
)
const ImgAccept = ['.jpg', '.jpeg', '.png']
const DefaultAccept = [...ImgAccept, '.mp4']
interface IDraggerUploadProps extends UploadProps {
value?: string
width?: number
height?: number
size?: number // 单位是mb
videoSize?: number // 单位是mb
videoDuration?: number // 单位秒s
duration?: number
displayZoomSize?: number // 默认预览图适配系数
autoZoomSize?: boolean // 预览图是否自动适配图片宽高
checkSize?: boolean //是否校验宽高
render?: () => React.ReactNode // 自定义默认的上传背景内容
getUploadUrl?: (url: string) => void // 上传成功后的参数回调
}
const DraggerUpload: React.FC<IDraggerUploadProps> = observer(
(
{
value,
width,
height,
checkSize = true,
size = 5, // 默认是5mb
videoSize,
videoDuration,
displayZoomSize = 1,
autoZoomSize = false,
getUploadUrl,
name = 'file',
action = 'https://ssp-manager.tuiatest.cn/upload/index',
data,
showUploadList = false,
accept = DefaultAccept,
beforeUpload = () => {
return true
},
render,
...restProps
},
ref
) => {
const [imgUrl, changeImgUrl] = useState(value)
const [loading, changeLoading] = useState(false)
const [messageApi, contextHolder] = message.useMessage()
useEffect(() => {
changeImgUrl(value)
}, [value])
const commonBeforeUpload: UploadProps['beforeUpload'] = async function (file: FileType, FileList: FileType[]) {
return new Promise<void>(async (resolve, reject) => {
// 默认校验
if (checkIsImage(file)) {
await getImgInfo(file).then(({ width: imgWidth, height: imgHeight, size: imgSize }) => {
const isWidthValid = width ? imgWidth === width : true
const isHeightValid = height ? imgHeight === height : true
if ((!isWidthValid || !isHeightValid) && checkSize) {
messageApi.error(height ? `请上传宽${width},高${height}的图片` : `请上传宽${width}的图片`)
return false
}
if (imgSize > size * 1024 * 1024) {
messageApi.error(`请上传不大于${numeral(size * 1024 * 1024).format('0b')}的图片`)
return false
}
return true
})
}
if (checkIsVideo(file)) {
await getVideoInfo(file).then(({ duration = 0 }) => {
if (videoSize && videoSize * 1024 * 1024 < file.size) {
messageApi.error(`请上传不大于${numeral(videoSize * 1024 * 1024).format('0b')}的视频`)
return false
}
if (videoDuration && videoDuration < duration) {
messageApi.error(`请上传不长于${videoDuration}s的视频`)
return false
}
})
}
// 自定义校验
const canUpload = await beforeUpload?.(file, FileList)
if (!canUpload) {
reject(new Error('上传失败'))
return
}
resolve()
changeLoading(true)
return true
})
}
// 不手动修改上传内容(联调后看看需不需要在添加)
const commonOnChange: UploadProps['onChange'] = (info) => {
console.log('info', info)
changeLoading(true)
const {
file: { status, response },
} = info
if (status === 'error') {
changeLoading(false)
changeImgUrl('http://yun.dui88.com/-rr2kxnz9cm-e1lpywd8t.png')
getUploadUrl?.('http://yun.dui88.com/-rr2kxnz9cm-e1lpywd8t.png')
messageApi.error(`${info.file.name} 上传失败,请重新上传。`)
}
// 接口
// if (status !== 'uploading') {
// if (response) {
// const { data, code, msg } = response
// if (code === 200) {
// getUploadUrl?.(data.url)
// changeLoading(false)
// } else {
// messageApi.error(msg)
// }
// }
// }
}
const displayStyle = useMemo(() => {
if (!width || !height) return {}
let zoomSize = displayZoomSize
if (autoZoomSize) {
if (width > 300) {
zoomSize = 0.5
}
if (width > 500) {
zoomSize = 0.3
}
}
return { width: width * zoomSize, height: height * zoomSize }
}, [width, height, autoZoomSize, displayZoomSize])
return (
<>
{contextHolder}
<Upload
ref={ref}
multiple={false}
name={name}
showUploadList={showUploadList}
action={action}
data={data}
headers={defaultHeaders}
accept={Array.isArray(accept) ? accept?.join(',') : accept}
disabled={loading || restProps.disabled}
{...restProps}
beforeUpload={commonBeforeUpload}
onChange={commonOnChange}>
{render ? (
imgUrl && !loading ? (
<div style={{ width: 192, margin: 16 }}>
<img src={value} alt='avatar' style={displayStyle} />
</div>
) : (
render()
)
) : (
<div style={displayStyle}>
{imgUrl && !loading ? <img src={imgUrl} style={displayStyle} /> : <UploadButton loading={loading} />}
</div>
)}
</Upload>
</>
)
},
{
forwardRef: true,
}
)
export default DraggerUpload
import List from '@components/mediaAccount/list.tsx'
import Search from '@components/mediaAccount/search.tsx'
import { mediaAccountStore } from './store.ts'
const MediaAccountIndex = observer(() => {
const { searchParams } = mediaAccountStore
// 接口获取数据
const {
data = [
{
mediaId: 1,
mediaName: 'test',
mediaType: 'test',
mediaC: 'test',
mediaTime: 'test',
AE: 'test',
AEName: 'test,test2',
PE: 'test',
statue: true,
},
{
mediaId: 2,
mediaName: 'test2',
mediaType: 'test2',
mediaC: 'test2',
mediaTime: 'test2',
AE: 'test2',
AEName: 'test1,test2',
PE: 'test2',
statue: false,
},
],
run,
loading,
} = useRequest(getMediaAccountListAPI, { defaultParams: [searchParams] })
return (
<Spin spinning={loading}>
<Space direction='vertical' style={{ display: 'flex' }} size='middle'>
<Search searchRun={run} />
<List data={data} searchRun={run} />
</Space>
</Spin>
)
})
export default MediaAccountIndex
import { mediaAccountStore } from './store.ts'
const AddModal: React.FC = observer(() => {
const { isModalOpen: open, setModalOpen, addMediaAccountFc, mediaAccount, setMediaAccount } = mediaAccountStore
const [form] = Form.useForm()
const [imageUrl, setImageUrl] = useState<string>()
const [imageUrl2, setImageUrl2] = useState<string>()
const handleCancel = () => {
setModalOpen(false)
}
const handleOk = async (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
e.preventDefault()
form
.validateFields()
.then((values) => {
console.log(values)
addMediaAccountFc({ ...values })
handleCancel()
})
.catch((err) => {
console.error('Validation Failed:', err)
})
}
useEffect(() => {
if (open) {
form.setFieldsValue(mediaAccount)
} else {
// ?
setTimeout(() => {
form.resetFields()
setMediaAccount(undefined)
}, 100)
}
}, [mediaAccount, open])
return (
<Modal title='创建媒体' open={open} onOk={handleOk} onCancel={handleCancel} okText='提交审核'>
<Form
form={form}
labelCol={{ flex: '110px' }}
wrapperCol={{ flex: 1 }}
labelAlign='left'
labelWrap
colon={false}
style={{ maxWidth: 600 }}>
<Form.Item label='媒体名称' name='mediaName' rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.Item label='公司名称' name='cName' rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.Item label='对接人姓名' name='pe' rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.Item label='对接人电话' name='phone' rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.Item label='对接人邮箱' name='email' rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.Item label='收款账户信息' name='account' rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.Item label='营业执照' name='pic1' rules={[{ required: true }]}>
<DraggerUpload
value={imageUrl}
data={{ id: 1, name: 'yhcj' }}
getUploadUrl={(url: string) => {
form.setFieldValue('pic1', url)
setImageUrl(url)
}}
/>
</Form.Item>
<Form.Item label='基本账户信息(开户许可证)' name='pic2' rules={[{ required: true }]}>
<DraggerUpload
value={imageUrl2}
data={{ id: 1, name: 'yhcj' }}
getUploadUrl={(url: string) => {
form.setFieldValue('pic2', url)
setImageUrl2(url)
}}
/>
</Form.Item>
</Form>
</Modal>
)
})
export default AddModal
.search-form {
display: flex;
}
\ No newline at end of file
import type { TableColumnsType } from 'antd'
import AddModal from './addAccount.tsx'
import { mediaAccountStore } from './store.ts'
interface ListProp {
data: mediaAccountList[]
searchRun: (param: mediaSearchP) => void
}
const List: React.FC<ListProp> = observer((prop: ListProp) => {
const { data, searchRun } = prop
const { setModalOpen, searchParams, getMediaAccountFc } = mediaAccountStore
const editAddModal = (id: number | string) => {
setModalOpen(true)
getMediaAccountFc(id)
}
const handlePageChange = async (page: number, pageSize: number) => {
const param = {
...searchParams,
pageSize: pageSize,
currentPage: page,
}
searchRun(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> = [
{
title: '媒体ID',
dataIndex: 'mediaId',
key: 'mediaId',
},
{
title: '媒体名称',
dataIndex: 'mediaName',
key: 'mediaName',
},
{
title: '媒体类型',
dataIndex: 'mediaType',
key: 'mediaType',
},
{
title: '媒体接入方式',
dataIndex: 'mediaC',
key: 'mediaC',
},
{
title: '媒体接入时间',
dataIndex: 'mediaTime',
key: 'mediaTime',
},
{
title: '所属主运营',
dataIndex: 'AE',
key: 'AE',
render: (AE, record) => {
return (
<Space size='middle'>
{flag ? (
<Select
defaultValue={AE}
style={{ width: 120 }}
onChange={async (value) => {
await changeAE(record.mediaId, value)
changeFlag(false)
}}>
<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>
)
},
},
{
title: '其他运营',
dataIndex: 'AEName',
key: 'AEName',
render: (AEName, record) => {
return (
<Space size='middle'>
{flag ? (
<Select
mode='multiple'
defaultValue={AEName?.split(',')}
style={{ width: 120 }}
onChange={async (value) => {
await changeAEList(record.mediaId, value)
changeFlag(false)
}}>
<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>
)
},
},
{
title: '所属媒介',
dataIndex: 'PE',
key: 'PE',
render: (PE, record) => {
return (
<Space size='middle'>
{flag ? (
<Select
defaultValue={PE}
style={{ width: 120 }}
onChange={async (value) => {
await changePE(record.mediaId, value)
changeFlag(false)
}}>
<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>
)
},
},
{
title: '审核状态',
dataIndex: 'statue',
key: 'statue',
render: (statue, record) => (
<Space size='middle'>
<Switch checked={statue} onChange={(checked: boolean) => changeStatue(record.mediaId, checked)} />
</Space>
),
},
{
title: '操作',
key: 'action',
render: (_, record) => (
<Space size='middle'>
<a onClick={() => editAddModal(record.mediaId)}>设置</a>
</Space>
),
},
]
return (
<>
<Table<mediaAccountList>
dataSource={data}
columns={columns}
pagination={{ pageSizeOptions: [10, 20, 50], showQuickJumper: true, total: 500, onChange: handlePageChange }}
/>
<AddModal />
</>
)
})
export default List
const { Item: FormItem } = Form
import { mediaAccountStore } from './store'
interface SearchProp {
searchRun: (param: mediaSearchP) => void
}
const Search: React.FC<SearchProp> = observer((prop: SearchProp) => {
const [form] = Form.useForm()
const { searchRun } = prop
const { setModalOpen, searchParams, setSearchParams } = mediaAccountStore
function handleSearch(e: React.MouseEvent<HTMLElement, MouseEvent>) {
e.preventDefault()
form
.validateFields()
.then((values) => {
setSearchParams({ ...values })
searchRun({ ...searchParams, ...values })
})
.catch((err) => {
console.error('Validation Failed:', err)
})
}
const handleAdd = () => {
setModalOpen(true)
}
return (
<Form className='fill' layout='inline' form={form}>
<Flex className='fill' gap='small' justify='flex-start' wrap>
<FormItem label='媒体ID' name='mediaId' rules={[Rules.ids]}>
<Input style={{ width: 200 }} placeholder='请输入媒体ID间隔用,隔开' />
</FormItem>
<FormItem label='媒体名称' name='mediaName' rules={[Rules.name]}>
<Input style={{ width: 200 }} placeholder='请输入媒体名称' />
</FormItem>
<FormItem label='运营' name='AE' rules={[Rules.name]}>
<Input style={{ width: 200 }} placeholder='请输入运营' />
</FormItem>
<FormItem label='媒介' name='PE' rules={[Rules.name]}>
<Input style={{ width: 200 }} placeholder='请输入媒介' />
</FormItem>
</Flex>
<Flex className='fill' justify='flex-end'>
<FormItem layout='vertical' style={{ paddingTop: '10px', display: 'flex', justifyContent: 'flex-end' }}>
<Button type='primary' onClick={handleAdd}>
创建媒体
</Button>
</FormItem>
<FormItem layout='vertical' style={{ paddingTop: '10px', display: 'flex', justifyContent: 'flex-end' }}>
<Button
type='primary'
htmlType='submit'
onClick={(e: React.MouseEvent<HTMLElement, MouseEvent>) => handleSearch(e)}>
搜索
</Button>
</FormItem>
</Flex>
</Form>
)
})
export default Search
class MediaAccountStore {
isModalOpen = false
searchParams: mediaSearchP = {
mediaId: '',
mediaName: '',
AE: '',
PE: '',
pageSize: 15,
currentPage: 1,
}
mediaAccount: mediaAccount | undefined = {
mediaName: '',
cName: '',
pe: '',
phone: '',
email: '',
account: '',
pic1: '',
pic2: '',
}
constructor() {
makeAutoObservable(this) // 自动推断类型
}
getModalOpen = () => {
return this.isModalOpen
}
setModalOpen = (value: boolean) => {
this.isModalOpen = value
}
setSearchParams = (value: mediaSearchP) => {
this.searchParams = { ...this.searchParams, ...value }
}
setMediaAccount = (value: mediaAccount | undefined) => {
this.mediaAccount = value ? { ...value } : undefined
}
addMediaAccountFc = (params: mediaAccount) => {
console.log('params', params)
const res = addMediaAccountAPI(params)
console.log(res)
}
getMediaAccountFc = (id: number | string) => {
const res = getMediaAccountAPI(id)
console.log(res)
this.setMediaAccount({
mediaName: 'text',
cName: 'text',
pe: 'text',
phone: 'text',
email: 'text',
account: 'text',
pic1: 'http://yun.dui88.com/-rr2kxnz9cm-e1lpywd8t.png',
pic2: 'http://yun.dui88.com/-rr2kxnz9cm-e1lpywd8t.png',
})
}
}
export const mediaAccountStore = new MediaAccountStore()
......@@ -2,4 +2,12 @@
@layer theme, base, components, antd, untilities;
@import 'tailwindcss';
@import 'antd/dist/reset.css';
\ No newline at end of file
@import 'antd/dist/reset.css';
.fill {
width: 100%;
padding: 0;
margin: 0;
}
interface AddAccountProps {
open: boolean
handleClose: () => void
}
const AddModal: React.FC<AddAccountProps> = observer((prop: AddAccountProps) => {
const { handleClose, open } = prop
const handleOk = () => {
handleClose()
}
const handleCancel = () => {
handleClose()
}
return (
<Modal title='Basic Modal' open={open} onOk={handleOk} onCancel={handleCancel}>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</Modal>
)
})
export default AddModal
import List from './list.tsx'
import Search from './search.tsx'
import MediaAccountIndex from '@components/mediaAccount/index.tsx'
const StatementTypeIn = observer(() => {
return (
<Space direction='vertical' style={{ display: 'flex' }} size='middle'>
<Search />
<List />
</Space>
)
const Index = observer(() => {
return <MediaAccountIndex />
})
export default StatementTypeIn
export default Index
import type { TableProps } from 'antd'
import AddModal from './addAccount.tsx'
interface DataType {
key: string
name: string
age: number
address: string
}
const List: React.FC = observer(() => {
const [isModalOpen, setIsModalOpen] = useState(false)
const handleShowAddModal = () => {
setIsModalOpen(true)
}
const columns: TableProps<DataType>['columns'] = [
{
title: '姓名',
dataIndex: 'name',
key: 'name',
},
{
title: '年龄',
dataIndex: 'age',
key: 'age',
},
{
title: '住址',
dataIndex: 'address',
key: 'address',
},
{
title: 'Action',
key: 'action',
render: () => (
<Space size='middle'>
<a onClick={handleShowAddModal}>查看</a>
</Space>
),
},
]
const dataSource: DataType[] = [
{
key: '1',
name: '胡彦斌',
age: 32,
address: '西湖区湖底公园1号',
},
{
key: '2',
name: '胡彦祖',
age: 42,
address: '西湖区湖底公园1号',
},
]
return (
<>
<Table dataSource={dataSource} columns={columns} />
<AddModal open={isModalOpen} handleClose={() => setIsModalOpen(false)} />
</>
)
})
export default List
const { Item: FormItem } = Form
const Search: React.FC = observer(() => {
const [form] = Form.useForm()
function handleSearch(e: React.MouseEvent<HTMLElement, MouseEvent>) {
e.preventDefault()
form
.validateFields()
.then((values) => {
values.dealIdList = values.dealIdList?.split(',')
console.log('Received values of form: ', values)
})
.catch((err) => {
console.error('Validation Failed:', err)
})
}
return (
<Form layout='inline' form={form}>
<FormItem name='dealIdList' rules={[Rules.ids]}>
<Input style={{ width: 200 }} placeholder='请输入排期单ID间隔用,隔开' />
</FormItem>
<FormItem name='dealName' rules={[Rules.name]}>
<Input style={{ width: 200 }} placeholder='请输入排期单名称' />
</FormItem>
<FormItem>
<Button
type='primary'
htmlType='submit'
onClick={(e: React.MouseEvent<HTMLElement, MouseEvent>) => handleSearch(e)}>
搜索
</Button>
</FormItem>
</Form>
)
})
export default Search
......@@ -70,6 +70,10 @@ const Rules = {
pattern: REGEX.POSITIVE_INTEGER,
message: '请输入正整数',
},
nonSpecialChar: {
pattern: REGEX.NON_SPECIAL_CHARACTERS,
message: '请输入非特殊字符',
},
intLt1e100: {
pattern: /^([1-9][0-9]{0,1}|100)$/,
message: '请输入1-100整数',
......@@ -91,6 +95,7 @@ const Rules = {
pattern: /^(\d|[1-9]\d|100)(\.\d{1,2})?$/,
message: '请输入正确的百分比',
},
}
export { isEmail, buildREG, Rules }
export function isNothing(value: number | void | string | null) {
export function isNothing(value: number | void | string | null | Array<string | number>) {
return (
value === '' ||
value === undefined ||
......
export function splitStrBy(str: string | string[], separator = ',') {
if (isNothing(str)) return []
if (Array.isArray(str)) return str
return str.split(separator).filter(Boolean)
}
interface Video {
error?: boolean
duration?: number
width?: number
height?: number
type?: string
size?: number
}
interface Image {
width: number
height: number
size: number
}
export const getVideoInfo: (file: File) => Promise<Video> = (file) => {
const videoDOM = document.createElement('video') as HTMLVideoElement
videoDOM.src = URL.createObjectURL(file)
const evt = 'loadedmetadata'
return new Promise((resolve) => {
videoDOM.onerror = () => {
resolve({ error: true })
}
videoDOM.addEventListener(evt, function () {
const { videoWidth: width, videoHeight: height } = this
resolve({
duration: videoDOM.duration || 0,
type: 'MP4',
size: file.size,
width,
height,
})
})
})
}
export const getImgInfo: (file: File) => Promise<Image> = (file) => {
return new Promise((res) => {
if (typeof FileReader === 'function') {
const { size } = file
// 读取图片数据
const reader = new FileReader()
reader.onload = function (e: ProgressEvent<FileReader>) {
const data = e.target?.result
// 加载图片获取图片真实宽度和高度
const image = new Image()
image.onload = function () {
const width = image.width
const height = image.height
res({ width, height, size })
}
image.src = data as string
}
reader.readAsDataURL(file)
} else {
console.log('你的浏览器不支持图片大小判断,请更换现代浏览器,例如Chrome')
}
})
}
const videoSnapshotSuffix = '?x-oss-process=video/snapshot,t_1000,m_fast,w_200'
export const getFileUrl = (fileUrl: string) => {
if (fileUrl?.match(/.mp4$/) && !fileUrl?.includes(videoSnapshotSuffix)) {
return fileUrl + videoSnapshotSuffix
}
return fileUrl
}
export const getSuffix = (url: string) => {
return url?.replace(/.+\./, '')
}
export const checkIsVideo = (file: File): boolean => {
return /.mp4/.test(file.name)
}
export const checkIsImage = (file: File): boolean => {
return /.jpg|.jpeg|.png|.gif/.test(file.name)
}
......@@ -20,7 +20,14 @@
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
"noUncheckedSideEffectImports": true,
"paths": {
"@/*": ["./src/*"],
"@assets/*": ["./src/assets/*"],
"@components/*": ["./src/components/*"],
"@hooks/*": ["./src/hooks/*"],
"@styles/*": ["./src/styles/*"],
}
},
"include": [
"src",
......
......@@ -62,6 +62,9 @@ export default defineConfig(({ mode }) => ({
'Radio',
'Switch',
'Upload',
'Flex',
'Row',
'Col',
],
axios: [['default', 'axios']],
'pixiu-number-toolkit': ['REGEX'],
......@@ -97,4 +100,14 @@ export default defineConfig(({ mode }) => ({
target: `/${PUBLIC_FOLDER}/assets/`,
}),
],
resolve: {
alias: {
'@': '/src',
'@components': '/src/components',
'@hooks': '/src/hooks',
'@stores': '/src/stores',
'@apis': '/src/apis',
'@constants': '/src/constants',
},
},
}))
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