shenwu há 7 meses atrás
pai
commit
20bf0f0f22

+ 1 - 0
package.json

@@ -62,6 +62,7 @@
     "rc-menu": "^9.12.4",
     "rc-util": "^5.38.1",
     "react": "^18.2.0",
+    "react-cropper": "^2.3.3",
     "react-dom": "^18.2.0",
     "react-helmet-async": "^1.3.0"
   },

BIN
public/init.png


+ 83 - 73
src/Hook/useAjax.tsx

@@ -1,18 +1,18 @@
-import { message } from 'antd'
-import { useState } from 'react'
+import { message } from 'antd';
+import { useState } from 'react';
 export interface AjaxPromise {
-    /**请求*/
-    run: (params?: any) => Promise<any>,
-    /**执行上次的请求*/
-    refresh: () => Promise<any>,
-    /** 修改data */
-    mutate: (data: any) => void
-    /**清空data*/
-    initData: () => void,
-    /**loding状态*/
-    loading: boolean,
-    /**data数据*/
-    data: any,
+  /**请求*/
+  run: (params?: any) => Promise<any>;
+  /**执行上次的请求*/
+  refresh: () => Promise<any>;
+  /** 修改data */
+  mutate: (data: any) => void;
+  /**清空data*/
+  initData: () => void;
+  /**loding状态*/
+  loading: boolean;
+  /**data数据*/
+  data: any;
 }
 /**
  * ajax封装方法 hook 方法不要使用在非页面中
@@ -22,65 +22,75 @@ export interface AjaxPromise {
  * @returns data 请求结果数据
  * @returns initData 初始data
  * */
