Browse Source

fix:路由和页面

lidongsheng 1 year ago
parent
commit
f8fa314d4f
27 changed files with 950 additions and 34 deletions
  1. 16 0
      src/components/FilterHeaderLayout/index.module.css
  2. 22 0
      src/components/FilterHeaderLayout/index.tsx
  3. 4 2
      src/components/Nav/index.tsx
  4. 8 8
      src/lib/http/api.ts
  5. 1 1
      src/pages/Login/index.tsx
  6. 4 0
      src/pages/Manage/components/AdManage/components/ContentTable/index.module.css
  7. 88 0
      src/pages/Manage/components/AdManage/components/ContentTable/index.tsx
  8. 50 0
      src/pages/Manage/components/AdManage/components/ContentTable/types.d.ts
  9. 9 0
      src/pages/Manage/components/AdManage/components/CreateModal/index.module.css
  10. 118 0
      src/pages/Manage/components/AdManage/components/CreateModal/index.tsx
  11. 28 0
      src/pages/Manage/components/AdManage/components/CreateModal/types.d.ts
  12. 31 0
      src/pages/Manage/components/AdManage/components/HeaderFilter/index.tsx
  13. 5 0
      src/pages/Manage/components/AdManage/const.ts
  14. 72 1
      src/pages/Manage/components/AdManage/index.tsx
  15. 4 0
      src/pages/Manage/components/AppManage/components/ContentTable/index.module.css
  16. 102 0
      src/pages/Manage/components/AppManage/components/ContentTable/index.tsx
  17. 49 0
      src/pages/Manage/components/AppManage/components/ContentTable/types.d.ts
  18. 9 0
      src/pages/Manage/components/AppManage/components/CreateModal/index.module.css
  19. 157 0
      src/pages/Manage/components/AppManage/components/CreateModal/index.tsx
  20. 25 0
      src/pages/Manage/components/AppManage/components/CreateModal/types.d.ts
  21. 31 0
      src/pages/Manage/components/AppManage/components/HeaderFilter/index.tsx
  22. 4 0
      src/pages/Manage/components/AppManage/const.ts
  23. 77 2
      src/pages/Manage/components/AppManage/index.tsx
  24. 9 2
      src/pages/Manage/index.module.css
  25. 7 6
      src/pages/Manage/index.tsx
  26. 0 0
      types/home/index.d.ts
  27. 20 12
      types/index.d.ts

+ 16 - 0
src/components/FilterHeaderLayout/index.module.css

@@ -0,0 +1,16 @@
+.filter-header-layout {
+    width: 100%;
+    display: flex;
+    justify-content: space-between;
+    
+    .left {
+        margin-right: 16px;
+        /* background: wheat; */
+        flex-grow: 1;
+    }
+
+    .right {
+        /* background: antiquewhite; */
+        flex-shrink: 0;
+    }
+}

+ 22 - 0
src/components/FilterHeaderLayout/index.tsx

@@ -0,0 +1,22 @@
+import { ReactNode } from 'react'
+import styles from './index.module.css'
+
+interface Props {
+  leftComponent?: ReactNode, 
+  rightComponent?: ReactNode
+}
+
+export default function FilterHeaderLayout({ leftComponent, rightComponent }: Props) {    
+  return (
+    <>
+      <div className={styles['filter-header-layout']}>
+        <div className={styles['left']}>
+          { leftComponent }
+        </div>
+        <div className={styles['right']}>
+          { rightComponent }
+        </div>
+      </div>
+    </>
+  )
+}

+ 4 - 2
src/components/Nav/index.tsx

