Răsfoiți Sursa

feat: 产品类型管理
1. 产品分类管理

kindring 1 an în urmă
părinte
comite
153b2657eb

+ 192 - 0
components/typeEdit.vue

@@ -0,0 +1,192 @@
+<script>
+import axios from "axios";
+import {rCode} from "../map/rcodeMap_esm";
+import {handle} from "../until/handle";
+import InputRow from "./public/form/inputRow.vue";
+import RoundedTitle from "./public/roundedTitle.vue";
+import dbField_esm from "../map/dbField_esm";
+import {FormVerify} from "kind-form-verify";
+import {fieldCheck} from "../until/form/fieldVerify";
+
+const emptyType = {
+  type_name: '',
+  type_logo: '',
+  type_sort: 0,
+  type_key: ''
+}
+let formVerify = null;
+export default {
+  name: 'typeEdit',
+  components: {RoundedTitle, InputRow},
+  props: {
+  },
+  data () {
+    return {
+      isInitialized: false,
+      title: '编辑类型',
+      typeId: '',
+      formType: {
+        type_name: {
+          val: "",
+          oldVal: "",
+          init: "",
+          msg: "",
+          state: 0,
+        },
+        type_logo: {
+          val: "",
+          oldVal: "",
+          init: "",
+          msg: "",
+          state: 0,
+        },
+        type_sort: {
+          val: 0,
+          oldVal: 0,
+          init: 0,
+          msg: "",
+          state: 0,
+        },
+        type_key: {
+          val: "",
+          oldVal: "",
+          init: "",
+          msg: "",
+          state: 0,
+          reCheckField: 'type_name',
+        }
+      },
+      api: ''
+    }
+  },
+  mounted() {
+
+    // fieldCheck.checkField('type',this.form.type.val)
+    // formVerify.checkForm(this.form, true);
+    console.log(formVerify);
+
+  },
+  methods: {
+    /**
+     * 初始化编辑器
+     * @param id
+     * @param title
+     * @param api
+     * @param object
+     */
+    init (title, api, object) {
+
+      this.api = api
+      if (!api){
+        this.$message.error('请设置api地址')
+        throw new Error('请设置api地址')
+      }
+      let type = {
+        ...emptyType,
+        ...object
+      }
+      if(object) {
+        this.typeId = object.type_id
+        this.title = `编辑${title}`
+      }else{
+        this.typeId = ''
+        this.title = `新增${title}`
+      }
+      this.formType.type_name.init = type.type_name
+      this.formType.type_logo.init = type.type_logo
+      this.formType.type_sort.init = type.type_sort
+      this.formType.type_key.init = type.type_key
+      this.isInitialized = true
+      formVerify = new FormVerify(
+        this.formType,
+        fieldCheck,
+      )
+      formVerify.init()
+      formVerify.onLog = (msg) => {
+        console.log(msg);
+      };
+    },
+    initForm() {
+
+    },
+    close () {
+      this.isInitialized = false
+      this.$emit('close')
+    },
+    handleSave () {
+      this.executeSubmitData()
+    },
+    handleCancel () {
+      this.close()
+    },
+    handleReset() {
+      this.initForm()
+    },
+    async executeSubmitData(){
+      if(!this.isInitialized)
+      {
+        throw new Error('请先初始化类别编辑器')
+      }
+      let url = this.api;
+      if(this.typeId){
+        url = url + '?id=' + this.typeId
+      }
+      // 检测参数
+      if(!formVerify.check()) {
+          return this.$message.error('参数错误')
+      }
+      let params = formVerify.getFormData()
+      let [err, res] = await handle(axios.post(url, params))
+      if(err){
+        this.$message.error(`[${this.title}] 失败:${err.message}`)
+        console.log(err)
+        return err
+      }
+      let result = res.data
+      if(result.code !== rCode.OK) {
+        this.$message.error(`[${this.title}] 失败:${res.msg}`)
+        return result
+      }
+      this.$message.success(`[${this.title}] 成功`)
+      this.close()
+    }
+
+
+  }
+}
+</script>
+
+<template>
+  <div class="w-full ">
+    <rounded-title class="text-xl">
+      <span>{{ title }}</span>
+    </rounded-title>
+    <input-row class="mt-2" :msg="formType.type_name.msg"
+               label="类型名称">
+      <a-input v-model="formType.type_name.val" placeholder="类型名称"></a-input>
+    </input-row>
+    <input-row class="mt-2" :msg="formType.type_logo.msg"
+               label="类型logo">
+      <a-input v-model="formType.type_logo.val" placeholder="类型logo"></a-input>
+    </input-row>
+    <input-row class="mt-2" :msg="formType.type_sort.msg"
+               label="排序">
+      <a-input-number v-model="formType.type_sort.val"
+                      placeholder="排序"></a-input-number>
+    </input-row>
+    <input-row class="mt-2" :msg="formType.type_key.msg"
+               label="类型关键字key">
+      <a-input v-model="formType.type_key.val" placeholder="类型关键字key"></a-input>
+    </input-row>
+
+    <div class="mt-2">
+      <a-button type="primary" @click="handleReset">重置</a-button>
+      <a-button type="primary" @click="handleSave">确认</a-button>
+    </div>
+
+  </div>
+</template>
+
+<style scoped>
+
+</style>