-export function useAjax(fnc: (params?: any) => Promise<any>, options?: { type: 'table' | "noPage" }): AjaxPromise {
-    // const {message}= App.useApp()
-    const [loading, setLoding] = useState(false)//状态
-    const [data, setData] = useState<any>()//数据
-    const [oldParams, setOldParasm] = useState()
-    async function run(params?: any) {//请求
-        setLoding(() => true)//开启加载
-        setOldParasm(() => params)//存放本次请求参数
-        // 表单接口处理参数
+export function useAjax(
+  fnc: (params?: any) => Promise<any>,
+  options?: { type: 'table' | 'noPage' },
+): AjaxPromise {
+  // const {message}= App.useApp()
+  const [loading, setLoding] = useState(false); //状态
+  const [data, setData] = useState<any>(); //数据
+  const [oldParams, setOldParasm] = useState();
+  async function run(params?: any) {
+    //请求
+    setLoding(() => true); //开启加载
+    setOldParasm(() => params); //存放本次请求参数
+    // 表单接口处理参数
+    if (options?.type === 'table') {
+      params.pageNum = params.current;
+      delete params.current;
+    }
+    return fnc(params)
+      .then((res) => {
+        //开启请求
+        setLoding(() => false); //关闭请求加载
+        setData(() => res); //设置data
+        if (res?.code === 500) {
+          message.error(res?.msg);
+          return res;
+        }
         if (options?.type === 'table') {
-            params.pageNum = params.current
-            delete params.current
+          //表单数据返回
+          return {
+            data: res.data.records,
+            total: res.data.total,
+            current: res.data.current,
+            pageSize: res.data.size,
+            success: res.success,
+          };
         }
-        return fnc(params).then(res => {//开启请求
-            setLoding(() => false)//关闭请求加载
-            setData(() => res)//设置data
-            if (res?.code === 500) {
-                message.error(res?.msg)
-                return res
-            }
-            if (options?.type === 'table') {//表单数据返回
-                return {
-                    data: res.data.records,
-                    total: res.data.total,
-                    current: res.data.current,
-                    pageSize: res.data.size,
-                    success: res.success
-                }
-            }
-            if(options?.type === 'noPage'){
-                return {
-                    current: 1,
-                    data: res.data,
-                    pageSize: 20,
-                    total: res.data.length,
-                    success: true
-                }
-            }
-            return res//返回data
-        }).catch(err => {
-            console.log(err)
-            setLoding(() => false)//关闭请求加载
-            return err
-        })
-    }
-    async function refresh() {//上次的请求
-        setLoding(() => true)//开启加载
-        return fnc(oldParams).then(res => {//开启请求
-            setLoding(() => false)//关闭请求加载
-            setData(() => res)//设置data
-            return res//返回data
-        })
-    }
-    async function initData() {
-        setData(null)
-    }
-    // 修改保存数据
-    async function mutate(data: any) {
-        setData(data)
-    }
-    return { loading, data, run, refresh, initData, mutate }
+        if (options?.type === 'noPage') {
+          return {
+            current: 1,
+            data: res.data?.[0]?.id ? res.data : res.data.map((a: any,b: any)=>({...a,id:b})),
+            pageSize: 20,
+            total: res.data.length,
+            success: true,
+          };
+        }
+        return res; //返回data
+      })
+      .catch((err) => {
+        console.log(err);
+        setLoding(() => false); //关闭请求加载
+        return err;
+      });
+  }
+  async function refresh() {
+    //上次的请求
+    setLoding(() => true); //开启加载
+    return fnc(oldParams).then((res) => {
+      //开启请求
+      setLoding(() => false); //关闭请求加载
+      setData(() => res); //设置data
+      return res; //返回data
+    });
+  }
+  async function initData() {
+    setData(null);
+  }
+  // 修改保存数据
+  async function mutate(data: any) {
+    setData(data);
+  }
+  return { loading, data, run, refresh, initData, mutate };
 }

+ 76 - 0
src/Hook/useOss.tsx

@@ -0,0 +1,76 @@
+import { getOssSecretKeyApi } from '@/services/global';
+import { request } from '@umijs/max';
+import { message } from 'antd';
+import { RcFile } from 'antd/es/upload';
+import { useState } from 'react';
+
+export interface OssPromise {
+  /**上传*/
+  run: (file: RcFile, fileName?: string) => Promise<any>;
+  /**loding状态*/
+  loading: boolean;
+}
+export function useOss(isLoading?: boolean): OssPromise {
+  /**********************************/
+  const [loading, setLoding] = useState(false); //状态
+  /**********************************/
+
+  async function run(file: RcFile, fileName?: string) {
+    //请求
+    let hide: any;
+    if (isLoading) {
+      hide = message.loading('上传中...', 0);
+    }
+    setLoding(true);
+    let name = fileName || file.name.split('.')[0];
+    let suffix = file.type;
+    if (!file.type) {
+      suffix = 'audio/amr';
+    }
+    let res = await getOssSecretKeyApi({ type: suffix }).catch((error) => {
+      message.error(error?.message);
+      setLoding(false);
+      if (isLoading && hide) {
+        hide();
+      }
+    });
+    let msg: string = '';
+    if (res?.data) {
+      let ossData = res.data;
+      let formData = new FormData();
+      Object.keys(ossData).forEach((key: string) => {
+        if (key !== 'ossUrl') {
+          formData.append(key, ossData[key]);
+        }
+      });
+      formData.append('file', file);
+      let data = await request(ossData?.ossUrl, { method: 'POST', data: formData }).catch(
+        (error) => {
+          console.error(error);
+          message.error(error);
+        },
+      );
+      if (data) {
+        setLoding(false);
+        if (isLoading && hide) {
+          hide();
+          message.success('上传成功');
+        }
+        return data;
+      } else {
+        msg = '上传文件到OSSkey失败';
+        message.error('上传文件到OSSkey失败');
+      }
+    } else {
+      msg = '获取OSSkey失败';
+      message.error('获取OSSkey失败');
+    }
+    setLoding(false);
+    if (isLoading && hide) {
+      hide();
+    }
+    return { code: 500, data: null, msg };
+  }
+
+  return { run, loading };
+}

+ 212 - 0
src/components/CropperImg/index.tsx

@@ -0,0 +1,212 @@
+import { dataURLtoFile } from '@/utils/index';
+import { useDebounce } from 'ahooks';
+import { Button, Col, Form, InputNumber, Modal, Row } from 'antd';
+import { RcFile } from 'antd/lib/upload';
+import 'cropperjs/dist/cropper.css';
+import React, { useEffect, useRef, useState } from 'react';
+import Cropper from 'react-cropper';
+
+interface Props {
+  size?: { width: number; height: number };
+  isEdit?: boolean;
+  file?: RcFile;
+  visible?: boolean;
+  onChange?: (fileList: any[], file: any) => void;
+  onClose?: () => void;
+}
+
+const CropperImg: React.FC<Props> = (props) => {
+  const { size, isEdit, file, visible, onChange, onClose } = props;
+  const [image, setImage] = useState(''); // 初始图片
+  const cropperRef = useRef<any>(null); // 使用 useRef 来获取实例
+  const [dragMode] = useState<'none' | 'crop' | 'move'>('none');
+  const [imgSize, setImageSize] = useState<{ width: number; height: number }>({
+    width: 0,
+    height: 0,
+  });
+  const [detail, setDetail] = useState<{ width: number; height: number; rotate: number }>({
+    width: 0,
+    height: 0,
+    rotate: 0,
+  }); // 设置数据
+
+  /** 获取宽高 image */
+  useEffect(() => {
+    if (file) {
+      let img: any = new Image();
+      let _URL = window.URL || window.webkitURL;
+      img.onload = function (e: any) {
+        const reader = new FileReader();
+        if (!isEdit) {
+          // 可更改
+          let sizeWidth = size?.width || this.width,
+            sizeHeight = size?.height || this.height;
+          let width = sizeWidth > this.width ? this.width : sizeWidth;
+          let height = sizeHeight > this.height ? this.height : sizeHeight;
+          setImageSize({ width: width, height: height });
+          setDetail({ ...detail, width: width, height: height });
+        } else {
+          // 不可更改
+          setImageSize({ width: this.width, height: this.height });
+          if (size?.width && size?.height) {
+            setDetail({ ...detail, width: size?.width, height: size?.height });
+          } else {
+            setDetail({ ...detail, width: this.width, height: this.height });
+          }
+        }
+
+        reader.onload = async () => {
+          setImage(reader.result as any);
+        };
+        reader.readAsDataURL(file);
+      };
+      img.src = _URL.createObjectURL(file);
+    }
+  }, [size, isEdit]);
+
+  const debouncedValue = useDebounce(detail, { wait: 500 });
+
+  /** 数值变化重置实例 */
+  useEffect(() => {
+    if (cropperRef.current && debouncedValue?.width) {
+      console.log(cropperRef.current?.cropper, debouncedValue);
+      cropperRef.current?.cropper.setData(debouncedValue);
+    }
+  }, [debouncedValue]);
+
+  /** 初始化 */
+  useEffect(() => {
+    if (visible && cropperRef.current) {
+      setTimeout(() => {
+        cropperRef.current?.cropper.reset();
+      }, 200);
+    }
+  }, [visible]);
+
+  /** 关闭 */
+  const cancel = () => {
+    onClose && onClose();
+    setImage('');
+  };
+
+  const getCropData = async () => {
+    if (cropperRef.current) {
+      let newFile = await dataURLtoFile(
+        cropperRef.current?.cropper.getCroppedCanvas().toDataURL('image/jpeg'),
+        file?.name.split('.')[0] + '.jpg',
+      );
+      onChange &&
+        onChange(
+          [
+            {
+              lastModified: newFile.lastModified,
+              name: newFile.name,
+              percent: 0,
+              size: newFile.size,
+              thumbUrl: cropperRef.current?.cropper.getCroppedCanvas().toDataURL('image/jpeg'),
+              type: newFile.type,
+              originFileObj: newFile,
+            },
+          ],
+          newFile,
+        );
+      setImage('');
+    }
+  };
+
+  return (
+    <Modal
+      open={visible}
+      onCancel={cancel}
+      width={1000}
+      footer={
+        <div style={{ display: 'flex', justifyContent: 'center' }}>
+          <Button onClick={cancel}>取消</Button>
+          <Button type="primary" onClick={getCropData}>
+            确定
+          </Button>
+        </div>
+      }
+    >
+      <div style={{ width: '100%' }}>
+        <Row>
+          <Col span={16}>
+            <Cropper
+              ref={cropperRef} // 绑定 ref
+              style={{
+                height: 450,
+                width: 600,
+                backgroundImage: `url(${require('../../../public/init.png')})`,
+                border: '1px solid #efefef',
+              }}
+              initialAspectRatio={1}
+              preview=".img-preview"
+              src={image}
+              viewMode={1}
+              dragMode={dragMode}
+              guides={true}
+              rotatable={true}
+              minCropBoxHeight={10}
+              minCropBoxWidth={10}
+              background={false}
+              responsive={true}
+              autoCropArea={1}
+              cropBoxResizable={false}
+              zoomable={false}
+              movable={false}
+              // onInitialized={(instance: any) => {
+              //     console.log('instance--->', instance);
+              //     cropperRef.current = instance;
+              //     instance.zoomTo(0.6);
+              // }}
+              crop={() => {}}
+            />
+          </Col>
+          <Col span={8}>
+            <Row gutter={[10, 10]}>
+              <Col span={24} style={{ height: 275 }}>
+                <div className="box" style={{ width: '100%', float: 'right', overflow: 'hidden' }}>
+                  <h1 style={{ marginTop: 0 }}>预览</h1>
+                  <div
+                    className="img-preview"
+                    style={{ width: '100%', float: 'left', height: '200px', overflow: 'hidden' }}
+                  />
+                </div>
+              </Col>
+              <Col>
+                <Form>
+                  <Form.Item label="裁剪框宽" style={{ marginBottom: 10 }}>
+                    <InputNumber
+                      style={{ width: 200 }}
+                      value={detail.width}
+                      min={1}
+                      max={imgSize.width}
+                      disabled={isEdit}
+                      onChange={(e) => {
+                        setDetail({ ...detail, width: e || 0 });
+                      }}
+                    />
+                  </Form.Item>
+                  <Form.Item label="裁剪框高" style={{ marginBottom: 0 }}>
+                    <InputNumber
+                      style={{ width: 200 }}
+                      value={detail.height}
+                      min={1}
+                      max={imgSize.height}
+                      disabled={isEdit}
+                      onChange={(e) => {
+                        setDetail({ ...detail, height: e || 0 });
+                      }}
+                    />
+                  </Form.Item>
+                </Form>
+              </Col>
+            </Row>
+          </Col>
+        </Row>
+      </div>
+    </Modal>
+  );
+};
+
+export default React.memo(CropperImg);

+ 84 - 0
src/components/bookSelect/index.tsx

@@ -0,0 +1,84 @@
+import { useAjax } from "@/Hook/useAjax"
+import { longBookInfoList, shortBookInfoList } from "@/services/miniApp/bookManage"
+import { useModel } from "@umijs/max"
+import { Button, Divider, Pagination, Select, Space, Tag } from "antd"
+import { useCallback, useEffect, useMemo, useRef, useState } from "react"
+
+const BookSelect = (props: { value?: any, onChange?: (value: any) => void }) => {
+    let { value, onChange } = props
+    let { initialState } = useModel("@@initialState")
+    let publicData = useMemo(() => {
+        return {
+            appId: initialState?.selectApp?.id || "",
+            distributorId: initialState?.currentUser?.distributorId,
+            appType: initialState?.selectApp?.appType || ""
+        }
+    }, [initialState?.selectApp, initialState?.currentUser?.distributorId])
+    let getShortList = useAjax((params) => shortBookInfoList(params))//获取书列表
+    let getLongtList = useAjax((params) => longBookInfoList(params))//获取书列表
+    let api = useMemo(() => {
+        return initialState?.selectApp?.appCategory === 1 ? getLongtList : getShortList
+    }, [initialState?.selectApp])
+    let timeOut = useRef<any>()
+    let [params, setParmas] = useState({
+        pageNum: 1,
+        pageSize: 20,
+        bookName: ""
+    })
+    const [data, setData] = useState<{ records: any[], total: number }>({ records: [], total: 0 })
+    // 防抖
+    const debounce = useCallback((func: any, wait: number) => {
+        if (timeOut.current) {
+            clearTimeout(timeOut.current);
+        }
+        timeOut.current = setTimeout(() => { func() }, wait);
+    }, [])
+
+    // 使用自定义防抖函数
+    useEffect(() => {
+        if (params.bookName) {
+            api.run({ ...params, ...publicData }).then(res => {
+                if (res.data.total > 0) {
+                    setData({ records: res.data.records, total: res.data.total })
+                } else {
+                    setData({ records: [], total: 0 })
+                }
+            })
+        }
+    }, [params, api])
+    return <div style={{ display: 'flex' }}>
+        <Select
+            placeholder="输入书籍名称搜索"
+            showSearch
+            filterOption={false}
+            onSearch={(value) => {
+                debounce(() => { setParmas({ ...params, bookName: value }) }, 500)
+            }}
+            onChange={(value) => {
+                onChange?.(value)
+            }}
+            value={value}
+            dropdownRender={(menu) => {
+                return <>
+                    {menu}
+                    <Divider style={{ margin: '8px 0' }} />
+                    <Pagination
+                        total={data?.total}
+                        pageSize={params.pageSize}
+                        hideOnSinglePage
+                        showTotal={(total) => <Tag>{total}条数据</Tag>}
+                        size='small'
+                        showQuickJumper={false}
+                        showSizeChanger={false}
+                        onChange={(page, pageSize: number) => {
+                            setParmas({ ...params, pageNum: page, pageSize })
+                        }}
+                    />
+                </>
+            }}
+            options={data?.records?.map(item => ({ label: item.longBookInfo.bookName, value: item.bookId }))}
+        />
+    </div>
+
+}
+export default BookSelect

+ 3 - 0
src/components/uploadImg/index.less

@@ -0,0 +1,3 @@
+.upLoadTrue {
+  margin: 20px 0;
+}

+ 121 - 0
src/components/uploadImg/index.tsx

@@ -0,0 +1,121 @@
+import CropperImg from '@/components/CropperImg';
+import { useOss } from '@/Hook/useOss';
+import {  PlusOutlined } from '@ant-design/icons';
+import { message, Upload } from 'antd';
+import { RcFile } from 'antd/es/upload';
+import dayjs from 'dayjs';
+import React, { useEffect, useState } from 'react';
+import styles from './index.less';
+type CompareType = '<' | '<=' | '>' | '>=' | '=' | '!=';
+
+interface Props {
+  type?: any;
+  value?: string;
+  onChange?: (data: { value: string; file: RcFile; name: string }) => void;
+  size?: { width: number; height: number; evalW: CompareType; evalH: CompareType; msg: string };
+  // 是否裁剪
+  isCropper?: boolean;
+  isEdit?: boolean;
+}
+
+const UploadImg: React.FC<Props> = ({ value, onChange, size, type, isCropper, isEdit }) => {
+  /********************************/
+  const [visible, setVisible] = useState<boolean>(false);
+  const [file, setFile] = useState<RcFile>();
+  const ossUpload = useOss(true);
+  const [imageUrl, setImageUrl] = useState<string>('');
+  /********************************/
+
+  useEffect(() => {
+    setImageUrl(value || '');
+  }, [value]);
+
+  const uploadButton = (
+    <div>
+      <div style={{ height: 45, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
+        {' '}
+        <PlusOutlined/>
+      </div>
+      <div style={{ display: 'flex', flexFlow: 'column' }}>
+        <span>上传图片</span>
+        <span style={{ color: '#999', fontSize: 10 }}>
+          {size?.width} * {size?.height}
+        </span>
+      </div>
+    </div>
+  );
+
+  const upload = (file: RcFile) => {
+    let name = dayjs().valueOf().toString();
+    ossUpload.run(file, name).then((res: any) => {
+      if (res?.data) {
+        let [fileName, nameSuffix] = file.name.split('.');
+        onChange?.(res.data.url);
+      }
+    });
+  };
+
+  return (
+    <>
+      <Upload
+        name="avatar"
+        listType="picture-card"
+        accept="image/png,image/jpg,image/jpeg"
+        action="#"
+        showUploadList={false}
+        customRequest={() => {}}
+        className={imageUrl ? styles.upLoadTrue : ''}
+        beforeUpload={(file: RcFile): any => {
+          if (file.size > 10485760) {
+            message.error('图片大小大于10M,请压缩在上传');
+            return;
+          }
+          let img: any = new Image();
+          let _URL = window.URL || window.webkitURL;
+          img.onload = function (e: any) {
+            if (
+              (size?.width && eval(`${this.width} ${size.evalW} ${size?.width}`)) ||
+              (size?.height && eval(`${this.height} ${size?.evalH} ${size?.height}`))
+            ) {
+              if (
+                isCropper &&
+                ((isEdit &&
+                  eval(`${this.width} >= ${size?.width}`) &&
+                  eval(`${this.height} >= ${size?.height}`)) ||
+                  !isEdit)
+              ) {
+                setFile(file);
+                setVisible(true);
+              } else {
+                message.error(
+                  `传入的图片大小不符, 上传图片宽度${this.width}高度${this.height}, ${size?.msg}`,
+                );
+              }
+              return;
+            }
+            upload(file);
+          };
+          img.src = _URL.createObjectURL(file);
+        }}
+      >
+        {imageUrl ? <img src={imageUrl} alt="avatar" style={{ width: '100%' }} /> : uploadButton}
+      </Upload>
+      {/* 裁剪 */}
+      {visible && (
+        <CropperImg
+          visible={visible}
+          size={size}
+          isEdit={isEdit}
+          onClose={() => setVisible(false)}
+          file={file}
+          onChange={(fileList: any[], file: any) => {
+            upload(file);
+            setVisible(false);
+          }}
+        />
+      )}
+    </>
+  );
+};
+
+export default React.memo(UploadImg);

+ 13 - 5
src/models/appPageConifg.tsx

@@ -1,10 +1,17 @@
 import { useReducer } from "react"
 
+type Component = {
+    appComponentId: number,//组件ID
+    componentType:string,//
+    pageUrl:string,
+    configs:any[],//组件配置
+}
 type State = {
     tabs: 0 | 1,//男生女生选项
-    compAc: number,//当前选中的组件,切换tabs请0
-    manList: any[]//男生配置
-    womanList: any[]//女生配置
+    compAc: string,//当前选中的组件,切换tabs请0
+    manList: Component[]//男生配置
+    womanList: Component[]//女生配置
+    index:number,//每次操作修改数据都递增,为了检测到数据变动
 }
 type Action = {
     params?: any,
@@ -22,9 +29,10 @@ export function reducer(state: State, action: Action) {
 export default (): { state: State, dispatch: React.Dispatch<Action> } => {
     const [state, dispatch] = useReducer(reducer, {
         tabs: 0,
-        compAc: 0,
+        compAc: "",
         manList: [],
-        womanList: []
+        womanList: [],
+        index:0
     })
     return {
         state,

+ 48 - 0
src/pages/MiniApp/CompConfig/DrawerBox/compConfig.tsx

@@ -0,0 +1,48 @@
+import { BetaSchemaForm, ProFormInstance } from "@ant-design/pro-components";
+import { useEffect, useMemo, useRef } from "react";
+import { bannersConfig } from "./compFormConfig";
+import { useModel } from "@umijs/max";
+
+export function BannersConfig() {
+    let { state, dispatch } = useModel('appPageConifg')
+    const list = useMemo(() => {
+        return state.tabs === 0 ? state.manList : state.womanList
+    }, [state])
+    const formRef = useRef<ProFormInstance>();
+    // 提交表单
+    const submit = async (values: any) => {
+    }
+    useEffect(() => {
+        let thatConfig = list.find(item => item.componentType === "banners")
+        formRef.current?.setFieldsValue(thatConfig)
+    }, [list])
+    return <BetaSchemaForm<any>
+        formRef={formRef}
+        layoutType={"Form"}
+        size="small"
+        colProps={{
+            span: 12,
+        }}
+        grid={true}
+        onFinish={submit}
+        columns={bannersConfig()}
+        onValuesChange={(changedValues: any, values: any) => {
+            console.log("valuonValuesChangees", values, list)
+            let thatConfig = list.find(item => item.componentType === "banners")
+            if (thatConfig) {
+                thatConfig.configs = values.configs
+            }
+            console.log(1111)
+            dispatch({
+                type: 'setAll', params: state.tabs === 0 ? {
+                    manList: list,
+                    index:state.index+1
+                } : {
+                    womanList: list,
+                    index:state.index+1
+                }
+            })
+            console.log(2222,state)
+        }}
+    />
+}

+ 161 - 0
src/pages/MiniApp/CompConfig/DrawerBox/compFormConfig.tsx

@@ -0,0 +1,161 @@
+import UploadImg from "@/components/uploadImg";
+import { ArrowDownOutlined, ArrowUpOutlined } from "@ant-design/icons";
+import { ProFormColumnsType } from "@ant-design/pro-components";
+import styles from './index.less'
+import { Select, Tooltip } from "antd";
+import BookSelect from "@/components/bookSelect";
+export function bannersConfig(): ProFormColumnsType<any>[] {
+    return [
+        {
+            valueType: 'formList',
+            dataIndex: "configs",
+            colProps: {
+                span: 24
+            },
+            fieldProps: {
+                creatorButtonProps: {
+                    creatorButtonText: "添加一个Banner",
+                },
+                alwaysShowItemLabel: true,//全部展示title
+                actionRender: (field: any, action: any, defaultActionDom: any, count: any) => {//自定义按钮
+                    return [...defaultActionDom,
+                    field.name < count - 1 && <Tooltip title="向下移动"><ArrowDownOutlined className={styles.upDownBtn} onClick={() => { action.move(field.name + 1, field.name) }} /></Tooltip>,
+                    field.name > 0 && <Tooltip title="向上移动"><ArrowUpOutlined className={styles.upDownBtn} onClick={() => { action.move(field.name - 1, field.name) }} /></Tooltip>]
+                }
+            },
+            columns: [
+                {
+                    valueType: 'group',
+                    colProps: {
+                        span: 24
+                    },
+                    columns: [
+                        {
+                            title: 'banner名称',
+                            dataIndex: 'templateName',
+                            colProps: {
+                                span: 24
+                            },
+                            fieldProps: {
+                                placeholder: '请输入banner名称'
+                            },
+                            formItemProps: {
+                                style: { marginBottom: 10 },
+                                rules: [
+                                    {
+                                        required: true,
+                                        message: '此项为必填项',
+                                    },
+                                ],
+                            }
+                        },
+                        {
+                            title: 'banner类型',
+                            dataIndex: 'bannerType',
+                            valueType: 'radio',
+                            valueEnum: { 0: { text: "小说" }, 1: { text: "活动" } },
+                            colProps: {
+                                span: 24
+                            },
+                            formItemProps: {
+                                style: { marginBottom: 10 },
+                                rules: [
+                                    {
+                                        required: true,
+                                        message: '此项为必填项',
+                                    },
+                                ],
+                            }
+                        },
+                        {
+                            valueType: 'dependency',
+                            name: ['bannerType'],
+                            columns: ({ bannerType }) => {
+                                return [
+                                    {
+                                        dataIndex: "bookId",
+                                        title: '小说',
+                                        hideInForm: bannerType != 0,
+                                        formItemProps: {
+                                            style: { marginBottom: 10 },
+                                            rules: [
+                                                {
+                                                    required: true,
+                                                    message: '此项为必填项',
+                                                },
+                                            ],
+                                        },
+                                        colProps: {
+                                            span: 24
+                                        },
+                                        renderFormItem(schema, config, form, action) {
+                                            return <BookSelect />
+                                        },
+                                    },
+                                    {
+                                        valueType: 'select',
+                                        dataIndex: "activityPagePath",
+                                        title: '活动页面',
+                                        colProps: {
+                                            span: 24
+                                        },
+                                        fieldProps: {
+                                            style: { width: 'auto' },
+                                            showSearch: true,
+                                            placeholder: '请选择活动页面'
+                                        },
+                                        hideInForm: bannerType != 1,
+                                        formItemProps: {
+                                            style: { marginBottom: 10 },
+                                            rules: [
+                                                {
+                                                    required: true,
+                                                    message: '此项为必填项',
+                                                },
+                                            ],
+                                        }
+                                    }
+                                ]
+                            }
+                        },
+                        {
+                            title: 'banner图片',
+                            dataIndex: 'bannerImage',
+                            colProps: {
+                                span: 24,
+                            },
+                            formItemProps: {
+                                rules: [
+                                    {
+                                        required: true,
+                                        message: '此项为必填项',
+                                    },
+                                ],
+                            },
+                            renderFormItem: () => {
+                                return (
+                                    <UploadImg
+                                        size={{
+                                            width: 690,
+                                            height: 248,
+                                            evalW: '!=',
+                                            evalH: '!=',
+                                            msg: '需要图片大小690*248',
+                                        }}
+                                        isEdit={true}
+                                        type="image"
+                                        isCropper
+                                    />
+                                );
+                            },
+                        },
+                        {
+                            valueType: 'divider',
+                        }
+                    ]
+                }
+            ]
+        },
+
+    ]
+}

+ 37 - 9
src/pages/MiniApp/CompConfig/DrawerBox/content.tsx

@@ -1,8 +1,8 @@
 import { useDrop } from "ahooks";
 import { Col, Row, Space } from "antd";
-import React, { useState, useImperativeHandle, forwardRef, useRef } from "react";
+import React, { useState, useImperativeHandle, forwardRef, useRef, useMemo } from "react";
 import styles from './index.less'
-import Banners from "../components/banners";
+import { Banners } from "../components/banners";
 import { useModel } from "@umijs/max";
 
 // 使用 forwardRef 以支持传递 ref
@@ -10,8 +10,32 @@ const DragItem = () => {
     const { state, dispatch } = useModel("appPageConifg")
     const [isHovering, setIsHovering] = useState(false);
     const dropRef = useRef(null);
+    const list = useMemo(() => {
+        return state.tabs === 0 ? state.manList : state.womanList
+    }, [state])
     useDrop(dropRef, {
+        // 接收到组件拖拽到手机内容内,添加进入数据
         onDom: (content: string, e) => {
+            console.log(11111)
+            let arr = content?.split('_')
+            let appComponentId = arr[0]
+            let componentType = arr[1]
+            let pageUrl = arr[2]
+            let newConfig = { appComponentId, componentType, pageUrl, configs: [] }
+            let { tabs, manList, womanList } = state
+            switch (componentType) {
+                case "banners":
+                    dispatch({
+                        type: 'setAll', params: tabs === 0 ? {
+                            manList: [...state.manList, newConfig]
+                        } : {
+                            womanList: [...state.womanList, newConfig]
+                        }
+                    })
+                    break
+                default:
+                    break
+            }
             console.log(`custom: ${content} dropped`);
         },
         onDragOver: (e) => {
@@ -20,11 +44,15 @@ const DragItem = () => {
         onDragEnter: () => setIsHovering(true),
         onDragLeave: () => setIsHovering(false),
     });
-    console.log("state",state)
-    return <div ref={dropRef}>
-        <div className={styles.comp}>
-            <Banners data={[]} />
-        </div>
+    return <div ref={dropRef} style={{ minHeight: '100%' }} >
+        {
+            list?.map(item => {
+                return <div key={item.appComponentId} className={`${styles.comp} ${state.compAc === item.componentType ? styles.ac : ""}`} onClick={() => { dispatch({ type: "setAll", params: { compAc: item.componentType } }) }}>
+                    <Banners data={item.configs} />
+                </div>
+            })
+        }
+
     </div>
 };
 // 定义组件的 Props 类型
@@ -32,7 +60,7 @@ interface Props {
 }
 const Content = forwardRef((props: Props, ref) => {
     const { state, dispatch } = useModel("appPageConifg")
-    // 使用 useImperativeHandle 暴露方法给父组件
+     // 使用 useImperativeHandle 暴露方法给父组件
     useImperativeHandle(ref, () => ({
     }));
     return <div className={styles.phone}>
@@ -55,7 +83,7 @@ const Content = forwardRef((props: Props, ref) => {
 
             </Space>
             {/* 内容 */}
-            <DragItem  />
+            <DragItem />
         </div>
     </div>
 })

+ 19 - 2
src/pages/MiniApp/CompConfig/DrawerBox/index.less

@@ -55,15 +55,32 @@
             }
         }
         .comp{
+            cursor: pointer;
+            box-sizing: border-box;
             &:hover{
                 border:1px solid rgb(72, 159, 247);
                 border-radius: 10px;
             }
         }
-        .comp{}
+        .ac{
+            border:1px dashed rgb(72, 159, 247);
+            border-radius: 10px;
+        }
     }
 }
 
 
 // 右侧参数设置
-.set {}
+.set {
+    padding: 20px;
+    box-sizing: border-box;
+    overflow-y: auto;
+}
+
+.upDownBtn{
+    margin-left: 8px;
+    cursor: pointer;
+    &:hover{
+        color: #40a9ff;
+    }
+}

+ 2 - 0
src/pages/MiniApp/CompConfig/DrawerBox/index.tsx

@@ -5,12 +5,14 @@ import Content from "./content";
 import SetData from "./set";
 import { useToken } from "@ant-design/pro-components";
 import { useSize } from "ahooks";
+import { useModel } from "@umijs/max";
 // 定义组件的 Props 类型
 interface Props {
 }
 
 // 使用 forwardRef 以支持传递 ref
 const ModalForm = forwardRef((props: Props, ref) => {
+    let { state, dispatch } = useModel('appPageConifg')
     const [open, setOpen] = useState(false);
     const headerSize = useSize(document.querySelector('.ant-drawer-header'));
     const { token } = useToken()

+ 1 - 1
src/pages/MiniApp/CompConfig/DrawerBox/pageList.tsx

@@ -38,7 +38,7 @@ const PageList = forwardRef((props: Props, ref) => {
     return <Row gutter={[20, 20]} className={styles.pageList}>
         <Col span={24}>
             <Typography.Title level={4} >顶部组件</Typography.Title>
-            <DragItem ><div key="banners" className={styles.banners}>banners</div></DragItem>
+            <DragItem ><div key="6_banners_pages/index/index"  className={styles.banners}>banners</div></DragItem>
         </Col>
         <Col span={24}>
             <Typography.Title level={4} >内容组件</Typography.Title>

+ 10 - 6
src/pages/MiniApp/CompConfig/DrawerBox/set.tsx

@@ -1,20 +1,24 @@
-import { Col, Drawer, Row } from "antd";
 import React, { useState, useImperativeHandle, forwardRef } from "react";
-
+import { useModel } from "@umijs/max";
+import { BannersConfig } from "./compConfig";
+import styles from './index.less'
+import { useSize } from "ahooks";
 // 定义组件的 Props 类型
 interface Props {
 }
 
 // 使用 forwardRef 以支持传递 ref
 const SetData = forwardRef((props: Props, ref) => {
-
+    const { state, dispatch } = useModel("appPageConifg")
+    const size = useSize(document.querySelector(".ant-drawer-body"))
     // 使用 useImperativeHandle 暴露方法给父组件
     useImperativeHandle(ref, () => ({
     }));
 
-    return <Row>
-         <Col>设置区域</Col>
-    </Row>
+    return <div className={styles.set} style={{ height: size?.height ? size.height - 50 : 'auto' }}>
+        {state.compAc === 'banners' && <div> <BannersConfig /></div>}
+        {state.compAc === '' && <div>选中组件可设置内容</div>}
+    </div>
 });
 
 export default SetData;

+ 5 - 5
src/pages/MiniApp/CompConfig/components/banners.tsx

@@ -1,7 +1,7 @@
-import { useToken } from "@ant-design/pro-components";
+import { BetaSchemaForm, useToken } from "@ant-design/pro-components";
 import { Carousel } from "antd";
 import styles from './index.less'
-function Banners(props: { data: any[] }) {
+export function Banners(props: { data: any[] }) {
     const { token } = useToken()
     let { data } = props
     return <Carousel autoplay autoplaySpeed={5000} className={styles.banners}>
@@ -12,12 +12,12 @@ function Banners(props: { data: any[] }) {
                 </div>
             </div> : data?.map((item: any, index: number) => {
                 return <div key={index} style={{ borderRadius: "10px" }}>
-                    <div  className={styles.banners_box}  >
-                        <img src="https://bossaudioandcomic-1252317822.image.myqcloud.com/activity/document/0d5f0b28395be160af37a59150f14ab3.jpg" style={{ objectFit: 'cover', width: "100%", height: "100%", borderRadius: "10px" }} />
+                    <div className={styles.banners_box}  >
+                        <img src={item?.bannerImage} style={{ objectFit: 'cover', width: "100%", height: "100%", borderRadius: "10px" }} />
                     </div>
                 </div>
             })
         }
     </Carousel>
 }
-export default Banners
+

+ 0 - 366
src/pages/MiniApp/CompConfig/formConfig.tsx

@@ -1,366 +0,0 @@
-import { convertEnumArr } from "@/utils";
-import { ProFormColumnsType } from "@ant-design/pro-components";
-
-function formConfig(enumList?: any): ProFormColumnsType<{
-    name: string;
-    state: string;
-}>[] {
-    let templateTypeEnum = enumList?.TEMPLATE_TYPE?.values || []
-    let ORDER_TYPEEnum = enumList?.ORDER_TYPE?.values || []
-    let COLOREnum = enumList?.COLOR?.values || []
-    let VIP_DAYSEnum = enumList?.VIP_DAYS?.values || []
-    return [
-        {
-            title: '模板名称',
-            dataIndex: 'templateName',
-            colProps:{
-                span:24
-            },
-            formItemProps: {
-                rules: [
-                    {
-                        required: true,
-                        message: '此项为必填项',
-                    },
-                ],
-            }
-        },
-        {
-            title: '充值模板描述',
-            dataIndex: 'templateDescription',
-            valueType: 'textarea',
-        },
-        {
-            title: '备注',
-            dataIndex: 'remark',
-            valueType: 'textarea',
-        },
-        {
-            title: '首充档位',
-            dataIndex: 'rechargeConfigList',
-            valueType: 'formList',
-            fieldProps: {
-                min: 4,
-                max: 6,
-                creatorButtonProps: {
-                    creatorButtonText: "添加一个首充档位配置",
-                },
-            },
-            colProps: {
-                span: 24
-            },
-            columns: [
-                {
-                    valueType: 'group',
-                    colProps: {
-                        span: 24
-                    },
-                    columns: [
-                        {
-                            title: '类型',
-                            dataIndex: 'gearType',
-                            valueType: 'select',
-                            width: "100%",
-                            initialValue:1,
-                            fieldProps: { placeholder: "请选择档位类型" },
-                            colProps: {
-                                span: 4
-                            },
-                            formItemProps: {
-                                rules: [
-                                    {
-                                        required: true,
-                                        message: '此项为必填项',
-                                    },
-                                ],
-                            },
-                            valueEnum: new Map(ORDER_TYPEEnum?.map((item: { value: any; description: any; }) => [item.value, item.description]))
-                        },
-                        {
-                            valueType: 'dependency',
-                            name: ['gearType'],
-                            columns: ({ gearType }) => {
-                                return [
-                                    {
-                                        title: '价格',
-                                        dataIndex: 'price',
-                                        valueType: 'money',
-                                        width: "100%",
-                                        hideInForm: gearType === 3,
-                                        fieldProps: { placeholder: "输入价格" },
-                                        colProps: {
-                                            span: 5
-                                        },
-                                        formItemProps: {
-                                            rules: [
-                                                {
-                                                    required: true,
-                                                    message: '此项为必填项',
-                                                },
-                                            ],
-                                        }
-                                    },
-                                    {
-                                        title: '赠送',
-                                        dataIndex: 'gift',
-                                        valueType: 'digit',
-                                        width: "100%",
-                                        hideInForm: gearType !== 1,
-                                        fieldProps: { placeholder: "输入赠送书币数" },
-                                        colProps: {
-                                            span: 5
-                                        },
-                                        formItemProps: {
-                                            rules: [
-                                                {
-                                                    required: true,
-                                                    message: '此项为必填项',
-                                                },
-                                            ],
-                                        }
-                                    },
-                                    {
-                                        title: 'vip天数',
-                                        dataIndex: 'vipDays',
-                                        valueType: 'select',
-                                        width: "100%",
-                                        hideInForm: gearType !== 2,
-                                        fieldProps: { placeholder: "选择VIP天数" },
-                                        colProps: {
-                                            span: 4
-                                        },
-                                        formItemProps: {
-                                            rules: [
-                                                {
-                                                    required: true,
-                                                    message: '此项为必填项',
-                                                },
-                                            ],
-                                        },
-                                        valueEnum: new Map(VIP_DAYSEnum?.map((item: { value: any; description: any; }) => [item.value, item.description]))
-                                    },
-                                    {
-                                        title: '价格',
-                                        width: "100%",
-                                        fieldProps: { placeholder: "价格在小说管理设置", disabled: true },
-                                        hideInForm: gearType !== 3,
-                                        colProps: {
-                                            span: 5
-                                        }
-                                    },
-                                    {
-                                        title: '文案',
-                                        dataIndex: 'description',
-                                        width: "100%",
-                                        fieldProps: { placeholder: "输入展示文案" },
-                                        hideInForm: gearType !== 3,
-                                        colProps: {
-                                            span: 5
-                                        },
-                                        formItemProps: {
-                                            rules: [
-                                                {
-                                                    required: true,
-                                                    message: '此项为必填项',
-                                                },
-                                            ],
-                                        }
-                                    },
-                                    {
-                                        title: '角标',
-                                        dataIndex: 'subscript',
-                                        width: "100%",
-                                        fieldProps: { placeholder: "右上角角标文案" },
-                                        colProps: {
-                                            span: 5
-                                        }
-                                    },
-                                    {
-                                        title: '底色',
-                                        dataIndex: "color",
-                                        valueType: "radioButton",
-                                        width: "100%",
-                                        initialValue: false,
-                                        colProps: {
-                                            span: 4
-                                        },
-                                        valueEnum: new Map(COLOREnum?.map((item: { value: any; description: any; }) => [item.value, item.description]))
-                                    }
-                                ]
-                            }
-
-                        },
-                    ]
-                }
-            ]
-        },
-        {
-            title: '非首充档位',
-            dataIndex: 'rechargeConfigList1',
-            valueType: 'formList',
-            fieldProps: {
-                min: 4,
-                max: 6,
-                creatorButtonProps: {
-                    creatorButtonText: "添加一个非首充档位配置",
-                },
-            },
-            colProps: {
-                span: 24
-            },
-            columns: [
-                {
-                    valueType: 'group',
-                    colProps: {
-                        span: 24
-                    },
-                    columns: [
-                        {
-                            title: '类型',
-                            dataIndex: 'gearType',
-                            valueType: 'select',
-                            width: "100%",
-                            initialValue:1,
-                            fieldProps: { placeholder: "请选择档位类型" },
-                            colProps: {
-                                span: 4
-                            },
-                            formItemProps: {
-                                rules: [
-                                    {
-                                        required: true,
-                                        message: '此项为必填项',
-                                    },
-                                ],
-                            },
-                            valueEnum: new Map(ORDER_TYPEEnum?.map((item: { value: any; description: any; }) => [item.value, item.description]))
-                        },
-                        {
-                            valueType: 'dependency',
-                            name: ['gearType'],
-                            columns: ({ gearType }) => {
-                                return [
-                                    {
-                                        title: '价格',
-                                        dataIndex: 'price',
-                                        valueType: 'money',
-                                        width: "100%",
-                                        hideInForm: gearType === 3,
-                                        fieldProps: { placeholder: "输入价格" },
-                                        colProps: {
-                                            span: 5
-                                        },
-                                        formItemProps: {
-                                            rules: [
-                                                {
-                                                    required: true,
-                                                    message: '此项为必填项',
-                                                },
-                                            ],
-                                        }
-                                    },
-                                    {
-                                        title: '赠送',
-                                        dataIndex: 'gift',
-                                        valueType: 'digit',
-                                        width: "100%",
-                                        hideInForm: gearType !== 1,
-                                        fieldProps: { placeholder: "输入赠送书币数" },
-                                        colProps: {
-                                            span: 5
-                                        },
-                                        formItemProps: {
-                                            rules: [
-                                                {
-                                                    required: true,
-                                                    message: '此项为必填项',
-                                                },
-                                            ],
-                                        }
-                                    },
-                                    {
-                                        title: 'vip天数',
-                                        dataIndex: 'vipDays',
-                                        valueType: 'select',
-                                        width: "100%",
-                                        hideInForm: gearType !== 2,
-                                        fieldProps: { placeholder: "选择VIP天数" },
-                                        colProps: {
-                                            span: 4
-                                        },
-                                        formItemProps: {
-                                            rules: [
-                                                {
-                                                    required: true,
-                                                    message: '此项为必填项',
-                                                },
-                                            ],
-                                        },
-                                        valueEnum: new Map(VIP_DAYSEnum?.map((item: { value: any; description: any; }) => [item.value, item.description]))
-                                    },
-                                    {
-                                        title: '价格',
-                                        width: "100%",
-                                        fieldProps: { placeholder: "价格在小说管理设置", disabled: true },
-                                        hideInForm: gearType !== 3,
-                                        colProps: {
-                                            span: 5
-                                        }
-                                    },
-                                    {
-                                        title: '文案',
-                                        dataIndex: 'description',
-                                        width: "100%",
-                                        fieldProps: { placeholder: "输入展示文案" },
-                                        hideInForm: gearType !== 3,
-                                        colProps: {
-                                            span: 5
-                                        },
-                                        formItemProps: {
-                                            rules: [
-                                                {
-                                                    required: true,
-                                                    message: '此项为必填项',
-                                                },
-                                            ],
-                                        }
-                                    },
-                                    {
-                                        title: '角标',
-                                        dataIndex: 'subscript',
-                                        width: "100%",
-                                        fieldProps: { placeholder: "右上角角标文案" },
-                                        colProps: {
-                                            span: 5
-                                        }
-                                    },
-                                    {
-                                        title: '底色',
-                                        dataIndex: "color",
-                                        valueType: "radioButton",
-                                        width: "100%",
-                                        initialValue: false,
-                                        colProps: {
-                                            span: 4
-                                        },
-                                        valueEnum: new Map(COLOREnum?.map((item: { value: any; description: any; }) => [item.value, item.description]))
-                                    }
-                                ]
-                            }
-
-                        },
-                    ]
-                }
-            ]
-        },
-        {
-            title: '激活当前模板',
-            dataIndex: 'activateTemplate',
-            valueType: 'switch',
-            formItemProps: { layout: 'horizontal' },
-            colProps: { offset: 19 }
-        },
-    ]
-}
-
-export default formConfig

+ 2 - 2
src/pages/MiniApp/CompConfig/index.tsx

@@ -5,10 +5,10 @@ import React, { JSXElementConstructor, Key, ReactElement, ReactNode, ReactPortal
 import { appRechargeTemplateList, appRechargeTemplateSave, appRechargeTemplateSwitch, appRechargeTemplateRemove, appRechargeTemplateUpdate, appRechargeTemplateInfo } from "@/services/miniApp/moduleConfig"
 import { Button, Card, Col, Empty, message, Modal, Row, Space } from "antd"
 import { DeleteOutlined, EditOutlined, ExclamationCircleFilled, PlusOutlined } from "@ant-design/icons"
-import formConfig from "./formConfig"
 import { Template } from "./template"
 import { createStyles } from "antd-style";
 import DrawerBox from "./DrawerBox"
+import { appComponentConfigList } from "@/services/miniApp/compConfig"
 const useStyles = createStyles(({ token }) => {
     return {
         active: {
@@ -44,7 +44,7 @@ const Page: React.FC = () => {
     let [editValues, setEditValues] = useState<any>({})
     let [activeT, setActiveT] = useState<any>()
     let { styles } = useStyles()
-    let getList = useAjax((params) => appRechargeTemplateList(params))//获取模板列表
+    let getList = useAjax((params) => appComponentConfigList(params))//获取模板列表
     let add = useAjax((params) => appRechargeTemplateSave(params))//新增模板
     let switchT = useAjax((params) => appRechargeTemplateSwitch(params))//切换模板
     let editT = useAjax((params) => appRechargeTemplateUpdate(params))//编辑模板

+ 15 - 0
src/services/global.ts

@@ -25,4 +25,19 @@ export async function enumDictList() {
   return request(api + '/admin/enumDict/list', {
     method: 'GET',
   });
+}
+
+export type MediaTypeProps = 'image';
+
+
+/**
+ * 获取OSS密钥
+ * @param params filePrefix 文件名不带后缀 suffix 文件类型
+ * @returns
+ */
+export async function getOssSecretKeyApi(params: { type: string }) {
+  return request(api + `/admin/oss/form/upload`, {
+    method: 'GET',
+    params,
+  });
 }

+ 23 - 5
src/services/miniApp/compConfig/index.tsx

@@ -5,15 +5,33 @@ type Page = {
     pageNum: number | string
 }
 interface PublicParams{
-    miniappId: string,//微信小程序组件ID
-    distributorId: string,//分销商ID
+    appId: string,//微信小程序组件ID
     appType: string,//小程序类型 1:微信小程序、2:抖音小程序
 }
 export interface Params extends Page,PublicParams{}
-/**微信小程序组件分页列表 */
-export async function appRechargeTemplateList(params: Params) {
-    return request(api + '/admin/appRechargeTemplate/list', {
+/**微信小程序组件模板分页列表 */
+export async function appComponentConfigList(params: Params) {
+    return request(api + '/admin/appComponentConfig/list', {
         method: 'GET',
         params
     });
 }
+
+
+/**新增修改 */
+export async function appComponentConfigAddOrUpdate(data: any) {
+    return request(api + '/admin/appComponentConfig/addOrUpdate', {
+        method: 'POST',
+        data
+    });
+}
+
+/**小程序页面列表和可配置的组件 */
+export async function appComponentConfigGetAppPageList(params: Params) {
+    return request(api + '/admin/appComponentConfig/getAppPageList', {
+        method: 'GET',
+        params
+    });
+}
+
+

+ 12 - 0
src/utils/index.ts

@@ -28,3 +28,15 @@ export function convertEnumArr(enumArr: any, customParams: any = {}, config?: {
     return result;
 }
 
+/** base64转File */
+export const dataURLtoFile = (dataurl: any, filename: any) => {
+    let arr = dataurl.split(','),
+      mime = arr[0].match(/:(.*?);/)[1],
+      bstr = atob(arr[1]),
+      n = bstr.length,
+      u8arr = new Uint8Array(n);
+    while (n--) {
+      u8arr[n] = bstr.charCodeAt(n);
+    }
+    return new File([u8arr], filename, { type: mime });
+  };