shaogen1995 2 سال پیش
والد
کامیت
06c5ea8d3f

+ 2 - 2
后台/src/components/Z_upFileOne/index.tsx

@@ -27,7 +27,7 @@ type Props = {
   formatTxt?: string; //上传图片提示
   sizeTxt?: string; //后面的建议尺寸信息
   fromData?: any;
-  checkTxt?:string
+  checkTxt?: string;
 };
 
 function Z_upFileOne({
@@ -40,7 +40,7 @@ function Z_upFileOne({
   myUrl,
   format = ["image/jpeg", "image/png"],
   formatTxt = "png、jpg和jpeg",
-  checkTxt='请上传封面图!',
+  checkTxt = "请上传封面图!",
   sizeTxt,
   fromData,
 }: Props) {

+ 131 - 0
后台/src/pages/B2Goods/B2Edit/index.module.scss

@@ -0,0 +1,131 @@
+.B2Edit {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 99;
+  padding: 30px 20px;
+  background-color: #fff;
+  border-radius: 10px;
+
+  :global {
+    .B2Main {
+      width: 100%;
+      height: 100%;
+      overflow-y: auto;
+
+      .B2MainSon {
+        width: 800px;
+
+        .e_row {
+          padding-right: 50px;
+          display: flex;
+          font-size: 14px;
+          margin-bottom: 10px;
+
+          .e_rowL {
+            width: 68px;
+            text-align: right;
+
+            &>span {
+              color: #ff4d4f;
+              position: relative;
+              top: 3px;
+            }
+          }
+
+          .e_rowR {
+            width: calc(100% - 68px);
+          }
+
+        }
+      }
+    }
+
+    // 查看的情况
+    .nolyLookMain {
+
+      .ant-row {
+        pointer-events: none;
+      }
+
+      .ant-form-item-control-input {
+        pointer-events: none;
+
+        .ant-input-affix-wrapper {
+          border: transparent;
+
+          .ant-input-show-count-suffix {
+            opacity: 0;
+          }
+        }
+
+        .ant-select-selector {
+          border: transparent;
+        }
+
+        .ant-select-arrow {
+          opacity: 0;
+        }
+
+        .ant-input-affix-wrapper-disabled {
+          background-color: transparent;
+        }
+
+        .ant-input {
+          border: transparent;
+          color: black !important;
+        }
+
+        .ant-input-data-count {
+          opacity: 0;
+        }
+
+      }
+
+      .ant-checkbox-group {
+        pointer-events: none;
+      }
+
+      .myformBox3 .upImgBox .fileImgListBox>div {
+        margin: 0px 20px 15px 0;
+        cursor: default;
+      }
+
+      .closeLook {
+        .ant-form-item-control-input {
+          pointer-events: auto;
+        }
+      }
+
+      textarea {
+        min-height: 40px !important;
+      }
+
+      textarea,
+      input {
+        &::-webkit-input-placeholder {
+          color: black;
+        }
+
+        &:-moz-placeholder {
+          color: black;
+        }
+
+        &::-moz-placeholder {
+          color: black;
+        }
+
+        &:-ms-input-placeholder {
+          color: black;
+        }
+      }
+
+      // 下拉框 placeholder 字体颜色
+      .ant-select-selection-placeholder {
+        color: black;
+      }
+    }
+  }
+}

+ 209 - 0
后台/src/pages/B2Goods/B2Edit/index.tsx

@@ -0,0 +1,209 @@
+import React, { useCallback, useEffect, useRef, useState } from "react";
+import styles from "./index.module.scss";
+import classNames from "classnames";
+import { Button, Form, FormInstance, Input, Popconfirm, Select } from "antd";
+import { useSelector } from "react-redux";
+import { RootState } from "@/store";
+import TextArea from "antd/es/input/TextArea";
+import UpFileOne from "@/components/Z_upFileOne";
+import { B2_APIgetInfo, B2_APIsave } from "@/store/action/B2Goods";
+import { MessageFu } from "@/utils/message";
+
+type Props = {
+  editId: number;
+  isLook: boolean;
+  closeFu: () => void;
+  editFu: (val: string) => void;
+  addFu: (val: string) => void;
+  myType: string;
+};
+
+function B2Edit({ editId, isLook, closeFu, editFu, addFu, myType }: Props) {
+  const getInfoFu = useCallback(async (id: number) => {
+    const res = await B2_APIgetInfo(id);
+    if (res.code === 0) {
+      FormBoxRef.current?.setFieldsValue(res.data);
+      setCover(res.data.thumb);
+    }
+  }, []);
+
+  // 获取下拉框数据
+  const topList = useSelector((state: RootState) => state.B1Plate.list);
+
+  useEffect(() => {
+    if (editId > 0) getInfoFu(editId);
+    else {
+      FormBoxRef.current?.setFieldsValue({
+        type: myType,
+      });
+    }
+  }, [editId, getInfoFu, myType]);
+
+  // 表单的ref
+  const FormBoxRef = useRef<FormInstance>(null);
+
+  // 文件的校验
+  const [check, setCheck] = useState(false);
+
+  // 图片
+  const [cover, setCover] = useState("");
+
+  // 没有通过校验
+  const onFinishFailed = useCallback(() => {
+    setCheck(true);
+  }, []);
+
+  // 通过校验点击确定
+  const onFinish = useCallback(
+    async (value: any) => {
+      setCheck(true);
+      if (!cover) return;
+      const obj = {
+        ...value,
+        id: editId > 0 ? editId : null,
+        thumb: cover,
+      };
+      const res = await B2_APIsave(obj);
+      if (res.code === 0) {
+        MessageFu.success(editId > 0 ? "编辑成功!" : "新增成功!");
+        if (editId > 0) editFu(value.type);
+        else addFu(value.type);
+        closeFu();
+      }
+      // console.log("通过校验,点击确定", res);
+    },
+    [addFu, closeFu, cover, editFu, editId]
+  );
+
+  return (
+    <div className={styles.B2Edit}>
+      <div
+        className={classNames("B2Main mySorrl", isLook ? "nolyLookMain" : "")}
+      >
+        <div className="B2MainSon">
+          <Form
+            ref={FormBoxRef}
+            name="basic"
+            labelCol={{ span: 2 }}
+            onFinish={onFinish}
+            onFinishFailed={onFinishFailed}
+            autoComplete="off"
+          >
+            <Form.Item
+              label="类别"
+              name="type"
+              rules={[{ required: true, message: "请选择类别!" }]}
+            >
+              <Select
+                placeholder="请选择"
+                style={{ width: 400 }}
+                options={topList.map((v) => ({ value: v.name, label: v.name }))}
+              />
+            </Form.Item>
+
+            <Form.Item
+              label="标题"
+              name="name"
+              rules={[{ required: true, message: "请输入标题!" }]}
+              getValueFromEvent={(e) => e.target.value.replace(/\s+/g, "")}
+            >
+              <Input
+                disabled={myType === "craft"}
+                maxLength={20}
+                showCount
+                placeholder="请输入内容"
+              />
+            </Form.Item>
+
+            <Form.Item
+              label="正文"
+              name="content"
+              rules={[
+                { required: true, message: "请输入正文!" },
+                {
+                  validator: (rule, value) => {
+                    if (value) {
+                      const txt = value
+                        .replaceAll(" ", "")
+                        .replaceAll("\n", "");
+                      return txt === ""
+                        ? Promise.reject("请输入有效字符!")
+                        : Promise.resolve();
+                    } else return Promise.resolve();
+                  },
+                },
+              ]}
+            >
+              <TextArea
+                autoSize
+                placeholder="请输入内容"
+                showCount
+                maxLength={500}
+              />
+            </Form.Item>
+
+            <div className="e_row">
+              <div className="e_rowL">
+                <span>* </span>图片:
+              </div>
+              <div className="e_rowR">
+                <UpFileOne
+                  myUrl="cms/goods/upload"
+                  cover={cover}
+                  setCover={(val) => setCover(val)}
+                  isLook={isLook}
+                  coverCheck={check}
+                  size={5}
+                  dirCode={"myGoods"}
+                  checkTxt="请上传图片!"
+                />
+              </div>
+            </div>
+
+            <Form.Item
+              label="模型"
+              name="modelUrl"
+              getValueFromEvent={(e) => e.target.value.replace(/\s+/g, "")}
+            >
+              <Input
+                maxLength={20}
+                showCount
+                placeholder={isLook ? "(空)" : "请输入模型链接"}
+              />
+            </Form.Item>
+
+            {/* 确定和取消按钮 */}
+            <br />
+            <Form.Item
+              wrapperCol={{ offset: 9, span: 16 }}
+              className="closeLook"
+            >
+              {isLook ? (
+                <Button onClick={closeFu}>关 闭</Button>
+              ) : (
+                <>
+                  <Button type="primary" htmlType="submit">
+                    提交
+                  </Button>
+                  &emsp;
+                  <Popconfirm
+                    title="放弃编辑后,信息将不会保存!"
+                    okText="放弃"
+                    cancelText="取消"
+                    onConfirm={closeFu}
+                  >
+                    <Button>取消</Button>
+                  </Popconfirm>
+                </>
+              )}
+            </Form.Item>
+          </Form>
+        </div>
+      </div>
+    </div>
+  );
+}
+
+const MemoB2Edit = React.memo(B2Edit);
+
+export default MemoB2Edit;

+ 56 - 3
后台/src/pages/B2Goods/index.module.scss

@@ -1,5 +1,58 @@
-.B2Goods{
-  :global{
-    
+.B2Goods {
+  position: relative;
+
+  :global {
+    .top {
+      display: flex;
+      align-items: center;
+      background-color: #fff;
+      border-radius: 10px;
+      padding: 20px 15px;
+
+      .topSelect {
+        margin-right: 20px;
+        display: flex;
+      }
+    }
+
+    .tableBox {
+      border-radius: 10px;
+      overflow: hidden;
+      margin-top: 15px;
+      height: calc(100% - 80px);
+      background-color: #fff;
+
+      .ant-table-body {
+        height: 617px;
+        overflow-y: auto !important;
+
+        .ant-table-row {
+          .ant-table-cell {
+            padding: 10px;
+          }
+        }
+      }
+
+      .moveTit {
+        position: relative;
+        display: flex;
+
+        .inco {
+          cursor: pointer;
+          margin-left: 10px;
+          font-size: 14px;
+          color: #7e8293;
+        }
+      }
+    }
+
+    // 表头拖拽样式
+    .drop-over-downward td {
+      border-bottom: 2px dashed var(--themeColor) !important;
+    }
+
+    .drop-over-upward td {
+      border-top: 2px dashed var(--themeColor) !important;
+    }
   }
 }

+ 347 - 2
后台/src/pages/B2Goods/index.tsx

@@ -1,9 +1,354 @@
-import React from "react";
+import React, {
+  useCallback,
+  useEffect,
+  useMemo,
+  useRef,
+  useState,
+} from "react";
 import styles from "./index.module.scss";
+import { useDispatch, useSelector } from "react-redux";
+import { B1_APIgetList } from "@/store/action/B1Plate";
+import { RootState } from "@/store";
+import { Button, Input, Popconfirm, Table, Tooltip } from "antd";
+import { B2_APIdel, B2_APIgetList, B2_APIsort } from "@/store/action/B2Goods";
+import { ExclamationCircleFilled } from "@ant-design/icons";
+
+// 表格拖动排序-----------------
+import { DndProvider, useDrag, useDrop } from "react-dnd";
+import { HTML5Backend } from "react-dnd-html5-backend";
+import { MessageFu } from "@/utils/message";
+import { B2TableType } from "@/types";
+import ImageLazy from "@/components/ImageLazy";
+import B2Edit from "./B2Edit";
+
 function B2Goods() {
+  const dispatch = useDispatch();
+  useEffect(() => {
+    dispatch(B1_APIgetList(""));
+  }, [dispatch]);
+
+  // 顶部筛选 列表
+  const topList = useSelector((state: RootState) => state.B1Plate.list);
+
+  //点击新增/编辑/查看
+  const [editId, setEditId] = useState(0);
+  const [isLook, setIsLook] = useState(false);
+
+  // 发送请求的参数
+  const [fromData, setFromData] = useState({
+    pageNum: 1,
+    pageSize: 10,
+    searchKey: "",
+    type: "盛世",
+  });
+
+  useEffect(() => {
+    dispatch(B2_APIgetList(fromData));
+  }, [dispatch, fromData]);
+
+  const [inputKey, setInputKey] = useState(1);
+
+  // 标题的输入
+  const nameTime = useRef(-1);
+  const nameChange = useCallback(
+    (e: React.ChangeEvent<HTMLInputElement>) => {
+      clearTimeout(nameTime.current);
+      nameTime.current = window.setTimeout(() => {
+        setFromData({ ...fromData, searchKey: e.target.value });
+      }, 500);
+    },
+    [fromData]
+  );
+
+  // 点击重置
+  const resetSelectFu = useCallback(
+    (val?: string) => {
+      setInputKey(Date.now());
+      setFromData({
+        pageNum: 1,
+        pageSize: 10,
+        searchKey: "",
+        type: val ? val : fromData.type,
+      });
+    },
+    [fromData.type]
+  );
+
+  // 点击新增/编辑/查看
+  const openPageFu = useCallback((id: number, flag: boolean) => {
+    setEditId(id);
+    setIsLook(flag);
+  }, []);
+
+  // 点击删除
+  const delTableFu = useCallback(
+    async (id: number) => {
+      const res = await B2_APIdel(id);
+      if (res.code === 0) {
+        MessageFu.success("删除成功!");
+        dispatch(B2_APIgetList(fromData));
+      }
+    },
+    [dispatch, fromData]
+  );
+
+  const { tableInfo } = useSelector((state: RootState) => state.B2Goods);
+
+  const columns = useMemo(() => {
+    const arr: any = [
+      {
+        width: 100,
+        title: (
+          <div className="moveTit">
+            序号
+            <Tooltip title="按住鼠标可拖动表格调整顺序">
+              <div className="inco" hidden={tableInfo.list.length < 2}>
+                <ExclamationCircleFilled />
+              </div>
+            </Tooltip>
+          </div>
+        ),
+        render: (_1: any, _2: any, index: number) =>
+          index + 1 + (fromData.pageNum - 1) * fromData.pageSize,
+      },
+      {
+        title: "标题",
+        dataIndex: "name",
+      },
+      {
+        title: "图片",
+        render: (item: B2TableType) => (
+          <div className="tableImgAuto">
+            <ImageLazy width={60} height={60} src={item.thumb} />
+          </div>
+        ),
+      },
+      {
+        title: "正文",
+        render: (item: B2TableType) =>
+          item.content.length >= 50 ? (
+            <span style={{ cursor: "pointer" }} title={item.content}>
+              {item.content.substring(0, 50) + "..."}
+            </span>
+          ) : (
+            item.content
+          ),
+      },
+      {
+        title: "操作",
+        render: (item: B2TableType) => (
+          <>
+            <Button
+              size="small"
+              type="text"
+              onClick={() => openPageFu(item.id, true)}
+            >
+              查看
+            </Button>
+            <Button
+              size="small"
+              type="text"
+              onClick={() => openPageFu(item.id, false)}
+            >
+              编辑
+            </Button>
+            <Popconfirm
+              title="删除后无法恢复,是否删除?"
+              okText="删除"
+              cancelText="取消"
+              onConfirm={() => delTableFu(item.id)}
+            >
+              <Button size="small" type="text" danger>
+                删除
+              </Button>
+            </Popconfirm>
+          </>
+        ),
+      },
+    ];
+
+    return arr;
+  }, [
+    delTableFu,
+    fromData.pageNum,
+    fromData.pageSize,
+    openPageFu,
+    tableInfo.list.length,
+  ]);
+
+  // 表格拖动排序
+  interface DraggableBodyRowProps
+    extends React.HTMLAttributes<HTMLTableRowElement> {
+    index: number;
+    moveRow: (dragIndex: number, hoverIndex: number) => void;
+  }
+
+  const type = "DraggableBodyRow";
+
+  const DraggableBodyRow = useCallback(
+    ({
+      index,
+      moveRow,
+      className,
+      style,
+      ...restProps
+    }: DraggableBodyRowProps) => {
+      // eslint-disable-next-line react-hooks/rules-of-hooks
+      const ref = useRef<HTMLTableRowElement>(null);
+      // eslint-disable-next-line react-hooks/rules-of-hooks
+      const [{ isOver, dropClassName }, drop] = useDrop({
+        accept: type,
+        collect: (monitor) => {
+          const { index: dragIndex } = monitor.getItem() || {};
+          if (dragIndex === index) {
+            return {};
+          }
+          return {
+            isOver: monitor.isOver(),
+            dropClassName:
+              dragIndex < index ? " drop-over-downward" : " drop-over-upward",
+          };
+        },
+        drop: (item: { index: number }) => {
+          if (moveRow) moveRow(item.index, index);
+        },
+      });
+      // eslint-disable-next-line react-hooks/rules-of-hooks
+      const [, drag] = useDrag({
+        type,
+        item: { index },
+        collect: (monitor) => ({
+          isDragging: monitor.isDragging(),
+        }),
+      });
+      drop(drag(ref));
+
+      return (
+        <tr
+          ref={ref}
+          className={`${className}${isOver ? dropClassName : ""}`}
+          style={{ cursor: "move", ...style }}
+          {...restProps}
+        />
+      );
+    },
+    []
+  );
+
+  const components = {
+    body: {
+      row: DraggableBodyRow,
+    },
+  };
+
+  const moveRow = useCallback(
+    async (dragIndex: number, hoverIndex: number) => {
+      if (dragIndex === hoverIndex) return;
+      // 交互位置-之前的id
+      const beforeId = tableInfo.list[dragIndex].id;
+      const afterId = tableInfo.list[hoverIndex].id;
+      // console.log("交换位置", beforeId, afterId);
+      const res = await B2_APIsort(beforeId, afterId);
+      if (res.code === 0) dispatch(B2_APIgetList(fromData));
+    },
+    [dispatch, fromData, tableInfo.list]
+  );
+
+  // 页码变化
+  const paginationChange = useCallback(
+    () => (pageNum: number, pageSize: number) => {
+      setFromData({ ...fromData, pageNum, pageSize });
+    },
+    [fromData]
+  );
+
+  // 从子页面编辑成功
+  const editFuSon = useCallback(
+    (val: string) => {
+      if (val === fromData.type) dispatch(B2_APIgetList(fromData));
+      else setFromData({ ...fromData, type: val });
+    },
+    [dispatch, fromData]
+  );
+
   return (
     <div className={styles.B2Goods}>
-      <div className="pageTitle">藏品</div>
+      <div className="pageTitle">
+        {editId ? (isLook ? "查看" : editId > 0 ? "编辑" : "新增") : "藏品"}
+      </div>
+
+      {/* 顶部搜索 */}
+      <div className="top">
+        <div className="topSelect">
+          {topList.map((v) => (
+            <div key={v.id}>
+              <Button
+                onClick={() => setFromData({ ...fromData, type: v.name })}
+                type={v.name === fromData.type ? "primary" : "default"}
+              >
+                {v.name}
+              </Button>
+              &emsp;
+            </div>
+          ))}
+        </div>
+        <span>标题:</span>
+        <Input
+          key={inputKey}
+          maxLength={50}
+          style={{ width: 300 }}
+          placeholder="请输入关键字"
+          allowClear
+          onChange={(e) => nameChange(e)}
+        />
+        &emsp;&emsp;
+        <Button onClick={() => resetSelectFu("")}>重置</Button>
+        &emsp;&emsp;
+        <Button type="primary" onClick={() => openPageFu(-1, false)}>
+          新增
+        </Button>
+      </div>
+
+      {/* 表格主体 */}
+      <div className="tableBox">
+        <DndProvider backend={HTML5Backend}>
+          <Table
+            scroll={{ y: 617 }}
+            columns={columns}
+            dataSource={tableInfo.list}
+            components={components}
+            rowKey="id"
+            pagination={{
+              showQuickJumper: true,
+              position: ["bottomCenter"],
+              showSizeChanger: true,
+              current: fromData.pageNum,
+              pageSize: fromData.pageSize,
+              total: tableInfo.total,
+              onChange: paginationChange(),
+            }}
+            onRow={(_, index) => {
+              const attr = {
+                index,
+                moveRow,
+              };
+              return attr as React.HTMLAttributes<any>;
+            }}
+          />
+        </DndProvider>
+      </div>
+
+      {/* 点击新增/编辑/查看 */}
+      {editId ? (
+        <B2Edit
+          editId={editId}
+          isLook={isLook}
+          closeFu={() => setEditId(0)}
+          editFu={(val) => editFuSon(val)}
+          addFu={(val) => resetSelectFu(val)}
+          myType={fromData.type}
+        />
+      ) : null}
     </div>
   );
 }

+ 46 - 0
后台/src/store/action/B2Goods.ts

@@ -0,0 +1,46 @@
+import http from "@/utils/http";
+import { AppDispatch } from "..";
+
+/**
+ * 获取赏析-板块列表
+ */
+export const B2_APIgetList = (data: any) => {
+  return async (dispatch: AppDispatch) => {
+    const res = await http.post("cms/goods/pageList", data);
+    if (res.code === 0) {
+      const { records, total } = res.data;
+      dispatch({
+        type: "B2Goods/getList",
+        payload: { list: records, total: total },
+      });
+    }
+  };
+};
+
+/**
+ * 删除
+ */
+export const B2_APIdel = (id: number) => {
+  return http.get(`cms/goods/del/${id}`);
+};
+
+/**
+ * 排序
+ */
+export const B2_APIsort = (id1: number, id2: number) => {
+  return http.get(`cms/goods/sort/${id1}/${id2}`);
+};
+
+/**
+ * 获取详情
+ */
+export const B2_APIgetInfo = (id: number) => {
+  return http.get(`cms/goods/detail/${id}`);
+};
+
+/**
+ * 新增/修改
+ */
+export const B2_APIsave = (data: any) => {
+  return http.post("cms/goods/save", data);
+};

+ 28 - 0
后台/src/store/reducer/B2Goods.ts

@@ -0,0 +1,28 @@
+import { B2TableType } from "@/types";
+
+// 初始化状态
+const initState = {
+  // 列表数据
+  tableInfo: {
+    list: [] as B2TableType[],
+    total: 0,
+  },
+};
+
+// 定义 action 类型
+type Props = {
+  type: "B2Goods/getList";
+  payload: { list: B2TableType[]; total: number };
+};
+
+// 频道 reducer
+export default function Reducer(state = initState, action: Props) {
+  switch (action.type) {
+    // 获取列表数据
+    case "B2Goods/getList":
+      return { ...state, tableInfo: action.payload };
+
+    default:
+      return state;
+  }
+}

+ 2 - 0
后台/src/store/reducer/index.ts

@@ -11,6 +11,7 @@ import A5Maestro from "./A5Maestro";
 import A6Custom from "./A6Custom";
 import A7Project from "./A7Project";
 import B1Plate from "./B1Plate";
+import B2Goods from "./B2Goods";
 import D1User from "./D1User";
 import D2Log from "./D2Log";
 
@@ -25,6 +26,7 @@ const rootReducer = combineReducers({
   A6Custom,
   A7Project,
   B1Plate,
+  B2Goods,
   D1User,
   D2Log,
 });

+ 13 - 0
后台/src/types/api/B2Goods.d.ts

@@ -0,0 +1,13 @@
+export type B2TableType = {
+  content: string;
+  createTime: string;
+  creatorId?: any;
+  creatorName: string;
+  id: number;
+  modelUrl: string;
+  name: string;
+  sort: number;
+  thumb: string;
+  type: string;
+  updateTime: string;
+};

+ 1 - 0
后台/src/types/index.d.ts

@@ -1,5 +1,6 @@
 export * from './api/layot'
 export * from './api/A1Plate'
 export * from './api/A2Country'
+export * from './api/B2Goods'
 export * from './api/D1User'
 export * from './api/D2Log'