+ 0 - 0
server/configs/database.json → configs/database.json


+ 0 - 0
server/configs/path.json → configs/path.json


+ 9 - 0
map/apiMap.js

@@ -22,6 +22,15 @@ export const apiMap = {
   productInfo: {
   productInfo: {
     path: `/api/product`
     path: `/api/product`
   },
   },
+  productTypes: {
+    path: `/api/product/types`
+  },
+  productAddType: {
+    path: `/api/product/type/add`
+  },
+  productEditType: {
+    path: `/api/product/type/edit`
+  },
   searchSolution: {
   searchSolution: {
     path: `/api/solution/search`,
     path: `/api/solution/search`,
   },
   },

+ 137 - 5
pages/manger/product/type.vue

@@ -1,17 +1,149 @@
-<script setup>
 
 
-import RoundedTitle from "../../../components/public/roundedTitle.vue";
-</script>
 
 
 <template>
 <template>
-  <div class="w-full p-2">
+  <div class="w-full p-2 h-full">
     <rounded-title class="text-xl">
     <rounded-title class="text-xl">
       <span>产品分类管理</span>
       <span>产品分类管理</span>
-      <a href="/manger/product" class="px-10 h-full ml-5 rounded bg-blue-400 text-white cursor-pointer hover:text-orange-500 ">产品中心</a>
+      <a href="/manger/product" class="inline-flex px-10 h-full ml-5 rounded bg-blue-400 text-white cursor-pointer hover:text-orange-500 ">产品中心</a>
+      <a-button
+        class="ml-2"
+        type="primary"
+        @click="handleAddType"
+      >新增
+      </a-button>
     </rounded-title>
     </rounded-title>
+
+    <div class="w-full rounded border mt-2 bg-white p-2">
+<!--      产品类型列表 -->
+      <a-table
+        :columns="productTypeColumns"
+        :loading="loading"
+        :row-key="record => record.id"
+        :pagination="false"
+        :data-source="productTypes"
+      >
+        <template class="flex justify-center" slot="operation" slot-scope="text,record">
+          <a-button
+            type="primary"
+            @click="handleEditType(record)"
+          >编辑
+          </a-button>
+
+        </template>
+      </a-table>
+    </div>
+    <pop :show="typeEditShow" :loading="loading">
+      <pop-card>
+        <template slot="close-group">
+          <a-button type="danager" @click="handleCloseEdit">关闭</a-button>
+        </template>
+        <type-edit ref="type_edit" @close="handleCloseEdit"></type-edit>
+      </pop-card>
+    </pop>
+
+
+
   </div>
   </div>
 </template>
 </template>
+<script>
+import RoundedTitle from "../../../components/public/roundedTitle.vue";
+import ImageViewer from "../../../components/public/imageViewer.vue";
+import TableSelect from "../../../components/public/tableSelect.vue";
+import {handle} from "../../../until/handle";
+import axios from "axios";
+import {apiMap} from "../../../map/apiMap";
+import {rCode} from "../../../map/rcodeMap_esm";
+import Pop from "../../../components/public/pop.vue";
+import PopCard from "../../../components/public/popCard.vue";
+import TypeEdit from "../../../components/typeEdit.vue";
+
+const productTypeColumns = [
+  {
+    title: 'ID',
+    dataIndex: 'type_id',
+    width: '5%',
+  },
+  {
+    title: '图标',
+    dataIndex: 'type_logo',
+    width: '10%',
+  },
+  {
+    title: '类型名称',
+    dataIndex: 'type_name',
+    width: '15%',
+  },
+  {
+    title: '类型关键字',
+    dataIndex: 'type_key',
+    width: '20%',
+  },
+  {
+    title: '操作',
+    scopedSlots: {customRender: 'operation'},
+  }
+];
+export default {
+  name: "productType",
+  components: {TypeEdit, PopCard, Pop, ImageViewer, TableSelect, RoundedTitle},
+  data(){
+    return {
+      productTypeColumns,
+      loading: false,
+      productTypes: [],
+      page: 1,
+      pageSize: 10,
+      typeEditShow: false,
+    }
+  },
+  beforeMount() {
+    this.getProductTypes();
+  },
+  methods: {
+    // 获取产品类型列表
+    async getProductTypes(){
+      this.loading = true;
+      let [err,res] = await handle(axios.get(apiMap.productTypes.path));
+      this.loading = false;
+      if(err){
+        this.$message.error(err);
+        return {};
+      }
+      let result = res.data;
+      if(result.code !== rCode.OK){
+        this.$message.error(result.msg);
+        return {};
+      }
+      this.productTypes = result.data;
+    },
+    handleAddType(){
+      this.opentypeEdit();
+      this.$nextTick(() => {
+        this.$refs.type_edit.init("产品类型", apiMap.productAddType.path)
+      })
 
 
+
+    },
+    handleEditType(item){
+      //
+      this.opentypeEdit();
+      this.$nextTick(() => {
+        this.$refs.type_edit.init("产品类型", apiMap.productEditType.path, item);
+      })
+    },
+    handleCloseEdit(){
+      this.closeTypeEdit();
+    },
+    opentypeEdit(){
+      this.typeEditShow = true;
+    },
+    closeTypeEdit(){
+      this.typeEditShow = false;
+      this.getProductTypes();
+    }
+  }
+}
+</script>
 <style scoped>
 <style scoped>
 
 
 </style>
 </style>

+ 1 - 1
server/control/c_base.js

@@ -6,7 +6,7 @@ const codeMap = require("../map/rcodeMap");
 const dbField = require("../map/dbField");
 const dbField = require("../map/dbField");
 const {searchHandle} = require("../tools/searchSql");
 const {searchHandle} = require("../tools/searchSql");
 const {isEmpty, toNumber} = require("../tools/typeTool_cjs");
 const {isEmpty, toNumber} = require("../tools/typeTool_cjs");
-const config_path = require("../configs/path");
+const config_path = require("../../configs/path.json");
 const {isArray} = require("ant-design-vue/lib/_util/vue-types/utils");
 const {isArray} = require("ant-design-vue/lib/_util/vue-types/utils");
 const {mvFile, rmFile} = require("../tools/saveFiles_cjs");
 const {mvFile, rmFile} = require("../tools/saveFiles_cjs");
 const {getUnixTimeStamp} = require("../tools/time_cjs");
 const {getUnixTimeStamp} = require("../tools/time_cjs");

+ 69 - 8
server/control/product.js

@@ -3,6 +3,7 @@ const {searchHandle} = require('../tools/searchSql');
 const {handle} = require('../tools/handle_cjs');
 const {handle} = require('../tools/handle_cjs');
 const codeMap = require("../map/rcodeMap");
 const codeMap = require("../map/rcodeMap");
 const log = require("../logger").logger("c_product","info")
 const log = require("../logger").logger("c_product","info")
+const time = require("../tools/time_cjs")
 /**
 /**
  * 加载产品
  * 加载产品
  * @param key 产品类别
  * @param key 产品类别
@@ -60,13 +61,13 @@ async function searchProduct(type, key, p, l)
     _params.key = key
     _params.key = key
   }
   }
 
 
- return await searchHandle(
-    '搜索产品失败',
+ return searchHandle(
+   '搜索产品失败',
    d_product.searchProducts,
    d_product.searchProducts,
-    _params,
-    null,
-    p,
-    l);
+   _params,
+   null,
+   p,
+   l);
 }
 }
 
 
 async function searchProductByMini(type, key, p, l){
 async function searchProductByMini(type, key, p, l){
@@ -81,7 +82,7 @@ async function searchProductByMini(type, key, p, l){
     _params.key = key
     _params.key = key
   }
   }
 
 
-  return await searchHandle(
+  return searchHandle(
     '搜索产品失败',
     '搜索产品失败',
     d_product.searchProductsByMini,
     d_product.searchProductsByMini,
     _params,
     _params,
@@ -90,12 +91,72 @@ async function searchProductByMini(type, key, p, l){
     l);
     l);
 }
 }
 
 
+// 获取产品类型
+async function getProductTypes()
+{
+  let [err,res] = await handle(d_product.getProductTypeList());
+  if(err){
+    return [err,null];
+  }
+
+  return [null,res];
+}
 
 
+async function editType( id, productType)
+{
+  let [err,res] = await handle(d_product.editProductType(id, productType));
+  if(err){
+    return [err,null];
+  }
+  return [null,res];
+}
+
+async function addType( id, productType)
+{
+  // 数据校验
+  let [err,res] = await handle(d_product.addProductType(id, productType));
+  if(err){
+    return [err,null];
+  }
+  return [null,res];
+}
+
+// 删除产品类型
+async function deleteType(id) {
+  let err, res;
+
+  // 检索产品类型下是否有产品
+  [err, res] = await handle(d_product.getProductByTypeId(id));
+  if (err) {
+    // 删除失败
+    log.error('无法找到对应产品' + err.message);
+    return [err, null];
+  }
+  if (res.length > 0) {
+    // 存在产品
+    return [
+      {
+        eCode: codeMap.NotPermission,
+        eMsg: `该类型下存在产品,无法删除, 请先移除对应产品`
+      }, null];
+  }
+  [err, res] = await handle(d_product.deleteProductType(id));
+  if (err) {
+    log.error('删除失败' + err.message);
+    return [err, null];
+  }
+  return [null,res];
+
+}
 
 
 
 
 module.exports = {
 module.exports = {
   loadProduct,
   loadProduct,
   getProductInfo,
   getProductInfo,
   searchProduct,
   searchProduct,
-  searchProductByMini
+  searchProductByMini,
+  getProductTypes,
+  editType,
+  addType,
+  deleteType
 };
 };

+ 57 - 1
server/database/d_product.js

@@ -1,5 +1,6 @@
 const mysql = require('./mysql');
 const mysql = require('./mysql');
 const {searchSql,limitSql} = require("../tools/searchSql");
 const {searchSql,limitSql} = require("../tools/searchSql");
+const time = require("../tools/time_cjs");
 const log = require("../logger").logger("d_product","info")
 const log = require("../logger").logger("d_product","info")
 function loadProducts(key, page, limit) {
 function loadProducts(key, page, limit) {
   let sql = ``;
   let sql = ``;
@@ -111,11 +112,66 @@ function getProductById(id){
   let sql = `SELECT *,name as title FROM hfy_product WHERE proid = ? limit 1`;
   let sql = `SELECT *,name as title FROM hfy_product WHERE proid = ? limit 1`;
   return mysql.pq(sql,[id]);
   return mysql.pq(sql,[id]);
 }
 }
+
+function getProductByTypeId(id){
+  let sql = `SELECT * FROM hfy_product WHERE type_id = ?`;
+  return mysql.pq(sql,[id]);
+}
+
+// 获取产品类型列表
+function getProductTypeList() {
+  let sql = `SELECT * FROM hfy_product_type`;
+  return mysql.pq(sql, []);
+}
+
+// 编辑产品类型
+function editProductType(id, typeChange) {
+  let sql = ``
+  let values = [];
+  sql += `UPDATE hfy_product_type SET date_time = ?`;
+  values.push(time.getUnixTimeStamp());
+  if(typeChange.type_name){
+    sql += `,type_name = ?`;
+    values.push(typeChange.type_name);
+  }
+  if(typeChange.type_key){
+    sql += `,type_key = ?`;
+    values.push(typeChange.type_key);
+  }
+  if(typeChange.type_sort){
+    sql += `,type_sort = ?`;
+    values.push(typeChange.type_sort);
+  }
+  if(typeChange.type_logo){
+    sql += `,type_logo = ?`;
+    values.push(typeChange.type_logo);
+  }
+  sql += ` WHERE type_id = ?`;
+  values.push(id);
+  return mysql.pq(sql, values);
+}
+// 删除产品类型
+function deleteProductType(id) {
+  let sql = `DELETE FROM hfy_product_type WHERE type_id = ?`;
+  return mysql.pq(sql, [id]);
+}
+
+// 新增产品类型
+function addProductType(type) {
+  let sql = `INSERT INTO hfy_product_type (date_time, type_name, type_key, type_sort, type_logo) VALUES (?, ?, ?, ?, ?)`;
+  return mysql.pq(sql, [time.getUnixTimeStamp(), type.type_name, type.type_key, type.type_sort, type.type_logo]);
+}
+
 module.exports = {
 module.exports = {
   loadProducts,
   loadProducts,
   getProductInfo,
   getProductInfo,
   searchProducts,
   searchProducts,
   searchProductsByMini,
   searchProductsByMini,
   loadTypes,
   loadTypes,
-  getProductById
+  getProductByTypeId,
+  getProductById,
+  getProductTypeList,
+  editProductType,
+  deleteProductType,
+  addProductType
 }
 }

+ 1 - 1
server/database/pool.js

@@ -7,7 +7,7 @@
  * @LastDescript:
  * @LastDescript:
  */
  */
 const mysql = require('mysql2');
 const mysql = require('mysql2');
-const databseConfig = require('../configs/database.json'); //数据库连接的配置文件
+const databseConfig = require('../../configs/database.json'); //数据库连接的配置文件
 //作用
 //作用
 
 
 //导出全局连接池对象,使其可以统一使用此连接池操作数据库
 //导出全局连接池对象,使其可以统一使用此连接池操作数据库

+ 1 - 1
server/index.js

@@ -3,7 +3,7 @@ const session = require('express-session');
 const bodyParser = require('body-parser');
 const bodyParser = require('body-parser');
 
 
 const router = require('./router/index');
 const router = require('./router/index');
-const config_path = require('./configs/path');
+const config_path = require('../configs/path.json');
 const path = require("path");
 const path = require("path");
 const app = express();
 const app = express();
 const log = require('./logger').logger('app', 'info');
 const log = require('./logger').logger('app', 'info');

+ 1 - 1
server/router/r_base.js

@@ -9,7 +9,7 @@ const progressField = require('../map/progressField');
 const {isEmpty, toNumber} = require("../tools/typeTool_cjs");
 const {isEmpty, toNumber} = require("../tools/typeTool_cjs");
 const checkLogin = require("../middleware/checkSession");
 const checkLogin = require("../middleware/checkSession");
 const upload = require("../middleware/upload");
 const upload = require("../middleware/upload");
-const config_path = require("../configs/path");
+const config_path = require("../../configs/path.json");
 const log = require("../logger").logger("r_base","info");
 const log = require("../logger").logger("r_base","info");
 const fieldHandle = require("../tools/fieldHandle");
 const fieldHandle = require("../tools/fieldHandle");
 router.get('/carousel', async (req, res) => {
 router.get('/carousel', async (req, res) => {

+ 61 - 0
server/router/r_product.js

@@ -81,6 +81,67 @@ router.get('/mini', async (req, res) => {
   }
   }
 });
 });
 
 
+// 获取产品类型信息
+router.get('/types', async (req, res) => {
+  try {
+    let err, result;
+    [err, result] = await c.getProductTypes();
+    if (err) {
+      return controlError(res, err, null);
+    }
+    success(res, result);
+  } catch (e) {
+    ServerError(res, null, e.message);
+  }
+});
+
+// 编辑产品类型
+
+router.post('/type/edit', async (req, res) => {
+  try {
+    let err, result;
+    const id = req.query.id;
+    const body = req.body;
+    log.info(`[编辑产品类型] id=${id}, body=${JSON.stringify(body)}`);
+    [err, result] = await c.editType(id, body);
+    if(err){ return controlError(res, err, null);}
+    success(res, result);
+  }catch (e) {
+    ServerError(res, null, e.message);
+  }
+});
+
+
+router.post('/type/add', async (req, res) => {
+  try {
+    let err, result;
+    const body = req.body;
+    log.info(`[新增产品类型] body=${JSON.stringify(body)}`);
+    [err, result] = await c.addType(body);
+    if(err){ return controlError(res, err, null);}
+    success(res, result);
+  }catch (e) {
+    ServerError(res, null, e.message);
+  }
+});
+
+router.post('/type/del', async (req, res) => {
+  try {
+    let err, result;
+    const id = req.query.id;
+    const body = req.body;
+    log.info(`[移除产品类型] id=${id} body=${JSON.stringify(body)}`);
+    if(!id || !body || id !== body.id){
+      paramFail(res, "id is must be two ");
+      return;
+    }
+    [err, result] = await c.addType(body);
+    if(err){ return controlError(res, err, null);}
+    success(res, result);
+  }catch (e) {
+    ServerError(res, null, e.message);
+  }
+});
 
 
 /**
 /**
  * 获取产品信息
  * 获取产品信息

+ 1 - 1
server/tools/filePathTool.js

@@ -1,4 +1,4 @@
-const config_path = require("../configs/path.json");
+const config_path = require("../../configs/path.json");
 const dbField = require("../map/dbField");
 const dbField = require("../map/dbField");
 const {toNumber} = require("./typeTool_cjs");
 const {toNumber} = require("./typeTool_cjs");
 
 

+ 13 - 1
until/form/rules.js

@@ -57,6 +57,14 @@ const hrefRule = [
     message: '链接格式不正确'
     message: '链接格式不正确'
   },
   },
 ];
 ];
+const numberRule = [
+  {
+    type: 'number',
+    min: 0,
+    max: 999999999999999999,
+    message: '数字长度不符合'
+  }
+]
 
 
 
 
 const requireIdRule = [
 const requireIdRule = [
@@ -131,7 +139,7 @@ const cardRule = [
 export const paramsRules = [
 export const paramsRules = [
   {
   {
     name: "用户名验证规则",
     name: "用户名验证规则",
-    checkFields: ['name','workerName','buildingName','entranceName'],
+    checkFields: ['name','workerName','buildingName','entranceName', 'type_name'],
     rules: nameRule,
     rules: nameRule,
   },
   },
   {
   {
@@ -174,6 +182,10 @@ export const paramsRules = [
   {
   {
     checkFields: [/id/gi,'fileData', 'remark'],
     checkFields: [/id/gi,'fileData', 'remark'],
     rules: requireIdRule
     rules: requireIdRule
+  },
+  {
+    checkFields: ['sort', 'type_sort'],
+    rules: numberRule
   }
   }
 ]
 ]
 
 

+ 12 - 0
webpack.config.js

@@ -0,0 +1,12 @@
+const path = require('path')
+
+module.exports = {
+  resolve: {
+    extensions: ['.js', '.json', '.vue', '.ts'],
+    root: path.resolve(__dirname),
+    alias: {
+      '@': path.resolve(__dirname),
+      '~': path.resolve(__dirname),
+    },
+  },
+}