@@ -16,7 +16,7 @@ const { confirm } = Modal
 
 function Nav() {
   const [showModal, setShowModal] = useState(false)
-  const [current, setCurrent] = useState('/ad-manage')
+  const [current, setCurrent] = useState('/manage')
   const [userInfo, setUserInfo] = useState<UserInfoType>()
   const location = useLocation()
   const setUserState = useGlobalStateUpdate()
@@ -28,6 +28,8 @@ function Nav() {
   function fetchUserInfo() {
     http.get(getAgencyInfo).then(res => {
       const { data } = res
+      console.log(1111,data)
+      
       setUserInfo(data as UserInfoType)
       setUserState(data as UserInfoType)
     })
@@ -112,7 +114,7 @@ function Nav() {
             <Dropdown menu={{ items }} trigger={['click']}>
               <a className={styles['user-name']} onClick={(e) => e.preventDefault()}>
                 <Space>
-                  { userInfo?.company }
+                  { userInfo?.companyName }
                   <CaretDownOutlined />
                 </Space>
               </a>

+ 8 - 8
src/lib/http/api.ts

@@ -1,25 +1,25 @@
 // Login
 
 // 登录
-export const userLogin = `${import.meta.env.VITE_API_URL}/ad/platform/user/login`
+export const userLogin = `${import.meta.env.VITE_API_URL}/ad/union/user/login`
 
-// 获取验证码
+// 获取验证码 TODO
 export const sendVerificationCode = `${import.meta.env.VITE_API_URL}/ad/platform/user/sendVerificationCode`
 
-// 手机号登录
+// 手机号登录 TODO
 export const loginPhone = `${import.meta.env.VITE_API_URL}/ad/platform/user/login/phone`
 
-// 忘记密码
+// 忘记密码 TODO
 export const forgotPassword = `${import.meta.env.VITE_API_URL}/ad/platform/user/forgotPassword`
 
 // 登出
-export const userLogout = `${import.meta.env.VITE_API_URL}/ad/platform/user/logout`
+export const userLogout = `${import.meta.env.VITE_API_URL}/ad/union/user/logout`
 
 // 修改密码
-export const changePassword = `${import.meta.env.VITE_API_URL}/ad/platform/user/changePassword`
+export const changePassword = `${import.meta.env.VITE_API_URL}/ad/union/user/changePassword`
 
-// 获取用户信息
-export const getAgencyInfo = `${import.meta.env.VITE_API_URL}/ad/platform/user/getAgencyInfo`
+// 获取用户信息 
+export const getAgencyInfo = `${import.meta.env.VITE_API_URL}/ad/union/user/findUserDetailByLogin`
 
 export default {
 }

+ 1 - 1
src/pages/Login/index.tsx

@@ -19,7 +19,7 @@ export default function Login() {
       router.navigate(decodeURIComponent(searchParams.get('redirectUrl') as string))
     }else{
       sessionStorage.removeItem('advertiserId')
-      router.navigate('/index')
+      router.navigate('/')
     }
   }
 

+ 4 - 0
src/pages/Manage/components/AdManage/components/ContentTable/index.module.css

@@ -0,0 +1,4 @@
+.pagination{
+    float: right;
+    margin-top: 16px;
+}

+ 88 - 0
src/pages/Manage/components/AdManage/components/ContentTable/index.tsx

@@ -0,0 +1,88 @@
+import { Table, Pagination, Switch } from 'antd'
+import type { ColumnsType } from 'antd/es/table'
+import type { PropsType, RowDataType, PaginationOnChangeType, TableOnChangeType } from './types'
+import { useEffect, useState } from 'react'
+import { throttle } from 'lodash'
+import styles from './index.module.css'
+
+export default function ContentTable ({
+  tableData,
+  total,
+  pageNumber,
+  pageSize,
+  onTableChange,
+  onPaginationChange,
+  onSwitchStatus,
+}:PropsType) {
+  const [height, setHeight] = useState(500)
+  useEffect(()=>{
+    const otherHeight = 60 + 64 + 48 + 48 + 55 // 预留的高度
+    setHeight(window.innerHeight - otherHeight)
+    const resizeEvent =throttle((e)=>{
+      setHeight(e.target.innerHeight - otherHeight)
+    },300)
+    window.addEventListener('resize',resizeEvent)
+    return ()=> window.removeEventListener('resize',resizeEvent)
+  },[])
+ 
+  const columns:ColumnsType<RowDataType> = [
+    {
+      title: '广告名称',
+      dataIndex: 'unionAdName',
+    },
+    {
+      title: '广告ID',
+      dataIndex: 'unionAdId',
+    },
+    {
+      title: '所属应用',
+      dataIndex: 'unionAppName',
+    },
+    {
+      title: '广告类型',
+      dataIndex: 'unionAdPosition',
+    },
+    {
+      title: '状态',
+      dataIndex: 'status',
+      render:(v,row) => (
+        <Switch checked={v} onChange={(checked) => onSwitchStatus({checked,row}) }/>
+      )
+    },
+    {
+      title: '创建时间',
+      dataIndex: 'createTime',
+    }
+  ]
+
+  const paginationChange:PaginationOnChangeType = (pageNumber, pageSize) => {
+    onPaginationChange?.({pageNumber, pageSize})
+  }
+
+  const tableChnage:TableOnChangeType = (_pagination, filters, sorter, extra) => {
+    onTableChange?.({filters, sorter, extra})
+  }
+
+  return (
+    <>
+      <Table 
+        rowKey='id'
+        columns={ columns } 
+        dataSource={ tableData } 
+        pagination={ false }
+        scroll={{y: height}}
+        onChange={ tableChnage }
+      />
+      <Pagination 
+        className={styles['pagination']}
+        showSizeChanger 
+        disabled={!total}
+        current={ pageNumber}
+        pageSize={pageSize}
+        total={total} 
+        showTotal={ (total) => `共${total}条记录` } 
+        onChange={ paginationChange }
+      />
+    </>
+  )
+}

+ 50 - 0
src/pages/Manage/components/AdManage/components/ContentTable/types.d.ts

@@ -0,0 +1,50 @@
+import type { FilterValue, SorterResult, TableCurrentDataSource } from 'antd/es/table/interface'
+import type { TablePaginationConfig } from 'antd/es/table'
+
+export interface PropsType {
+    tableData: RowDataType[]
+    total: number,
+    pageNumber?: number,
+    pageSize?: number,
+    onTableChange?: EventFunctionType<[TableChangeParamsType]>,
+    onPaginationChange?: EventFunctionType<[PaginationParamsType]>,
+    onSwitchStatus: EventFunctionType<[{checked:boolean,row:RowDataType}]>,
+    // onEditRow: EventFunctionType<[RowDataType]>,
+    // onDeleteRow: EventFunctionType<[RowDataType]>
+}
+
+//  表格行数据类型
+export interface RowDataType  {
+    id: number,
+    unionAdName: string, // 广告名称
+    unionAdId: string, // 广告ID
+    unionAdPosition: number, // 广告类型
+    unionAppName: number, // 所属应用名称
+    applicationId: number, // 所属应用id
+    status: number, // 状态
+    createTime: string, // 创建时间
+}
+
+//  onTableChange事件参数类型
+export interface TableChangeParamsType {
+    filters: Record<string, FilterValue| null>, 
+    sorter: SorterResult<RowDataType> | SorterResult<RowDataType>[], 
+    extra: TableCurrentDataSource<RowDataType>
+}
+
+// onPaginationChange事件参数类型
+export interface PaginationParamsType {
+    pageNumber: number,
+    pageSize: number,
+}
+
+// ant Table 的onChange事件类型
+export type TableOnChangeType = EventFunctionType<[
+    TablePaginationConfig,
+    Record<string, FilterValue| null>,
+    SorterResult<RowDataType> | SorterResult<RowDataType>[],
+    TableCurrentDataSource<RowDataType>
+]>
+
+//  ant Pagination 的onChange事件类型
+export type PaginationOnChangeType = EventFunctionType<[number, number]>

+ 9 - 0
src/pages/Manage/components/AdManage/components/CreateModal/index.module.css

@@ -0,0 +1,9 @@
+.custom-modal {
+
+    :global {
+        .ant-modal-header {
+            margin-bottom: 24px;
+        }
+    }
+
+}

+ 118 - 0
src/pages/Manage/components/AdManage/components/CreateModal/index.tsx

@@ -0,0 +1,118 @@
+import { Modal, Form, Select, Input, Radio } from 'antd'
+import type { PropsType, Action, FormDataType, OptionsItemType, RadioOptionsType } from './types'
+import { forwardRef, useImperativeHandle, useState } from 'react'
+import type { RowDataType } from '../ContentTable/types'
+import { adTypes } from '../../const'
+import styles from './index.module.css'
+
+const { Item, useForm } = Form
+const adTypesOptions:RadioOptionsType = Object.entries(adTypes).map(([value,label])=>({value: +value,label}))
+const initialValues = {
+  unionAdPosition: 0, // 广告类型
+}
+
+console.log(adTypesOptions)
+
+const CreateModal = forwardRef(({ onCreate, onUpdate }:PropsType, ref) => {
+  const [isOpen, setIsOpen] = useState(false)
+  const [action, setAction] = useState<Action>()
+  const [form] = useForm()
+  const [editRow, setEditRow] = useState<RowDataType>()
+  const isEdit = action === 'edit'
+  const title = isEdit ? '编辑广告' : '新建广告'
+  const allAppOptions:OptionsItemType[] = []
+
+  useImperativeHandle(ref,()=>({
+    open: (editRow?:RowDataType) => {
+      setAction(editRow?'edit':'create')
+      setEditRow(editRow)
+      if (editRow) {
+        const {
+          applicationId, // 应用id
+          unionAdName, // 广告名称
+          unionAdPosition // 广告类型
+        } =  editRow 
+        form.setFieldsValue({
+          applicationId,
+          unionAdName,
+          unionAdPosition 
+        })
+      }
+      setIsOpen(true)
+    }
+  }))
+
+ 
+
+  const onOk = async () => {
+    try {
+      const res:FormDataType =  await form.validateFields()
+      switch (action) {
+        case 'create':
+          onCreate(res, ()=>{setIsOpen(false)})
+          break
+        case 'edit':
+          onUpdate({...(editRow as RowDataType), ...res},()=>{setIsOpen(false)})
+      }
+    } catch (error) { /* empty */ }
+  }
+
+  const onCancel = () => {
+    setIsOpen(false)
+  }
+
+  const afterOpenChange = (isOpen: boolean) => {
+    if (!isOpen) {
+      form.resetFields()
+    }
+  }
+
+  return (
+    <Modal
+      className={styles['custom-modal']}
+      centered={true}
+      open={isOpen}
+      title={title}
+      width={500}
+      onCancel={onCancel}
+      onOk={onOk}
+      afterOpenChange={afterOpenChange}
+    >
+      <Form
+        form={form}
+        initialValues={initialValues}
+        labelCol={{ span: 5 }}
+        wrapperCol={{ span: 18, offset: 1}}
+        colon={ false }
+        requiredMark={ false }
+      >
+        <Item
+          name='applicationId'
+          label='应用'
+          rules={[{ required: true, message: '请选择应用' }]}
+        >
+          <Select 
+            placeholder='请选择应用'
+            options={allAppOptions}
+          />
+        </Item>
+        <Item
+          name='unionAdName'
+          label='广告名称'
+          rules={[{ required: true, message: '请输入广告名称' }]}
+        >
+          <Input placeholder='请输入广告名称' showCount maxLength={ 20 } />
+        </Item>
+        <Item
+          name='unionAdPosition'
+          label='广告类型'
+          rules={[{ required: true, message: '请选择广告类型' }]}
+        >
+          <Radio.Group options={adTypesOptions} />
+        </Item>
+      </Form>
+    </Modal>
+  )
+})
+
+export default CreateModal

+ 28 - 0
src/pages/Manage/components/AdManage/components/CreateModal/types.d.ts

@@ -0,0 +1,28 @@
+import { RowDataType } from '../ContentTable/types'
+
+export interface PropsType {
+    onCreate: EventFunctionType<[FormDataType,EventFunctionType<[]>]>,
+    onUpdate: EventFunctionType<[RowDataType,EventFunctionType<[]>]>
+}
+
+export interface FormDataType {
+    applicationId: number, // 应用id
+    unionAdName: string, // 广告名称
+    unionAdPosition: number, // 广告类型
+}
+
+export type Action = 'create' | 'edit' 
+
+export interface RefType {
+    open: EventFunctionType<[RowDataType?]>
+}
+
+// select 组件选项类型
+export interface OptionsItemType {
+    label: string,
+    value: string | number,
+    disabled?: boolean
+}
+
+// ant Radio 选项类型
+export type RadioOptionsType = string[] | number[] | Array<{ label: ReactNode; value: string|number|boolean; disabled?: boolean; }>

+ 31 - 0
src/pages/Manage/components/AdManage/components/HeaderFilter/index.tsx

@@ -0,0 +1,31 @@
+import { PlusOutlined } from '@ant-design/icons'
+import { Button } from 'antd'
+import FilterHeaderLayout from '@src/components/FilterHeaderLayout'
+import { debounce } from 'lodash'
+
+interface PropsType {
+  onCreate: EventFunctionType<[]>
+}
+
+export default function HeaderFilter({ onCreate }:PropsType) {
+
+  const createApp = debounce(() => {
+    onCreate()
+  }, 1000, {leading: true, trailing: false})
+
+  return (
+    <div style={{marginBottom:'16px'}}>
+      <FilterHeaderLayout 
+        rightComponent={
+          <>
+            <Button
+              type='primary'
+              icon={<PlusOutlined />} 
+              onClick={createApp}
+            >新建广告</Button>
+          </>
+        }
+      />
+    </div>
+  )
+}

+ 5 - 0
src/pages/Manage/components/AdManage/const.ts

@@ -0,0 +1,5 @@
+export const adTypes:MapType<string> =  {
+  0: '视频插屏广告'
+}
+
+

+ 72 - 1
src/pages/Manage/components/AdManage/index.tsx

@@ -1,7 +1,78 @@
+import { useEffect, useRef, useState } from 'react'
+import HeaderFilter from './components/HeaderFilter'
+import ContentTable from './components/ContentTable'
+import CreateModal from './components/CreateModal'
+import type { 
+  RowDataType, PaginationParamsType, 
+} from './components/ContentTable/types'
+import type { RefType, FormDataType } from './components/CreateModal/types'
+import { useGlobalStateUpdate } from '@src/store/globalStates/loadingState'
+
+
 export function AdManage(){
+  const [tableData, setTableData] = useState<RowDataType[]>([])
+  const [total, setTotal] = useState(0)
+  const [paginationParams, setPaginationParams] = useState<PaginationParamsType>({ pageNumber: 1, pageSize: 10})
+  const createModalRef = useRef<RefType>()
+  const setloading = useGlobalStateUpdate()
+
+  useEffect(()=>{
+    getTableData()   
+  },[paginationParams])
+
+  const  getTableData = async () => {
+    // TODO 获取数据
+
+    // setloading(true)
+    // const data = await getPlanList(mergeRequestParams(paginationParams, filterParams))
+    // if (data) {
+    //   setTableData(data.objs)
+    //   setTotal(data.totalSize)
+    // }
+    // setloading(false)
+  }
+
+  const onPaginationChange =async (params: PaginationParamsType) => {
+    setPaginationParams(params)
+  }
+
+  const onCreate = () => {
+    createModalRef.current?.open()
+  }
+
+  const onSwitchStatus = ({row, checked}:{row:RowDataType, checked:boolean}) => {
+    console.log(row, checked)
+    // TODO 切换状态
+  }
+
+  const createAd = (form: FormDataType, cb:EventFunctionType<[]>) => {
+    console.log('创建',form)
+    // TODO 创建接口
+    cb()
+  }
+
+  const updateAd = (form: RowDataType,  cb:EventFunctionType<[]>) => {
+    console.log('更新',form)
+    // TODO 更新接口
+    cb()
+  }
+
   return (
     <>
-        广告列表
+      <HeaderFilter onCreate={onCreate} />
+      <ContentTable 
+        total={total} 
+        pageNumber={paginationParams.pageNumber}
+        pageSize={paginationParams.pageSize}
+        tableData={tableData} 
+        onPaginationChange = {onPaginationChange}
+        onSwitchStatus={onSwitchStatus}
+      />
+      <CreateModal 
+        ref={createModalRef}
+        onCreate={createAd}
+        onUpdate={updateAd}
+      />
     </>
   )
 }

+ 4 - 0
src/pages/Manage/components/AppManage/components/ContentTable/index.module.css

@@ -0,0 +1,4 @@
+.pagination{
+    float: right;
+    margin-top: 16px;
+}

+ 102 - 0
src/pages/Manage/components/AppManage/components/ContentTable/index.tsx

@@ -0,0 +1,102 @@
+import { Space, Table, Popconfirm, Pagination } from 'antd'
+import type { ColumnsType } from 'antd/es/table'
+import type { PropsType, RowDataType, PaginationOnChangeType, TableOnChangeType } from './types'
+import { useEffect, useState } from 'react'
+import { throttle } from 'lodash'
+import styles from './index.module.css'
+
+export default function ContentTable ({
+  tableData,
+  total,
+  pageNumber,
+  pageSize,
+  onTableChange,
+  onPaginationChange,
+  onEditRow,
+  onDeleteRow
+}:PropsType) {
+  const [height, setHeight] = useState(500)
+  useEffect(()=>{
+    const otherHeight = 60 + 64 + 48 + 48 + 55 // 预留的高度
+    setHeight(window.innerHeight - otherHeight)
+    const resizeEvent =throttle((e)=>{
+      setHeight(e.target.innerHeight - otherHeight)
+    },300)
+    window.addEventListener('resize',resizeEvent)
+    return ()=> window.removeEventListener('resize',resizeEvent)
+  },[])
+
+  const columns:ColumnsType<RowDataType> = [
+    {
+      title: '应用名称',
+      dataIndex: 'unionAppName',
+    },
+    {
+      title: '应用ID',
+      dataIndex: 'unionAppId',
+    },
+    {
+      title: '应用类型',
+      dataIndex: 'thirdAppType',
+    },
+    {
+      title: '状态',
+      dataIndex: 'status',
+    },
+    {
+      title: '创建时间',
+      dataIndex: 'createTime',
+    },
+    {
+      title: '操作',
+      key: 'action',
+      width: 80,
+      render: (_v,row) => {
+        return (
+          <Space size='middle' >
+            <a onClick={()=>{onEditRow(row)}}>编辑</a>
+            <Popconfirm 
+              title={`确定删除「${row.unionAppName}」这个应用?`}     
+              onConfirm={ ()=>{onDeleteRow(row)} }
+              okText="确认"
+              cancelText="取消"
+            >
+              <a>删除</a>
+            </Popconfirm>
+          </Space>
+        )
+      }
+    },
+  ]
+
+  const paginationChange:PaginationOnChangeType = (pageNumber, pageSize) => {
+    onPaginationChange?.({pageNumber, pageSize})
+  }
+
+  const tableChnage:TableOnChangeType = (_pagination, filters, sorter, extra) => {
+    onTableChange?.({filters, sorter, extra})
+  }
+
+  return (
+    <>
+      <Table 
+        rowKey='id'
+        columns={ columns } 
+        dataSource={ tableData } 
+        pagination={ false }
+        scroll={{y: height}}
+        onChange={ tableChnage }
+      />
+      <Pagination 
+        className={styles['pagination']}
+        showSizeChanger 
+        disabled={!total}
+        current={ pageNumber}
+        pageSize={pageSize}
+        total={total} 
+        showTotal={ (total) => `共${total}条记录` } 
+        onChange={ paginationChange }
+      />
+    </>
+  )
+}

+ 49 - 0
src/pages/Manage/components/AppManage/components/ContentTable/types.d.ts

@@ -0,0 +1,49 @@
+import type { FilterValue, SorterResult, TableCurrentDataSource } from 'antd/es/table/interface'
+import type { TablePaginationConfig } from 'antd/es/table'
+
+export interface PropsType {
+    tableData: RowDataType[]
+    total: number,
+    pageNumber?: number,
+    pageSize?: number,
+    onTableChange?: EventFunctionType<[TableChangeParamsType]>,
+    onPaginationChange?: EventFunctionType<[PaginationParamsType]>,
+    onEditRow: EventFunctionType<[RowDataType]>,
+    onDeleteRow: EventFunctionType<[RowDataType]>
+}
+
+//  表格行数据类型
+export interface RowDataType  {
+    id: number,
+    unionAppName: string, // 应用名称
+    unionAppId: string, // 应用ID
+    thirdAppType: number, // 应用类型
+    status: number, // 状态
+    createTime: string, // 创建时间
+    thirdMpAppId?: string, // 小程序appid
+    thirdAppDomain?: string // H5链接
+}
+
+//  onTableChange事件参数类型
+export interface TableChangeParamsType {
+    filters: Record<string, FilterValue| null>, 
+    sorter: SorterResult<RowDataType> | SorterResult<RowDataType>[], 
+    extra: TableCurrentDataSource<RowDataType>
+}
+
+// onPaginationChange事件参数类型
+export interface PaginationParamsType {
+    pageNumber: number,
+    pageSize: number,
+}
+
+// ant Table 的onChange事件类型
+export type TableOnChangeType = EventFunctionType<[
+    TablePaginationConfig,
+    Record<string, FilterValue| null>,
+    SorterResult<RowDataType> | SorterResult<RowDataType>[],
+    TableCurrentDataSource<RowDataType>
+]>
+
+//  ant Pagination 的onChange事件类型
+export type PaginationOnChangeType = EventFunctionType<[number, number]>

+ 9 - 0
src/pages/Manage/components/AppManage/components/CreateModal/index.module.css

@@ -0,0 +1,9 @@
+.custom-modal {
+
+    :global {
+        .ant-modal-header {
+            margin-bottom: 24px;
+        }
+    }
+
+}

+ 157 - 0
src/pages/Manage/components/AppManage/components/CreateModal/index.tsx

@@ -0,0 +1,157 @@
+import { Modal, Form, Select, Input } from 'antd'
+import type { PropsType, Action, FormDataType, OptionsItemType } from './types'
+import { forwardRef, useImperativeHandle, useState } from 'react'
+import type { RowDataType } from '../ContentTable/types'
+import { appTypes } from '../../const'
+import styles from './index.module.css'
+
+const { Item, useForm } = Form
+const appTypesOptions:OptionsItemType[] = Object.entries(appTypes).map(([value,label])=>({value: +value,label}))
+const initialValues = {
+  thirdAppType: 0, // 应用类型
+}
+
+const CreateModal = forwardRef(({ onCreate, onUpdate }:PropsType, ref) => {
+  const [isOpen, setIsOpen] = useState(false)
+  const [action, setAction] = useState<Action>()
+  const [form] = useForm()
+  const [editRow, setEditRow] = useState<RowDataType>()
+  const isEdit = action === 'edit'
+  const title = isEdit ? '编辑应用' : '新建应用'
+
+  useImperativeHandle(ref,()=>({
+    open: (editRow?:RowDataType) => {
+      setAction(editRow?'edit':'create')
+      setEditRow(editRow)
+      if (editRow) {
+        const {
+          thirdAppType, // 应用类型
+          unionAppName, // 应用名称
+          thirdMpAppId, // 小程序appid
+          thirdAppDomain // H5链接
+        } =  editRow 
+        form.setFieldsValue({
+          thirdAppType, // 应用类型
+          unionAppName, // 应用名称
+          thirdMpAppId, // 小程序appid
+          thirdAppDomain // H5链接
+        })
+      }
+      setIsOpen(true)
+    }
+  }))
+
+ 
+
+  const onOk = async () => {
+    try {
+      const res:FormDataType =  await form.validateFields()
+      switch (action) {
+        case 'create':
+          onCreate(res, ()=>{setIsOpen(false)})
+          break
+        case 'edit':
+          onUpdate({...(editRow as RowDataType), ...res},()=>{setIsOpen(false)})
+      }
+    } catch (error) { /* empty */ }
+  }
+
+  const onCancel = () => {
+    setIsOpen(false)
+  }
+
+  const afterOpenChange = (isOpen: boolean) => {
+    if (!isOpen) {
+      form.resetFields()
+    }
+  }
+
+  return (
+    <Modal
+      className={styles['custom-modal']}
+      centered={true}
+      open={isOpen}
+      title={title}
+      width={500}
+      onCancel={onCancel}
+      onOk={onOk}
+      afterOpenChange={afterOpenChange}
+    >
+      <Form
+        form={form}
+        initialValues={initialValues}
+        labelCol={{ span: 5 }}
+        wrapperCol={{ span: 18, offset: 1}}
+        colon={ false }
+        requiredMark={ false }
+      >
+        { !isEdit && <Item
+          name='thirdAppType'
+          label='应用类型'
+          rules={[{ required: true, message: '请选择应用类型' }]}
+        >
+          <Select 
+            placeholder='请选择应用类型'
+            options={appTypesOptions}
+          />
+        </Item>
+        }
+        <Item
+          name='unionAppName'
+          label='应用名称'
+          rules={[{ required: true, message: '请输入应用名称' }]}
+        >
+          <Input placeholder='请输入应用名称' showCount maxLength={ 20 } />
+        </Item>
+        {
+          !isEdit &&  <>
+            <Item
+              noStyle
+              shouldUpdate={(preV, curV) => preV.thirdAppType !== curV.thirdAppType}
+            >
+              {
+                ({getFieldValue}) => getFieldValue('thirdAppType')===0 && (
+                  <Form.Item 
+                    name='thirdMpAppId' 
+                    label='小程序appId'
+                    rules={[{ required: true, message: '请输入小程序appId' }]}
+                  >
+                    <Input placeholder='请输入小程序appId' disabled={isEdit} />
+                  </Form.Item>
+                ) 
+              }
+            </Item>
+            <Item
+              noStyle
+              shouldUpdate={(preV, curV) => preV.thirdAppType !== curV.thirdAppType}
+            >
+              {
+                ({getFieldValue}) => getFieldValue('thirdAppType')===1 && (
+                  <Form.Item 
+                    name='thirdAppDomain' 
+                    label='H5链接'
+                    rules={[
+                      () => ({
+                        validator(_, value) {
+                          if (!value) return Promise.reject(new Error('请输入H5链接'))
+                          // H5,验证网址格式
+                          const pattern = /^(https:\/\/)[^\s]+/
+                          if (!pattern.test(value)) return Promise.reject(new Error('请输入https://开头的链接'))
+                          return Promise.resolve()
+                        },
+                      })
+                    ]}
+                  >
+                    <Input placeholder='请输入H5链接'  disabled={isEdit} />
+                  </Form.Item>
+                ) 
+              }
+            </Item>
+          </>
+        }
+      </Form>
+    </Modal>
+  )
+})
+
+export default CreateModal

+ 25 - 0
src/pages/Manage/components/AppManage/components/CreateModal/types.d.ts

@@ -0,0 +1,25 @@
+import { RowDataType } from '../ContentTable/types'
+
+export interface PropsType {
+    onCreate: EventFunctionType<[FormDataType,EventFunctionType<[]>]>,
+    onUpdate: EventFunctionType<[RowDataType,EventFunctionType<[]>]>
+}
+
+export interface FormDataType {
+    thirdAppType: number, // 应用类型
+    unionAppName: string, // 应用名称
+    thirdMpAppId?: string, // 小程序appid
+    thirdAppDomain?: string // H5链接
+}
+
+export type Action = 'create' | 'edit' 
+
+export interface RefType {
+    open: EventFunctionType<[RowDataType?]>
+}
+
+// select 组件选项类型
+export interface OptionsItemType {
+    label: string,
+    value: string | number
+}

+ 31 - 0
src/pages/Manage/components/AppManage/components/HeaderFilter/index.tsx

@@ -0,0 +1,31 @@
+import { PlusOutlined } from '@ant-design/icons'
+import { Button } from 'antd'
+import FilterHeaderLayout from '@src/components/FilterHeaderLayout'
+import { debounce } from 'lodash'
+
+interface PropsType {
+  onCreate: EventFunctionType<[]>
+}
+
+export default function HeaderFilter({ onCreate }:PropsType) {
+
+  const createApp = debounce(() => {
+    onCreate()
+  }, 1000, {leading: true, trailing: false})
+
+  return (
+    <div style={{marginBottom:'16px'}}>
+      <FilterHeaderLayout 
+        rightComponent={
+          <>
+            <Button
+              type='primary'
+              icon={<PlusOutlined />} 
+              onClick={createApp}
+            >新建应用</Button>
+          </>
+        }
+      />
+    </div>
+  )
+}

+ 4 - 0
src/pages/Manage/components/AppManage/const.ts

@@ -0,0 +1,4 @@
+export const appTypes:MapType<string> =  {
+  0: '小程序',
+  1: 'H5',
+}

+ 77 - 2
src/pages/Manage/components/AppManage/index.tsx

@@ -1,7 +1,82 @@
+import { useEffect, useRef, useState } from 'react'
+import HeaderFilter from './components/HeaderFilter'
+import ContentTable from './components/ContentTable'
+import CreateModal from './components/CreateModal'
+import type { 
+  RowDataType, PaginationParamsType 
+} from './components/ContentTable/types'
+import { useGlobalStateUpdate } from '@src/store/globalStates/loadingState'
+import { RefType, FormDataType } from './components/CreateModal/types'
+
+
 export function AppManage(){
+  const [tableData, setTableData] = useState<RowDataType[]>([])
+  const [total, setTotal] = useState(0)
+  const [paginationParams, setPaginationParams] = useState<PaginationParamsType>({ pageNumber: 1, pageSize: 10})
+  const createModalRef = useRef<RefType>()
+  const setloading = useGlobalStateUpdate()
+
+  useEffect(()=>{
+    getTableData()   
+  },[paginationParams])
+
+  const  getTableData = async () => {
+    // TODO 获取数据
+
+    // setloading(true)
+    // const data = await getPlanList(mergeRequestParams(paginationParams, filterParams))
+    // if (data) {
+    //   setTableData(data.objs)
+    //   setTotal(data.totalSize)
+    // }
+    // setloading(false)
+  }
+
+  const onPaginationChange =async (params: PaginationParamsType) => {
+    setPaginationParams(params)
+  }
+
+  const onCreate = () => {
+    createModalRef.current?.open()
+  }
+
+  const editRow = (row: RowDataType) => {
+    createModalRef.current?.open(row)
+  }
+
+  const deleteRow = async (row: RowDataType) => {
+    // TODO 删除
+  }
+
+  const createApp = (form: FormDataType, cb:EventFunctionType<[]>) => {
+    console.log('创建',form)
+    // TODO 创建接口
+    cb()
+  }
+
+  const updateApp = (form: RowDataType,  cb:EventFunctionType<[]>) => {
+    console.log('更新',form)
+    // TODO 更新接口
+    cb()
+  }
+
   return (
     <>
-        应用列表
+      <HeaderFilter onCreate={onCreate} />
+      <ContentTable 
+        total={ total } 
+        pageNumber={ paginationParams.pageNumber }
+        pageSize={ paginationParams.pageSize }
+        tableData={ tableData } 
+        onPaginationChange = { onPaginationChange }
+        onEditRow={ editRow } 
+        onDeleteRow={ deleteRow }
+      />
+      <CreateModal 
+        ref={createModalRef}
+        onCreate={createApp}
+        onUpdate={updateApp}
+      />
     </>
   )
-}
+}

+ 9 - 2
src/pages/Manage/index.module.css

@@ -5,18 +5,25 @@
 
 .sider {
     padding-top: 8px;
+    margin: 16px;
+    border-radius: 8px;
+    overflow: hidden;
+    margin-right: 0;
 
     :global {
         .ant-layout-sider-children {
             overflow: auto;
         }
+        .ant-layout-sider-trigger {
+            position: absolute;
+        }
     }
 }
 
 .content {
     background-color: #fff;
-    margin: 24px;
-    padding: 24px;
+    margin: 16px;
+    padding: 16px;
     border-radius: 8px;
     overflow: auto;
 }

+ 7 - 6
src/pages/Manage/index.tsx

@@ -2,7 +2,7 @@ import { ReactNode, useState } from 'react'
 import { Outlet, useLocation, useNavigate } from 'react-router-dom'
 import { Layout, Menu } from 'antd'
 import {
-  PieChartOutlined,
+  // PieChartOutlined,
   AppstoreOutlined,
 } from '@ant-design/icons'
 import styles from './index.module.css'
@@ -27,6 +27,7 @@ export default function Index(){
         collapsed={collapsed} 
         breakpoint='xl'
         onCollapse={(v)=>setCollapsed(v)}
+        collapsedWidth={60}
       >
         <Menu 
           defaultSelectedKeys={defaultSelectedKeys} 
@@ -59,11 +60,11 @@ const items: MenuItem[] = [
       }
     ]
   },
-  {
-    label: '广告数据',
-    key: '/manage/ad-data',
-    icon: <PieChartOutlined />
-  }
+  // {
+  //   label: '广告数据',
+  //   key: '/manage/ad-data',
+  //   icon: <PieChartOutlined />
+  // }
 ]
 
 interface MenuItem {

+ 0 - 0
types/home/index.d.ts


+ 20 - 12
types/index.d.ts

@@ -1,13 +1,21 @@
 type UserInfoType = {
-  aptitudeType: number
-  company: string
-  contactPerson: string
-  createTime: string
-  email: string
-  id: number
-  isDelete: number
-  phone: string
-  picAddress: string
-  shortName: string
-  updateTime: string
-}
+  accountInfoId: number, // 账号信息主键ID
+  trafficMasterCode: string, // 流量主Code
+  phone: string, // 手机号
+  email: string, // 邮箱
+  companyInfoId: number, // 公司信息表主键ID
+  companyName: string, // 公司名称
+  contactPerson: string, // 联系人
+  bankAccountCompany: string, // 开户公司
+  companyAddress: string, // 公司所在地
+  bankName: string, // 名称银行
+  bankAccount: string // 银行账号
+}
+
+// 定义组件的事件类型
+type EventFunctionType<T extends unknown[]> = (...params: T) => void 
+
+type Keys = number | string
+interface MapType<V> {
+  [key: string|number|symbol]: V
+}