Browse Source

Merge branch 'main' into feature/v1.0.36

lianbingjie 1 năm trước cách đây
mục cha
commit
8f9f5b1197

+ 32 - 5
apps/bigmember_pc/src/components/forecast/ForeCast.vue

@@ -34,8 +34,9 @@
         <div class="filter-label" slot="header">合同到期时间:</div>
       </TimeSelector>
       <!-- 地区 -->
-      <div class="filter-region" v-show="tabActive === 'fwl'">
+      <div class="filter-line" v-show="tabActive === 'fwl'">
         <AreaCityCountryCascader
+          class="filter-line-item"
           ref="areaCityCountry"
           :showTags="false"
           :isHaveAll="isShowAllCountry"
@@ -46,6 +47,12 @@
         >
           <div class="filter-label" slot="header">项目地区:</div>
         </AreaCityCountryCascader>
+        <div class="filter-line-item">
+          <div class="filter-label">价格区间:</div>
+          <div class="filter-content">
+            <MoneySelection v-model="filterPrice" @change="onPriceChange" />
+          </div>
+        </div>
       </div>
     </div>
     <!-- 中标企业预测-->
@@ -531,6 +538,7 @@ import {
   changeEntGroup,
   forecastArea
 } from '@/api/modules'
+import MoneySelection from '@/components/selector-cascader/MoneySelection.vue'
 import AreaCityCountryCascader from '@/components/selector-cascader/AreaCityCountryCascader.vue'
 export default {
   props: [
@@ -558,6 +566,7 @@ export default {
     [Dialog.name]: Dialog,
     [Button.name]: Button,
     [Checkbox.name]: Checkbox,
+    MoneySelection,
     Empty,
     GroupCard,
     Tips,
@@ -589,6 +598,10 @@ export default {
         group: '', // 当前点击编辑的分组信息
         fid: ''
       },
+      filterPrice: {
+        min: '',
+        max: ''
+      },
       tabActive: 'fwl',
       regionMapData: '',
       sourceAreaMap: {},
@@ -1170,6 +1183,11 @@ export default {
       this.$emit('changeArea', regionMap)
       this.$emit('changeId', this.selectedId)
     },
+    onPriceChange() {
+      this.selectedId = []
+      this.$emit('changePrice', this.filterPrice)
+      this.$emit('changeId', this.selectedId)
+    },
     // 重置筛选(目前需求只有潜在项目预测-服务类项目有)
     goResetFilter() {
       this.regionMapData = ''
@@ -1178,6 +1196,8 @@ export default {
       this.$refs.timeSelector.setState({
         exact: 'all'
       })
+      this.filterPrice = { min: '', max: '' }
+      this.$emit('changePrice', this.filterPrice)
       this.$emit('onChange', { start: 0, end: 0 })
     }
   }
@@ -1211,6 +1231,7 @@ export default {
   }
 
   .filtrate_box {
+    padding: 0 40px;
     border-bottom: 1px dashed #e0e0e0;
 
     // min-height: 123px;
@@ -1228,16 +1249,22 @@ export default {
       color: #686868;
     }
 
-    .filter-region {
+    .filter-line {
+      display: flex;
+      align-items: center;
       border-top: 1px dashed #e0e0e0;
-      background: #f2f2f4;
+      background: #fff;
       .area-city-country {
         border-radius: 0 0 8px 8px;
       }
     }
+    .filter-line-item {
+      display: flex;
+      align-items: center;
+    }
 
     .tab_box {
-      padding: 0 0 4px 40px;
+      padding-bottom: 4px;
     }
 
     ::v-deep {
@@ -1246,7 +1273,7 @@ export default {
         background-color: #e0e0e0;
       }
       .selector-card.s-line {
-        padding: 16px 40px;
+        padding: 16px 0;
       }
     }
   }

+ 286 - 0
apps/bigmember_pc/src/components/selector-cascader/MoneySelection.vue

@@ -0,0 +1,286 @@
+<template>
+  <div class="selector-money select-input-container select-common-inputs">
+    <el-select
+      ref="selectprice"
+      :placeholder="placeholder"
+      :value="inputValue"
+      :popper-append-to-body="false"
+      class="select_common"
+      popper-class="select_common_data"
+    >
+      <div slot="empty" class="select_box_container">
+        <div class="price-content">
+          <el-checkbox :value="checkedAll" @click.native="clickAll">全部</el-checkbox>
+          <div class="price-exact-container">
+            <div class="price-input-group" :class="{ active: !checkedAll }">
+              <div class="price-input">
+                <div class="price-input-group-item">
+                  <input
+                    type="text"
+                    class="min-input"
+                    name="minprice"
+                    maxlength="9"
+                    oninput="value=value.replace(/^\D*(\d*(?:\.\d{0,2})?).*$/g, '$1')"
+                    v-model.number="inputGroupCache.min"
+                  />
+                  <span class="unit">万元</span>
+                </div>
+                <span class="splitter"></span>
+                <div class="price-input-group-item">
+                  <input
+                    type="text"
+                    class="max-input"
+                    name="maxprice"
+                    maxlength="9"
+                    oninput="value=value.replace(/^\D*(\d*(?:\.\d{0,2})?).*$/g, '$1')"
+                    v-model.number="inputGroupCache.max"
+                  />
+                  <span class="unit">万元</span>
+                </div>
+              </div>
+            </div>
+            <button class="select-btn" @click="onConfirm">确定</button>
+          </div>
+        </div>
+      </div>
+    </el-select>
+  </div>
+</template>
+
+<script>
+import { Select, Checkbox, CheckboxGroup } from 'element-ui'
+import { selectorVModelMixin } from '@/utils/mixins/selector-v-model'
+
+export default {
+  name: 'MoneySelection',
+  mixins: [selectorVModelMixin],
+  components: {
+    [CheckboxGroup.name]: CheckboxGroup,
+    [Checkbox.name]: Checkbox,
+    [Select.name]: Select
+  },
+  props: {
+    placeholder: {
+      type: String,
+      default: '价格区间'
+    },
+    allRequired: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {
+      checkedAll: true,
+      inputGroupCache: {
+        min: '',
+        max: ''
+      },
+      price: {
+        min: '',
+        max: ''
+      }
+    }
+  },
+  computed: {
+    inputValue() {
+      const { min, max } = this.price
+      if (min !== '' && max !== '') {
+        return `${min || 0}万~${max || ''}万`
+      } else {
+        return `${this.placeholder}`
+      }
+    }
+  },
+  methods: {
+    resetStatePrice() {
+      this.checkedAll = true
+      this.inputGroupCache.min = ''
+      this.inputGroupCache.max = ''
+      this.price.min = ''
+      this.price.max = ''
+    },
+    clickAll() {
+      this.resetStatePrice()
+      // this.onChange()
+    },
+    compareMinMax() {
+      const { min, max } = this.inputGroupCache
+      const hasMinAndMax = String(min).length && String(max).length
+      if (hasMinAndMax && min > max) {
+        this.inputGroupCache.max = min
+        this.inputGroupCache.min = max
+      }
+    },
+    setState({ min = '', max = '' }) {
+      this.price.min = min
+      this.price.max = max
+      if (min !== '') {
+        this.checkedAll = false
+        this.inputGroupCache.min = min
+      }
+      if (max !== '') {
+        this.checkedAll = false
+        this.inputGroupCache.max = max
+      }
+      if (min !== '' && max !== '') {
+        this.compareMinMax()
+        this.syncToPrice()
+      }
+
+      if (min === '' && max === '') {
+        this.resetStatePrice()
+      }
+    },
+    getState() {
+      if (this.checkedAll) {
+        return { min: '', max: '' }
+      } else {
+        return this.inputGroupCache
+      }
+    },
+    syncToPrice() {
+      Object.assign(this.price, this.inputGroupCache)
+    },
+    onConfirm() {
+      this.compareMinMax()
+      const { min, max } = this.inputGroupCache
+      if (min || max) {
+        this.checkedAll = false
+      }
+      this.$nextTick(() => {
+        this.$refs.selectprice.blur()
+        this.syncToPrice()
+        this.onChange()
+      })
+    },
+    onChange() {
+      const state = this.getState()
+      const { min, max } = state
+      if (this.allRequired) {
+        if (min === '' || max === '') {
+          return this.$toast('最小值和最大值均为必填')
+        }
+      }
+      this.$emit('change', state)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+$hover-border-color: #e0e0e0;
+
+.select-common-inputs {
+  position: relative;
+  ::v-deep {
+    .el-input__inner {
+      height: 30px;
+      width: 200px;
+      background-color: transparent;
+
+      // ellipsis
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+      text-align: justify;
+    }
+    .el-select__caret {
+      color: #aaa;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+    }
+    .el-input__icon {
+      line-height: normal;
+    }
+  }
+}
+
+::v-deep {
+  .el-select-dropdown.el-popper {
+    &.select_common_data {
+      left: 0 !important;
+      margin: 0;
+      margin-top: 5px;
+      border: 1px solid #2cb7ca;
+      min-width: 425px;
+      // height: 104px;
+      // box-sizing: border-box;
+      z-index: 99 !important;
+    }
+
+    .popper__arrow {
+      display: none;
+    }
+  }
+}
+
+@media only screen and (max-width: 1230px),
+  only screen and (max-device-width: 1230px) {
+  ::v-deep {
+    .el-select-dropdown.el-popper.select_common_data {
+      left: -225px !important;
+    }
+  }
+}
+
+.select_box_container {
+  min-width: 425px;
+  padding: 18px 12px;
+  box-sizing: border-box;
+}
+
+.price-exact-container {
+  margin-top: 4px;
+  display: flex;
+  align-items: center;
+  justify-content: space-between
+}
+.price-input-group {
+  border: 1px solid #f5f6f7;
+  background-color: #f5f6f7;
+  padding: 5px 5px;
+  border-radius: 2px;
+  &.active {
+    border-color: #2cb7ca;
+    background-color: #2cb7ca;
+  }
+}
+.price-input-group-item {
+  display: flex;
+  align-items: center;
+  padding: 0 10px;
+  background-color: #fff;
+  border: 1px solid #2cb7ca;
+  border-radius: 4px;
+  input {
+    width: 106px;
+    height: 28px;
+    padding-left: 0;
+    border: none;
+  }
+}
+
+.splitter {
+  margin: 0 6px;
+  width: 14px;
+  height: 1px;
+  line-height: 15px;
+  background-color: $hover-border-color;
+}
+.unit {
+  color: #1d1d1d;
+  font-size: 14px;
+}
+
+.price-input {
+  display: flex;
+  align-items: center;
+}
+
+.select-btn {
+  color: #2cb7ca;
+  background-color: transparent;
+}
+</style>

+ 20 - 3
apps/bigmember_pc/src/views/potential-for/PotenTial.vue

@@ -16,6 +16,7 @@
         @tabChange="tabChange"
         @export="onExport"
         @changeArea="onChangeArea"
+        @changePrice="changePrice"
         @changeId="onChangeSelectedId"
         ref="myList"
         :showFiltrate="true"
@@ -59,6 +60,7 @@ import { dateFormatter } from '@/utils/'
 import { tryCallHooks } from '@jianyu/easy-inject-qiankun'
 import ExportTip from '@/views/portrayal/components/DataExportTip.vue'
 // import { Loading } from 'element-ui'
+import { debounce } from 'lodash'
 export default {
   name: 'potential',
   components: {
@@ -88,6 +90,7 @@ export default {
       ],
       Loading: false,
       area: {},
+      price: {},
       selectIds: [],
       showDataExportDialog: false,
       checkNum: 0,
@@ -121,6 +124,8 @@ export default {
         startTime: this.startTime,
         endTime: this.endTime,
         area: areaParams,
+        minprice: this.price.min || undefined,
+        maxprice: this.price.max || undefined,
         total: this.filterCount
       }
       getPotenList(params).then((res) => {
@@ -175,7 +180,7 @@ export default {
         this.startTime = parseInt(item.start / 1000)
         this.endTime = parseInt(item.end / 1000)
       }
-      this.getnewList()
+      this.doSearch()
     },
     tabChange(item) {
       this.pageNum = 1
@@ -186,11 +191,21 @@ export default {
       this.potenObj.list = []
       this.getnewList()
     },
-    onChangeArea(regionMap) {
+    doSearch() {
       this.pageNum = 1
       this.$refs.myList.listState.pageNum = 1
-      this.area = regionMap || {}
+      this.getnewListDebounce()
+    },
+    getnewListDebounce: debounce(function() {
       this.getnewList()
+    }, 1000),
+    changePrice(v) {
+      this.price = v
+      this.doSearch()
+    },
+    onChangeArea(regionMap) {
+      this.area = regionMap || {}
+      this.doSearch()
     },
     async checkBoxChange(data) {
       const params = {
@@ -225,6 +240,8 @@ export default {
         dataType: this.dataType,
         startTime: this.startTime,
         endTime: this.endTime,
+        minprice: this.price.min || undefined,
+        maxprice: this.price.max || undefined,
         area:
           Object.keys(this.area).length === 0 ? '' : JSON.stringify(this.area),
         count:

+ 7 - 1
apps/bigmember_pc/src/views/push-setting/index.vue

@@ -668,7 +668,13 @@ export default {
           if (this.free) {
             return '每日上午、下午各推送一次'
           } else {
-            return `每日推送:${times.toString().replace(/,/g, '、')}`
+            const suffix = times?.toString().replace(/,/g, '、')
+            return `每日推送:${suffix}`
+            if (suffix) {
+              return `每日推送:${suffix}`
+            } else {
+              return '每日推送'
+            }
           }
         case 3:
           return `每周推送(周五 ${str.toString().replace(/,/g, '、')})`

+ 15 - 9
data/data-models/modules/article/model/content.js

@@ -2,7 +2,7 @@ import BaseModel from '../../../core/base'
 import useSummaryModel from '../transform/summary2'
 import useCommonTitleModel from '../transform/content'
 import useThirdPartyVerifyModel from '../transform/third-party-verify'
-import { replaceKeyword } from '@jy/util'
+import { replaceKeywordWithRichText } from '@jy/util'
 const thirdPartyVerify = useThirdPartyVerifyModel()
 
 class ContentModel extends BaseModel {
@@ -42,7 +42,7 @@ class ContentModel extends BaseModel {
       try {
         result.content.contentHighlighted = this.highlightContentHTML(result.content.content, data, result)
       } catch (error) {
-        console.error(error)        
+        console.error(error)
       }
     }
 
@@ -61,10 +61,10 @@ class ContentModel extends BaseModel {
     const projectCode = baseInfo?.projectCode
     // 下划线高亮项目名称编号
     if (projectName && title.toLowerCase().indexOf(projectName.toLowerCase()) > -1) {
-      title = replaceKeyword(title, projectName, '<span class="keyword keyword-underline project project-name hide-underline">$1</span>')
+      title = replaceKeywordWithRichText(title, projectName, '<span class="keyword keyword-underline project project-name hide-underline">$1</span>')
     }
     if (projectCode && title.toLowerCase().indexOf(projectCode.toLowerCase()) > -1) {
-      title = replaceKeyword(title, projectCode, '<span class="keyword keyword-underline project project-code">$1</span>')
+      title = replaceKeywordWithRichText(title, projectCode, '<span class="keyword keyword-underline project project-code">$1</span>')
     }
 
     // ------------------
@@ -74,7 +74,7 @@ class ContentModel extends BaseModel {
       highlightKeys = formatted.content.highlightKeys
     }
     highlightKeys.forEach((key) => {
-      title = replaceKeyword(title, key, '<span class="keyword highlight-text">$1</span>')
+      title = replaceKeywordWithRichText(title, key, '<span class="keyword highlight-text">$1</span>')
     })
     return title
   }
@@ -89,12 +89,18 @@ class ContentModel extends BaseModel {
 
     content = content.replace(/[^\{\u4e00-\u9fa5]{1,90}{[^\}\u4e00-\u9fa5]+?}/g, '').trim()
 
+    let tempNode = document.createElement('div')
+    tempNode.innerHTML = content
+
+
+
+
     // 下划线高亮项目名称编号
     if(projectName && content.toLowerCase().indexOf(projectName.toLowerCase()) > -1){
-      content = replaceKeyword(content, projectName, '<span class="keyword keyword-underline my-follow project project-name hide-underline">$1</span>')
+      content = replaceKeywordWithRichText(content, projectName, '<span class="keyword keyword-underline my-follow project project-name hide-underline">$1</span>')
     }
     if(projectCode && content.toLowerCase().indexOf(projectCode.toLowerCase()) > -1){
-      content = replaceKeyword(content, projectCode, '<span class="keyword keyword-underline my-follow project project-code">$1</span>')
+      content = replaceKeywordWithRichText(content, projectCode, '<span class="keyword keyword-underline my-follow project project-code">$1</span>')
     }
     // 下划线高亮中标企业
     const winners = summary.winners
@@ -103,7 +109,7 @@ class ContentModel extends BaseModel {
         const winnerName = winners[i].name
         const winnerId = winners[i].id
         if (winnerName && content.toLowerCase().indexOf(winnerName.toLowerCase()) > -1) {
-          content = replaceKeyword(content, winnerName, `<span data-eid='${winnerId}' class='keyword keyword-underline winner-name my-follow-ent'>$1</span>`)
+          content = replaceKeywordWithRichText(content, winnerName, `<span data-eid='${winnerId}' class='keyword keyword-underline winner-name my-follow-ent'>$1</span>`)
         }
       }
     }
@@ -125,7 +131,7 @@ class ContentModel extends BaseModel {
       highlightKeys = formatted.content.highlightKeys
     }
     highlightKeys.forEach((key) => {
-      content = replaceKeyword(content, key, '<span class="keyword highlight-text">$1</span>')
+      content = replaceKeywordWithRichText(content, key, '<span class="keyword highlight-text">$1</span>')
     })
 
     // 将多个连续的br替换成一个

+ 1 - 1
data/data-models/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@jy/data-models",
-  "version": "0.0.1",
+  "version": "0.0.2",
   "description": "聚合API接口,对外提供业务数据模型",
   "main": "index.js",
   "files": [

+ 57 - 3
packages/util/modules/format/str.js

@@ -77,14 +77,14 @@ export function replaceKeyword(
   }
 
 
-  
+
   // 数组去空
   let lastArr = oldCharArr
     .filter((item) => !!item)
     .sort((a, b) => b.length - a.length)
   // 数组去重
   lastArr = Array.from(new Set(lastArr))
-  
+
   if (lastArr.length === 0 && notStrReplacer.length === 0) {
     return value
   }
@@ -105,6 +105,60 @@ export function replaceKeyword(
   }
 }
 
+/**
+ * 富文本高亮专用替换,用于指定关键词包裹高亮,使用dom解析避免HTML标签被影响
+ * @param htmlString - String
+ * @param keyword - String
+ * @param richChar - String $1
+ */
+export function replaceKeywordWithRichText (
+  htmlString,
+  keyword,
+  richChar = '<span class="highlight-text">$1</span>'
+) {
+  if (!keyword || !richChar || keyword === '略') return htmlString
+
+  // 创建一个临时的DOM元素来解析HTML字符串
+  const tempDiv = document.createElement('div')
+  tempDiv.innerHTML = htmlString
+
+  // 格式化需要替换的富文本
+  const richString = richChar.replace(/\$1/g, keyword)
+
+  // 递归函数来遍历DOM节点并替换文本
+  function replaceText(node) {
+    if (node.nodeType === Node.TEXT_NODE && node.textContent.includes(keyword)) {
+      // 创建一个文档片段来存放替换后的富文本和剩余文本
+      const frag = document.createDocumentFragment()
+      // 将文本分割为关键词前后的文本,以及替换的富文本
+      const parts = node.textContent.split(keyword)
+      for (let i = 0; i < parts.length; i++) {
+        if (i === 1) {
+          // 关键词替换为富文本
+          const richTextNode = document.createElement('div')
+          richTextNode.innerHTML = richString;
+          frag.appendChild(richTextNode.firstChild);
+        } else {
+          // 其他文本正常添加
+          frag.appendChild(document.createTextNode(parts[i]))
+        }
+      }
+      // 替换原始文本节点
+      node.parentNode.replaceChild(frag, node)
+    } else if (node.nodeType === Node.ELEMENT_NODE) {
+      // 如果是元素节点,递归其子节点
+      Array.from(node.childNodes).forEach(replaceText)
+    }
+  }
+
+  // 从临时div开始递归替换文本
+  replaceText(tempDiv)
+
+  // 返回修改后的HTML字符串
+  return tempDiv.innerHTML
+
+}
+
 /**
  * 从key=value&key1=value1&key2=value2...中获取key值
  * @param {String} formString 目标字符串,必须为key=value&key1=value1的格式
@@ -125,7 +179,7 @@ export function getFormValue(formString, targetKey) {
 
 /**
  * 从url中获取对应name的参数
- * @param {String} name 
+ * @param {String} name
  * @returns {String}
  */
 export function getQueryParam(name) {

+ 1 - 1
packages/util/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@jy/util",
-  "version": "0.0.1",
+  "version": "0.0.2",
   "description": "剑鱼项目通用工具集",
   "main": "index.js",
   "scripts": {