Pārlūkot izejas kodu

add:
轮播管理界面搜索框制作,基础接口制作

kindring 2 gadi atpakaļ
vecāks
revīzija
7b8a0c4113

+ 20 - 0
README.md

@@ -45,6 +45,7 @@ pm2 start npm --name "hfy" -- run start
 ## 功能新增设计
 ### 文件库功能 file_lab
 > id , 图片名称 , 图片路径 , 图片tag , 上传日期
+
 | 字段 | 类型 | 可选值 | 默认值 | 备注 |
 | --- | --- | --- | --- | --- |
 | id | int | pk | pk | 文件id |
@@ -55,6 +56,25 @@ pm2 start npm --name "hfy" -- run start
 | uploadTime | varchar | '' | '' | 上传时间 |
 
 
+### 轮播表
+> 用于存储轮播数据,可以选择站内产品或者新闻或者是特殊页面  
+
+
+| 字段            | 类型 | 可选值                                   | 默认值 | 备注     |
+|---------------| --- |---------------------------------------| --- |--------|
+| id            | int | pk                                    | pk | 轮播id   |
+| title         | varchar | ''                                    | '' | 轮播标题   |
+| type          | char | '0:product','1:news','2:page','3:href' | 0 | 轮播类型   |
+| imgPath       | varchar | ''                                    | '' | 轮播图片路径 |
+| value         | varchar | ''                                    | '' | 轮播链接   |
+| valueShowText | varchar | ''                                    | '' | 轮播展示文字 |
+| sort          | int | ''                                    | '' | 轮播排序   |
+| status        | char | '0:hide','1:show'                     | 0 | 轮播状态   |
+| createTime    | varchar | ''                                    | '' | 创建时间   |
+| updateTime    | varchar | ''                                    | '' | 更新时间   |
+
+
+
 
 
 

+ 52 - 0
components/public/form/inputRow.vue

@@ -0,0 +1,52 @@
+<script >
+export default {
+  name: 'inputRow',
+  props: {
+    label: {
+      type: String,
+      default: ''
+    },
+    form: {
+      type: Object,
+      default: () => {}
+    },
+    checkFormItem: {
+      type: Function,
+      default: () => {}
+    },
+    state: {
+      type: Number,
+      default: 0
+    },
+    msg: {
+      type: String,
+      default: ''
+    }
+  },
+  data(){
+    return {
+    }
+  },
+  methods: {
+  }
+}
+</script>
+
+<template>
+  <div class="w-full px-1.5 flex">
+    <div class="w-4/12 flex items-center u px-1">
+      <p class="w-full text-justify">{{ label }}</p>
+    </div>
+    <div class="w-8/12">
+      <slot></slot>
+      <div :class="`w-full  ${state===0?'text-red-600':'text-green-300'}`" v-show="msg" >
+        {{msg}}
+      </div>
+      <slot name="extra"></slot>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+
+</style>

+ 77 - 0
components/public/hideScroll.vue

@@ -0,0 +1,77 @@
+<template>
+<div class="hide-scroll" ref="hide-scroll">
+  <div class="scroll-content"  ref="scroll">
+    <div :style="`width:${width}px;`">
+      <slot></slot>
+    </div>
+  </div>
+</div>
+</template>
+
+<script>
+export default {
+  name: "hideScroll",
+  props: {
+    itemHeight: {
+      type: Number,
+      default: 50
+    }
+  },
+  data(){
+    return {
+      width:0,
+      height: 0,
+      timer: null,
+      timeWait: 700,// 等待延迟
+    }
+  },
+  mounted(){
+    this.width = this.$refs["hide-scroll"].offsetWidth;
+    this.height = this.$refs["hide-scroll"].offsetHeight;
+    window.addEventListener('resize',()=>{
+      this.width = this.$refs["hide-scroll"].offsetWidth;
+      this.height = this.$refs["hide-scroll"].offsetHeight;
+    })
+    let scroll = this.$refs["scroll"]
+    scroll.addEventListener('scroll',(e)=>{
+      // console.log('scroll change')
+      // console.log(scroll.scrollTop)
+      if(this.timer){
+        clearTimeout(this.timer);
+        this.timer = null;
+      }
+      let scrollTop = scroll.scrollTop;
+      let index = Math.floor(scrollTop/this.itemHeight);
+      //  获取当前位置应该对应的元素index
+      this.timer = setTimeout(()=>{
+        this.$emit('onScroll', {
+          scrollTop:scrollTop,
+          index:index
+        });
+      },this.timeWait);
+    })
+  },
+  methods:{
+  }
+}
+</script>
+
+<style scoped >
+.hide-scroll{
+  width: 100%;
+  height: 100%;
+  position: absolute;
+  overflow: hidden;
+
+
+}
+.hide-scroll .scroll-content{
+  width: calc(100% + 100px);
+  height: 100%;
+  overflow:auto;
+
+}
+.hide-scroll .scroll-content div{
+
+}
+</style>

+ 1 - 0
components/public/imageTable.vue

@@ -243,6 +243,7 @@ export default {
       console.log(`selectImg ${fileData.filePath} fileId is ${fileData.fileId}`);
       this.imgUrl = fileData.filePath;
       this.fileData = fileData;
+
     },
     // 编辑图片信息
     editImg(fileData){

+ 1 - 1
components/public/popCard.vue

@@ -4,7 +4,7 @@
 
 <template>
   <div class="w-8/12 h-5/6 p-2 rounded flex flex-col justify-center border bg-white">
-    <div class="p-header mx-1.5 h-16  text-2xl flex border-b ">
+    <div class="p-header mx-1.5 h-16  text-2xl flex border-b items-center">
       <div class="icon-btn-group">
         <slot name="close-group"></slot>
       </div>

+ 225 - 0
components/search/searchBox.vue

@@ -0,0 +1,225 @@
+<script>
+import handle from "../../until/handle";
+import {rCode} from "../../map/rcodeMap_esm";
+import Loading from "../public/loading.vue";
+import HideScroll from "../public/hideScroll.vue";
+export default {
+  name: "searchBox",
+  components: {Loading, HideScroll},
+  props: {
+    searchPlaceholder: {
+      type: String,
+      default: '输入搜索关键字'
+    },
+    loadTip: {
+      type: String,
+      default: '加载中'
+    },
+    loadMsg: {
+      type: String,
+      default: '加载失败'
+    },
+    limit: {
+      type: Number,
+      default: 20
+    },
+    loadDataApi: {
+      type: Function,
+      default: () => {}
+    },
+
+  },
+  data(){
+    return {
+      searchLoading:false,// 搜索加载框
+      searchTimer: null,// 防抖搜索框
+      searchKey: '',// 关键字
+      page: 1,// 页数
+      count: 20,// 每页数量
+      total: 0,// 当前数据总量
+      loadState: 0,// 加载状态
+      loadLock: false,// 加载锁
+      domPlaceholder: [],//当前的数据列表
+    }
+  },
+  methods: {
+    async loadData(page,isPush){
+      if(isPush){
+        page = 1;
+        this.loadState = 0;
+      }
+      this.searchLoading = true;
+      // console.log('---------')
+      // console.log(page);
+      let searchParam = {
+
+      }
+      // 1.搜索相关设备
+      let [err,res] = await this.loadDataApi({
+        key: this.searchKey,//
+        page: page,// 当前页数
+      });
+      this.loadLock = false;
+      this.searchLoading = false;
+      // 2.检查返回值是否正常
+      if (err){
+        console.log(err);
+        this.msg = err.message;
+        isPush?this.loadState=2:this.$message.error(this.msg);
+        return false;
+      }
+      this.loadState = 1;
+      // 只有在第一页时会需要加载总数
+      if(res.page <= 1){
+        this.total = res.total;
+      }
+
+
+      // 3.根据是否为新设备创建新数据集合
+      if(isPush || page===1){
+        this.domPlaceholder = new Array(res.total)
+          .fill({
+            loaded: false,
+            val:{}
+          });
+      }
+      console.log('total' + this.domPlaceholder.length)
+
+      // 4.将当前的分块请求结果填充至指定位置
+      let startIndex = (res.page - 1) * res.limit;
+      let key = res.key;
+
+      console.log(`startIndex:${startIndex} , endIndex:${res.count+startIndex}`);
+      for (let i = startIndex,n=0;n<res.count;n++,i++){
+        let item = res.data[n];
+        if(item){
+          // 时间字段转换
+          // item.subTitle = time.dateFormat(time.timeStamp_to_Date(item.loginTime),'YY-MM-DD H:m:s');
+          // 展示字段转换
+          item['innerText'] = item.showText.replace(key,`<span style="color:red;">${key}</span>`);
+        }
+        this.$set(this.domPlaceholder,i,{
+          loaded:!!item,
+          val:item?item:{}
+        });
+      }
+      // 判断当前位置
+    },
+    onSearch(v){
+      console.log('search');
+      console.log(v);
+      if(this.loadLock){
+        this.$message.info('加载设备列表中')
+        return
+      }
+      this.loadLock = true;
+      this.loadData(1);
+    },
+
+    async onScrollChange(data){
+      let scrollTop = data.scrollTop;
+      let index = data.index;
+      console.log('onScrollChange'+scrollTop + 'index:' + index);
+      //获取
+      let page = Math.floor(scrollTop/50/this.limit);
+      console.log(page);
+      // 加载数据
+      if(page < this.page){
+        return console.log(`不加载数据`)
+      }
+      await this.loadData(page + 1);
+      if(index + this.limit > this.total){
+        console.log(`同时获取下一页数据`);
+        await this.loadData(page + 2);
+      }
+    },
+    onSelectedItem(item){
+      this.$emit('onSelectedItem',item);
+    }
+  }
+}
+</script>
+
+<template>
+<div class="w-full h-full rounded bg-yellow-50 flex flex-col">
+    <div class="w-full h-auto">
+<!--      其他搜索项 -->
+      <slot name="otherSearchItem"></slot>
+    </div>
+    <div class="serarch mt-1 box-border px-1">
+      <a-input-search
+        :placeholder="searchPlaceholder"
+        enter-button
+        :loading="searchLoading"
+        v-model="searchKey"
+        @search="onSearch"
+        allow-clear
+        style="border-radius: 0;"
+      />
+    </div>
+  <div class="devices w-full h-48 md:h-full bg-white mt-2 relative">
+    <loading
+      :loading-state="loadState"
+      :tip="loadTip"
+    >
+      <hide-scroll
+        @onScroll="onScrollChange"
+        :itemHeight="50"
+      >
+        <div
+          v-for="(item,i) in domPlaceholder"
+          :key="'search-key:'+i"
+          class="item cursor-default" >
+          <div
+            v-if="item.loaded"
+            class="text text-base">
+            <span v-html="item.val.innerText"></span>
+            <div class="subText text-gray-300 text-xs">{{ item.val.subTitle }}</div>
+          </div>
+          <div
+            v-if="item.loaded"
+            class="action">
+            <a-button class="mx-1" @click="onSelectedItem(item.val)">选中</a-button>
+          </div>
+          <div class="w-full h-full bg-black text-red-500 opacity-30" v-else-if="!item.loaded">
+            {{i}}.....loading
+          </div>
+
+        </div>
+      </hide-scroll>
+
+      <template v-slot:loadFail>
+        <div class="w-full h-full flex justify-center items-center">
+          <h2 class="text-2xl text-red-700">{{loadMsg}}</h2>
+          <a-button primary @click="loadData(1)">重新加载</a-button>
+        </div>
+      </template>
+    </loading>
+  </div>
+
+</div>
+</template>
+
+<style scoped>
+.item{
+  width: 100%;
+  height: 50px;
+  display: flex;
+  align-items: center;
+  border-bottom: 1px solid #66ccff;
+  padding: 0 0.25em;
+  box-sizing: border-box;
+}
+.text{
+  width: 100%;
+  height: 100%;
+
+}
+.subText{
+  width: 100%;
+  color: #afa7a7;
+}
+.action{
+  display: flex;
+}
+</style>

+ 12 - 0
map/apiMap.js

@@ -16,6 +16,9 @@ export const apiMap = {
   searchProduct: {
     path: `/api/product/search`,
   },
+  searchProductMini: {
+    path: `/api/product/mini`,
+  },
   productInfo: {
     path: `/api/product`
   },
@@ -31,6 +34,9 @@ export const apiMap = {
   searchNews: {
     path: `/api/news/search`,
   },
+  searchNewsMini: {
+    path: `/api/news/mini`,
+  },
   newsInfo: {
     // 新闻与解决方案都是文章,不多做接口
     path: `/api/solution`
@@ -40,6 +46,12 @@ export const apiMap = {
   },
   downloadItem: {
     path: `/api/download/`
+  },
+  carouselList: {
+    path: `/api/base/carousel`
+  },
+  baseTypes: {
+    path: `/api/base/types`
   }
 }
 

+ 9 - 0
map/dbField_esm.js

@@ -13,6 +13,8 @@ export const db_user = {
   }
 }
 
+
+
 export const db_base = {
   // 文件类型
   fileType: {
@@ -20,6 +22,13 @@ export const db_base = {
     other: 0,
     image: 1,
     video: 2,
+  },
+  // 轮播类型
+  carouselType: {
+    href: 0,
+    production: 1,
+    news: 2,
+    page: 3,
   }
 }
 

+ 1 - 1
pages/manger/Login.vue

@@ -161,7 +161,7 @@ export default {
     if(url){
       this.jumpUrl = url;
     }
-
+    this.$refs.captchaImg.refreshCaptcha();
   },
   methods:{
     setCaptcha(captcha){

+ 351 - 17
pages/manger/index/carousel.vue

@@ -1,6 +1,7 @@
 <script>
 import axios from "axios";
 import {defineComponent} from "vue";
+import fieldIsAllow from "../../../until/fieldIsAllow"
 import RoundedTitle from "../../../components/public/roundedTitle.vue";
 import {rCode} from "../../../map/rcodeMap_esm";
 import handle from "../../../until/handle";
@@ -8,12 +9,23 @@ import ImageViewer from "../../../components/public/imageViewer.vue";
 import ImageTable from "../../../components/public/imageTable.vue";
 import Pop from "../../../components/public/pop.vue";
 import PopCard from "../../../components/public/popCard.vue";
+import InputRow from "../../../components/public/form/inputRow.vue";
+import dbField_esm from "../../../map/dbField_esm";
+import {apiMap} from "../../../map/apiMap";
+import SearchBox from "../../../components/search/searchBox.vue";
+import {pTypes} from "../../../map/productMap";
+import {newsType} from "../../../map/newMap";
 export default defineComponent({
   name: 'carousel',
-  components: {PopCard, Pop, ImageTable, ImageViewer, RoundedTitle},
+  computed: {
+    dbField_esm() {
+      return dbField_esm
+    }
+  },
+  components: {SearchBox, InputRow, PopCard, Pop, ImageTable, ImageViewer, RoundedTitle},
   async asyncData(ctx){
     // 加载轮播图数据
-    let [err,res] = await handle(axios.get('/api/base/carousel'));
+    let [err,res] = await handle(axios.get(apiMap.carouselList.path));
     if(err){
       return {};
     }
@@ -28,6 +40,7 @@ export default defineComponent({
   },
   data(){
     return {
+      limit: 10,
       loading: false,
       carouselList: [],
       popShow: false,
@@ -35,6 +48,85 @@ export default defineComponent({
       carouselPopShow: true,
       carouselPopLoading: false,
       isEditCarousel: false,
+      carouselData: {},
+      form: {
+        // 排序
+        sort: {
+          val:0,
+          init: 0,
+          msg: '',
+          state: 0,
+        },
+        // 状态 0:禁用,1:启用
+        status: {
+          val: 0,
+          init: 0,
+          msg: '',
+          state: 0,
+        },
+        // 图像资源id, 0为无图
+        file: {
+          val: 0,
+          init: 0,
+          msg: '',
+          state: 0,
+        },
+        // 轮播类型 '0:product','1:news','2:page','3:href'
+        type: {
+          val: 0,
+          init: 0,
+          msg: '',
+          state: 0,
+          options: [
+            {label: '直接链接', value: 0},
+            {label: '内部产品', value: 1},
+            {label: '指向新闻', value: 2},
+            {label: '内部页面', value: 3},
+          ],
+        },
+        // 具体值
+        value: {
+          val: '',
+          init: '',
+          msg: '',
+          state: 0,
+          showText: '',// 展示用字段
+          oldShowText: '',
+        },
+        // file
+        fileData: {
+          val: '',
+          init: '',
+          msg: '',
+          state: 0,
+          showText: '',// 展示用字段
+          oldShowText: '',
+        },
+      },
+      productSelectVisible: false,
+      productSearch: {
+        type: {
+          val: pTypes[0].key,
+          oldVal: pTypes[0].key,
+          init: pTypes[0].key,
+          msg: '',
+          options: pTypes,
+        }
+      },
+      newsSelectVisible: false,
+      newsVisible: false,
+      newsSearch: {
+        type: {
+          val: newsType[0].key,
+          oldVal: newsType[0].key,
+          init: newsType[0].key,
+          msg: '',
+          options: pTypes,
+        }
+      },
+
+      imageSelectVisible: false,
+
     }
   },
   mounted() {
@@ -45,14 +137,13 @@ export default defineComponent({
   methods: {
     async getCarouselList(){
       this.loading = true;
-      let [err,res] = await handle(this.$axios.get('/api/base/carousel'));
+      let [err,res] = await handle(this.$axios.get(apiMap.carouselList.path));
       this.loading = false;
       if(err){
         if(this.NotificationKey){
           this.$notification.close(this.NotificationKey);
         }
         this.NotificationKey = `open${Date.now()}`;
-
         return this.$notification.error({
           message: '轮播数据加载失败',
           description:`异常: ${err.message}`,
@@ -88,22 +179,142 @@ export default defineComponent({
         return {}
       }
     },
-    async addCarouselItem(){
-      // 打开弹窗. 选择图片,填写链接地址,排序
+    // 搜索产品,只需要产品名等信息
+    async getProductSearch(searchParam){
+      console.log(searchParam)
+      if(this.productSearch.type.val !== this.productSearch.type.oldVal){
+        searchParam.p = 1;
+      }
+      searchParam.type = this.productSearch.type.val;
+      searchParam.l = this.limit;
+      searchParam.p = searchParam.page;
+      let [err,res] = await handle(
+        this.$axios.get(
+          apiMap.searchProductMini.path,
+          {params:searchParam})
+      );
+      if(err){
+        console.log(err);
+        return [{message:'请求数据失败'},null];
+      }
 
+      let result = res.data;
+      if(result.code === rCode.OK){
+        this.productSearch.type.oldVal = this.productSearch.type.val;
+        // data 转换
+        result.data = result.data.map(item=>{
+          item.showText=item.name;
+          return item;
+        });
+        return [null,result];
+      }else{
+        // 可捕获的服务器错误
+        return [{message:result.msg},null];
+      }
+    },
+    // 加载轮播默认数据
+
+
+    checkFormItem(field,enumOptions,reCheckField){
+      let formItem = this.form[field];
+      if (formItem){
+        if (enumOptions){
+          // 遍历枚举
+          for (let i = 0; i < enumOptions.length; i++) {
+            let enumOption = enumOptions[i];
+            if (enumOption.value === formItem.val){
+              return true;
+            }
+          }
+          formItem.msg = '选项不在范围内';
+          return false;
+        }
+        if(reCheckField){
+          // 检查用字段
+          formItem.msg = fieldIsAllow({
+            [reCheckField]:formItem.val,
+          })
+        }else{
+          formItem.msg = fieldIsAllow({
+            [field]:formItem.val,
+          })
+        }
+      }else{
+        let r = true;
+        for (const fieldKey in this.form) {
+          this.form[fieldKey].msg = fieldIsAllow({
+            [fieldKey]:this.form[fieldKey].val,
+          })
+          if (this.form[fieldKey].msg){
+            r = false;
+          }
+        }
+        return r
+      }
     },
+    initCarouseForm(){
+      this.carouselData = {};
+      let keys = Object.keys(this.form);
+      for(let i = 0; i < keys.length; i++){
+        let key = keys[i];
+        this.form[key].val = this.form[key].init;
+        this.form[key].msg = '';
+        this.form[key].state = 0;
+        this.form.value.showText = '';
+      }
+    },
+
+    openAddCarouselModal(){
+      // 初始化表单
+      this.initCarouseForm();
+      // 打开弹窗. 选择图片,填写链接地址,排序
+      this.carouselPopShow = true;
+      this.isEditCarousel = false;
+    },
+
+
+
+
     showPop(){
       this.popShow = true;
       this.popLoading = false;
     },
     cancelPop(){
-      this.popShow = false;
-      this.popLoading = false;
+      this.imageSelectVisible = false;
     },
     okHandle(fileItem){
+      console.log('文件列表');
       console.log(fileItem);
       this.cancelPop();
-    }
+      this.$nextTick(()=>{
+        this.form.fileData.val = fileItem.filePath;
+        this.form.fileData.state = 1;
+        this.form.fileData.msg = '';
+        this.form.fileData.showText = fileItem.filePath;
+      })
+    },
+    onProductSearchHandle(e){
+      console.log(`onProductSearchHandle ${e}`);
+      console.log(e);
+      console.log(this.productSearch.type.val);
+      // this.productSearch.type
+    },
+    onSelectedItemHandle(item){
+      console.log(`selected item ${item}`);
+      console.log(item);
+      this.form.value.val = item.id;
+      this.form.value.showText = item.showText;
+    },
+    onTypeChangeHandle(e){
+      console.log(`type change ${e}`);
+      // 清除其他值
+      this.form.value.msg = '';
+      this.form.value.showText = '';
+    },
+    loadDefaultProductItem(){
+      this.productSearch.type.val = pTypes[0].key;
+      this.$refs.productSearch.loadData(1,true);
+    },
   },
 
 })
@@ -118,7 +329,7 @@ export default defineComponent({
     <div class="py-1 border-b border-cyan-300 flex justify-between">
         点击下方快进行管理轮播图数据,一次性不要添加过多轮播图
 <!--      新增按钮-->
-      <a-button type="primary" class="ant-icon-btn" icon="plus" @click="addCarouselItem" :loading="loading"></a-button>
+      <a-button type="primary" class="ant-icon-btn" icon="plus" @click="openAddCarouselModal" :loading="loading"></a-button>
 <!--      刷新按钮-->
       <a-button type="primary" class="ant-icon-btn" icon="reload" @click="getCarouselList" :loading="loading"></a-button>
     </div>
@@ -147,18 +358,138 @@ export default defineComponent({
   </div>
   </div>
 
-  <pop :show="popShow" :loading="popLoading">
-    <image-table @cancel="cancelPop" @ok="okHandle"></image-table>
-    <!--      <choose-to-sit></choose-to-sit>-->
-  </pop>
+
   <pop :show="carouselPopShow" :loading="carouselPopLoading">
     <pop-card>
       <template slot="header" class="w-full">
         {{isEditCarousel ? '编辑轮播图' : '新增轮播图'}}
       </template>
-      <template slot="close-group">取消</template>
+      <template slot="close-group">
+        <a-button icon="close"  @click="carouselPopShow = false"></a-button>
+      </template>
       <div class="w-full">
-        内容
+         <input-row :msg="form.sort.msg"
+                    label="排序">
+           <a-input-number v-model="form.sort.val"
+                           @focus="form.password.msg=''"
+                           @blur="checkFormItem('sort')"
+           />
+         </input-row>
+
+<!--          轮播类型选择 -->
+          <input-row :msg="form.type.msg"
+                      label="轮播类型">
+            <a-radio-group v-model="form.type.val" @change="onTypeChangeHandle">
+              <a-radio-button v-for="opt in form.type.options"
+                              :key="'cType'+opt.value"
+                              :value="opt.value">
+                {{ opt.label }}
+              </a-radio-button>
+            </a-radio-group>
+          </input-row>
+
+<!--        轮播具体值 -->
+<!--        链接-->
+        <input-row
+          v-show="form.type.val === dbField_esm.db_base.carouselType.href"
+          :msg="form.value.msg"
+                    label="输入链接">
+          <a-input v-model="form.value.val"
+                   placeholder="输入要指向的链接地址"
+                   @focus="form.value.msg=''"
+                   @blur="checkFormItem('value', null,'href')"
+          />
+        </input-row>
+<!--        产品选择-->
+        <input-row
+          v-show="form.type.val === dbField_esm.db_base.carouselType.production"
+          :msg="form.value.msg"
+          label="选择产品">
+          {{ form.value.showText }}
+          <a-popover  v-model="productSelectVisible" title="选择产品" trigger="click">
+            <div slot="content" class="searchBox" >
+              <search-box
+                class="h-72"
+                :loadDataApi="getProductSearch"
+                :limit="limit"
+                search-placeholder="请输入产品名称关键字"
+                loadTip="正在搜索产品中..."
+                ref="productSearch"
+                @onSelectedItem="onSelectedItemHandle"
+              >
+                <div class="w-full" slot="otherSearchItem">
+                  <a-radio-group v-model="productSearch.type.val"
+                                 @change="onProductSearchHandle">
+                    <a-radio-button v-for="opt in productSearch.type.options"
+                                    :key="'cType'+opt.key"
+                                    :value="opt.key">
+                      {{ opt.text }}
+                    </a-radio-button>
+                  </a-radio-group>
+                </div>
+              </search-box>
+            </div>
+            <a-button type="primary" @click="loadDefaultProductItem">
+              选择产品
+            </a-button>
+          </a-popover>
+        </input-row>
+<!--        新闻选择-->
+        <input-row
+          v-show="form.type.val === dbField_esm.db_base.carouselType.production"
+          :msg="form.value.msg"
+          label="文章选择">
+          {{ form.value.showText }}
+          <a-popover  v-model="productSelectVisible" title="选择你需要的文章" trigger="click">
+            <div slot="content" class="searchBox" >
+              <search-box
+                class="h-72"
+                :loadDataApi="getProductSearch"
+                :limit="limit"
+                search-placeholder="请输入产品名称关键字"
+                loadTip="正在搜索产品中..."
+                ref="productSearch"
+                @onSelectedItem="onSelectedItemHandle"
+              >
+                <div class="w-full" slot="otherSearchItem">
+                  <a-radio-group v-model="productSearch.type.val"
+                                 @change="onProductSearchHandle">
+                    <a-radio-button v-for="opt in productSearch.type.options"
+                                    :key="'cType'+opt.key"
+                                    :value="opt.key">
+                      {{ opt.text }}
+                    </a-radio-button>
+                  </a-radio-group>
+                </div>
+              </search-box>
+            </div>
+            <a-button type="primary" @click="loadDefaultProductItem">
+              选择产品
+            </a-button>
+          </a-popover>
+        </input-row>
+
+<!--        选择图片 -->
+        <input-row label="轮播图片">
+          <a-popover  v-model="imageSelectVisible"
+                      class="w-full"
+                     trigger="click">
+              <image-table slot="content"
+                           class="w-full h-full"
+                           @cancel="imageSelectVisible = false"
+                           @ok="okHandle"></image-table>
+
+            <div class="w-full h-60 rounded relative">
+              <image-viewer class="" :src="form.fileData.showText"></image-viewer>
+              <div class="absolute w-full h-full left-0 top-0
+              justify-center text-white bg-gray-400
+              items-center text-2xl flex opacity-0 hover:opacity-70">
+                点击选择图片
+              </div>
+            </div>
+
+          </a-popover>
+        </input-row>
       </div>
       <template class="w-full" slot="footer">
         <a-button>{{isEditCarousel? '保存': '新增'}}</a-button>
@@ -169,5 +500,8 @@ export default defineComponent({
 </template>
 
 <style scoped>
-
+.searchBox{
+  width: 420px;
+  height: 520px;
+}
 </style>

+ 8 - 8
server/configs/database.json

@@ -1,12 +1,12 @@
 {
-  "host-": "127.0.0.1",
-  "host": "154.23.140.251",
+  "host": "127.0.0.1",
+  "host-": "154.23.140.251",
   "port": "3306",
-  "user-": "root",
-  "user": "mysql85931094",
-  "password0": "12345678",
-  "password": "zA564uyabd",
+  "user": "root",
+  "user-": "mysql85931094",
+  "password": "12345678",
+  "password-": "zA564uyabd",
   "connectionLimit": "100",
-  "database2": "site",
-  "database": "mysql85931094_db"
+  "database": "site",
+  "database-": "mysql85931094_db"
 }

+ 7 - 0
server/control/c_base.js

@@ -145,6 +145,13 @@ async function deleteFile(fileId){
   }
   return [null,true];
 }
+
+
+function getTypes(){
+  // 同时获取所有需要获取的基础数据
+  let err,pType,nType;
+  // todo 同时获取所有需要获取的基础数据
+}
 module.exports = {
   getCarousel,
   uploadFile,

+ 22 - 1
server/control/product.js

@@ -68,11 +68,32 @@ async function searchProduct(type, key, p, l)
     l);
 }
 
+async function searchProductByMini(type, key, p, l){
+  p = p || 1;
+  l = l || 10;
+  let _params = {
+  }
+  if(type !== 'all'){
+    _params.type = type;
+  }
+  if(key){
+    _params.key = key
+  }
+
+  return await searchHandle(
+    '搜索产品失败',
+    d_product.searchProductsByMini,
+    _params,
+    p,
+    l);
+}
+
 
 
 
 module.exports = {
   loadProduct,
   getProductInfo,
-  searchProduct
+  searchProduct,
+  searchProductByMini
 };

+ 38 - 1
server/database/d_product.js

@@ -62,8 +62,45 @@ function searchProducts(type='array',searchParam,page,limit){
   return searchSql(mysql.pq,type,sql,values,limit,page);
 }
 
+/**
+ * 搜索产品,只返回id和name等信息,节约流量
+ * @param type
+ * @param searchParam
+ * @param page
+ * @param limit
+ * @returns {*}
+ */
+function searchProductsByMini(type='array',searchParam,page,limit){
+  let sql = ``;
+  let values = [];
+  if(type === 'count'){
+    sql = `select count(*) as total `;
+  }else{
+    sql = `select
+    p.proid as id,
+    p.name,
+    p_type.type_key
+    `;
+  }
+  sql += `
+   from
+   hfy_product as p,
+   hfy_product_type as p_type
+  `
+  sql += `where p.type_id = p_type.type_id`
+  if(searchParam.key){
+    sql += ` and p.name like '%${searchParam.key}%'`
+  }
+  if(searchParam.type){
+    sql += ` and p_type.type_key = ?`
+    values.push(searchParam.type)
+  }
+  return searchSql(mysql.pq,type,sql,values,limit,page);
+}
+
 module.exports = {
   loadProducts,
   getProductInfo,
-  searchProducts
+  searchProducts,
+  searchProductsByMini
 }

+ 0 - 1
server/database/mysql.js

@@ -20,7 +20,6 @@ function query(sql, values, cb) {
         // log.debug(`querySQL:${sql}  QueryValues:[${values.join(',')}]`)
         conn.query(sql, values, cb);
         conn.release();
-        conn.end();
     })
 } //简写部分代码
 

+ 15 - 0
server/router/r_base.js

@@ -95,5 +95,20 @@ router.delete('/file/:fileId',checkLogin(progressField.session_hfy),async (req,r
   }
 })
 
+router.get('/types',checkLogin(progressField.session_hfy),async (req,res)=>{
+  try{
+    log.info(`[类型] 获取基础类型信息`)
+    // 从数据库中读取数据.
+    let [err, data] = await c.getTypes();
+    if(err){
+      log.warn(`[类型] 获取类型失败 ${err.eMsg||err.message}`);
+      return controlError(res, err,`获取类型失败 ${err.eMsg||err.message}`);
+    }
+    return success(res, data);
+  }catch (e){
+    console.log(e);
+    ServerError(res, e, `获取数据异常 ${e.message}`);
+  }
+});
 
 module.exports =  router;

+ 27 - 0
server/router/r_product.js

@@ -56,6 +56,31 @@ router.get(
       }
   });
 
+router.get('/mini', async (req, res) => {
+  try{
+    log.info(`[搜索产品] 只获取基础值`)
+    let err, result;
+    let {key, l, p, type} = req.query;
+    type = type || 'all';
+    l = typeTool.toNumber(l);
+    p = typeTool.toNumber(p);
+    log.info(`page=${p},limit=${l}`);
+    [err, result] = await c.searchProductByMini(type, key, p, l);
+    if(err){
+      log.info(`[搜索产品] err=${err}`);
+      return controlError(res, err, null);}
+    log.info(`result len=${result.arr.length}`)
+    searchSuccess(res,
+      result.arr,
+      result.total,
+      result.page,
+      result.limit,
+    );
+  }catch (e) {
+    ServerError(res, null, e.message);
+  }
+});
+
 
 /**
  * 获取产品信息
@@ -79,4 +104,6 @@ router.get('/:id', async (req, res) => {
   }
 });
 
+
+
 module.exports =  router ;

+ 25 - 4
store/index.js

@@ -1,8 +1,25 @@
 import {login_types,state,mutations,actions,getters} from "./login";
+import indexTypes from "./indexStoreTypes";
 import login from "./login";
+import {apiMap} from "../map/apiMap";
+
 
 export const modules = {
   index: {
+    state: {
+      // 产品类别
+      pTypes: [],
+      // 新闻类别
+      nTypes: [],
+    },
+    mutations: {
+      [indexTypes.mutations.setProductTypes](state, pTypes) {
+        state.pTypes = pTypes;
+      },
+      [indexTypes.mutations.setNewsTypes](state, nTypes) {
+        state.nTypes = nTypes;
+      }
+    },
     actions : {
       async nuxtServerInit ({ commit }, { req }) {
         console.log('nuxtServerInit');
@@ -10,10 +27,14 @@ export const modules = {
           if (req.session.owner) {
             commit('login/'+login_types.mutations.userLogin, req.session.owner)
           }
-          if (req.session.captcha) {
-            // 更新login 模块的captcha
-            commit('login/'+login_types.mutations.SET_CAPTCHA, req.session.captcha)
-          }
+          // 获取新闻与产品类别信息
+          let err, res;
+          [err, res] = await this.$axios.$get(apiMap.baseTypes.path);
+          if(err){return console.log(err);}
+          if(res.code !== rCode.OK){return console.log(res.msg);}
+          let result = res.data;
+          commit(indexTypes.mutations.setProductTypes, result.pTypes);
+          commit(indexTypes.mutations.setNewsTypes, result.pTypes);
         }
 
       },

+ 6 - 0
store/indexStoreTypes.js

@@ -0,0 +1,6 @@
+export const login_types = {
+    mutations: {
+      setProductTypes: 'setProductTypes',
+      setNewsTypes: 'setNewsTypes',
+    }
+}

+ 18 - 0
until/fieldIsAllow.js

@@ -43,6 +43,20 @@ const nameRule = [
     }
 ]
 
+const hrefRule = [
+    {
+        type: 'string',
+        min: 1,
+        max: 500,
+        message: '链接长度不符合'
+    },
+    {
+        type: 'string',
+        regex: /^http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w- .\/?%&=]*)?$/,
+        message: '链接格式不正确'
+    },
+];
+
 let checkCode = function (val) {
     let p = /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/;
     let factor = [ 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2 ];
@@ -136,6 +150,10 @@ const paramsRules = [
         keys: ['card','workerCard'],
         rules: cardRule,
     },
+    {
+        keys: ['href'],
+        rules: hrefRule,
+    }
 ]
 
 function _isEmpty(s) {