浏览代码

用户行为记录
供应商标签

zhengjinyi 2 年之前
父节点
当前提交
3e9ee9a1f5

+ 1 - 0
.env.development

@@ -4,6 +4,7 @@ ENV = 'development'
 # base api
 # base api
 # VUE_APP_BASE_API = 'http://192.168.2.67:18015'
 # VUE_APP_BASE_API = 'http://192.168.2.67:18015'
 # VUE_APP_BASE_API = 'http://localhost:18015'
 # VUE_APP_BASE_API = 'http://localhost:18015'
+# VUE_APP_BASE_API = 'http://192.168.2.102:18015'
 VUE_APP_BASE_API = 'https://mapi-b.caimei365.com'
 VUE_APP_BASE_API = 'https://mapi-b.caimei365.com'
 # 图片上传,索引更新等
 # 图片上传,索引更新等
 VUE_APP_CORE_API = 'https://core-b.caimei365.com'
 VUE_APP_CORE_API = 'https://core-b.caimei365.com'

+ 16 - 0
src/api/user/record/record.js

@@ -0,0 +1,16 @@
+import request from '@/utils/request'
+
+export function fetchRecordList(params) {
+  return request({
+    url: '/user/behavior/record/list',
+    method: 'get',
+    params
+  })
+}
+export function fetchBehaviorList(params) {
+  return request({
+    url: '/user/behavior/record/recordList',
+    method: 'get',
+    params
+  })
+}

+ 2 - 0
src/router/index.js

@@ -8,6 +8,7 @@ import Layout from '@/layout'
 
 
 /* Router Modules */
 /* Router Modules */
 import sysRouter from './modules/sys'
 import sysRouter from './modules/sys'
+import userRouter from './modules/user'
 import wechatRouter from './modules/wechat'
 import wechatRouter from './modules/wechat'
 import webPageRouter from './modules/webPage'
 import webPageRouter from './modules/webPage'
 import productRouter from './modules/product'
 import productRouter from './modules/product'
@@ -94,6 +95,7 @@ export const constantRoutes = [
  */
  */
 export const asyncRoutes = [
 export const asyncRoutes = [
   sysRouter,
   sysRouter,
+  userRouter,
   webPageRouter,
   webPageRouter,
   productRouter,
   productRouter,
   financeRouter,
   financeRouter,

+ 22 - 0
src/router/modules/keywordLibrary.js

@@ -46,6 +46,28 @@ const keywordLibraryRouter = {
           meta: { title: '标签列表' }
           meta: { title: '标签列表' }
         }
         }
       ]
       ]
+    },
+    {
+      path: 'supplierTag',
+      name: 'SupplierTagMenu',
+      redirect: '/library/supplierTag/list',
+      alwaysShow: true,
+      component: () => import('@/views/index'),
+      meta: { title: '供应商关联标签', useDefault: true },
+      children: [
+        {
+          path: 'supplierList',
+          name: 'SupplierList',
+          component: () => import('@/views/library/supplierTag/supplier-list'),
+          meta: { title: '供应商列表' }
+        },
+        {
+          path: 'list',
+          name: 'SupplierTagList',
+          component: () => import('@/views/library/supplierTag/list'),
+          meta: { title: '标签列表' }
+        }
+      ]
     }
     }
   ]
   ]
 }
 }

+ 70 - 0
src/router/modules/user.js

@@ -0,0 +1,70 @@
+/** When your routing table is too long, you can split it into small modules **/
+
+import Layout from '@/layout'
+
+const userRouter = {
+  path: '/user',
+  component: Layout,
+  redirect: '/user/club',
+  alwaysShow: true, // will always show the root menu
+  name: 'userSetting', // name必须和后台配置一致,不然匹配不到
+  meta: { title: '用户管理' },
+  children: [
+    {
+      path: 'club',
+      name: 'UserClubMenu',
+      redirect: '/user/club/list',
+      alwaysShow: true,
+      component: () => import('@/views/index'),
+      meta: { title: '机构管理' },
+      children: [
+        {
+          path: 'list',
+          name: 'ClubList',
+          component: () => import('@/views/user/club/list'),
+          meta: { title: '机构列表' }
+        }
+      ]
+    },
+    {
+      path: 'record',
+      name: 'UserRecordMenu',
+      redirect: '/user/record/list',
+      alwaysShow: true,
+      component: () => import('@/views/index'),
+      meta: { title: '用户行为记录' },
+      children: [
+        {
+          path: 'list',
+          name: 'RecordList',
+          component: () => import('@/views/user/record/list'),
+          meta: { title: '用户行为记录' }
+        },
+        {
+          path: 'detail',
+          hidden: true,
+          component: () => import('@/views/user/record/detail-list.vue'),
+          name: 'SysUserEdit',
+          meta: { title: '查看详情', noCache: true, activeMenu: '/user/record/' }
+        }
+      ]
+    },
+    {
+      path: 'supplier',
+      name: 'UserSupplierMenu',
+      redirect: '/user/supplier/list',
+      alwaysShow: true,
+      component: () => import('@/views/index'),
+      meta: { title: '供应商管理', useDefault: true },
+      children: [
+        {
+          path: 'list',
+          name: 'SupplierAllList',
+          component: () => import('@/views/user/supplier/list'),
+          meta: { title: '供应商列表' }
+        }
+      ]
+    }
+  ]
+}
+export default userRouter

+ 457 - 0
src/views/library/supplierTag/list.vue

@@ -0,0 +1,457 @@
+<template>
+  <div class="app-container">
+    <!-- 顶部操作区域 -->
+    <div class="filter-container">
+      <div class="filter-control">
+        <span>标签:</span>
+        <el-input
+          v-model="listQuery.keyword"
+          placeholder="关键词"
+          clearable
+          @keyup.enter.native="getList"
+          @clear="getList"
+        />
+      </div>
+      <div class="filter-control">
+        <el-button type="primary" @click="getList">查询</el-button>
+        <el-button v-permission="'tag:list:add'" type="primary" @click="selectionTagDialog = true">选择标签</el-button>
+        <el-button v-permission="'tag:list:del'" type="danger" :disabled="disabled" @click="handleDeleteTag">
+          删除
+        </el-button>
+      </div>
+    </div>
+    <!-- 关键词列表 -->
+    <el-table v-loading="isLoading" :data="list" border @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="序号" :index="indexMethod" type="index" sortable="custom" align="center" width="80" />
+      <el-table-column prop="keyword" label="关键词" align="center" />
+      <el-table-column prop="frequency" label="搜索次数" width="120" align="center" sortable />
+      <el-table-column width="180" label="来源" align="center">
+        <template slot-scope="{ row }">
+          <span v-if="row.fromSearch === 1">单个添加</span>
+          <span v-else-if="row.fromSearch === 2">系统推荐</span>
+          <span v-else>导入</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="最近搜索时间" align="center" width="200">
+        <template slot-scope="{ row }">
+          <span v-if="row.searchTime">{{ row.searchTime | parseTime }}</span>
+          <span v-else>-</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="添加时间" align="center" width="200">
+        <template slot-scope="{ row }">
+          <span v-if="row.searchTime">{{ row.searchTime | parseTime }}</span>
+          <span v-else>-</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" width="240">
+        <template slot-scope="{ row }">
+          <el-button v-permission="'tag:list:del'" type="danger" size="mini" @click="handleDeleteTag(row)">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 页码 -->
+    <pagination
+      :total="total"
+      :page.sync="listQuery.pageNum"
+      :limit.sync="listQuery.pageSize"
+      @pagination="fetchKeywordList"
+    />
+
+    <!-- 选择标签 -->
+    <el-dialog
+      title="选择标签"
+      :visible.sync="selectionTagDialog"
+      width="1000px"
+      :close-on-click-modal="false"
+      :show-close="false"
+    >
+      <div class="filter-container">
+        <div class="filter-control">
+          <span>标签:</span>
+          <el-input
+            v-model="listQuery.keyword"
+            placeholder="标签"
+            clearable
+            @keyup.enter.native="getList"
+            @clear="getList"
+          />
+        </div>
+        <div class="filter-control">
+          <el-button type="primary" @click="getList">查询</el-button>
+          <el-button type="primary" @click="handleAddTag">新增标签</el-button>
+        </div>
+      </div>
+      <el-table ref="table" v-loading="isLoading" :data="supplierList" height="400px" border @selection-change="handleSelectionTagChange">
+        <el-table-column type="selection" width="55" />
+        <el-table-column prop="keyword" label="标签" align="center" />
+        <el-table-column prop="frequency" label="搜索次数" width="120" align="center" sortable />
+        <el-table-column label="最近搜索时间" align="center" width="200">
+          <template slot-scope="{ row }">
+            <span v-if="row.searchTime">{{ row.searchTime | parseTime }}</span>
+            <span v-else>-</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="添加时间" align="center" width="200">
+          <template slot-scope="{ row }">
+            <span v-if="row.searchTime">{{ row.searchTime | parseTime }}</span>
+            <span v-else>-</span>
+          </template>
+        </el-table-column>
+      </el-table>
+      <div slot="footer">
+        <el-button @click="selectionTagDialog = false">取 消</el-button>
+        <el-button type="primary" @click="handleSelectionTagConfirm">确 定</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- 添加标签 -->
+    <el-dialog
+      title="添加标签"
+      :visible.sync="addTagDialog"
+      width="460px"
+      :close-on-click-modal="false"
+      :show-close="false"
+    >
+      <el-form ref="tagForm" :model="tagForm" :rules="tagFormRules">
+        <el-form-item label="标签名:" prop="keyword">
+          <el-input v-model="tagForm.keyword" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer">
+        <el-button @click="closeAddTagDialog">取 消</el-button>
+        <el-button type="primary" @click="handleAddTagConfirm">确 定</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { parseTime } from '@/utils'
+import { addKeyword, deleteKeyword, fetchKeywordList, importKeywordXlsx } from '@/api/library/keyword'
+import { export_json_to_excel } from '@/vendor/Export2Excel'
+export default {
+  data() {
+    const pickerOptions = {
+      shortcuts: [
+        {
+          text: '近1年',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 365)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '近半年',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 183)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '近1月',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '近1周',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '昨天',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24)
+            picker.$emit('pick', [start, end])
+          }
+        }
+      ]
+    }
+    return {
+      isLoading: true,
+      pickerOptions,
+      time: '',
+      listQuery: {
+        keyword: '',
+        beginTime: '',
+        endTime: '',
+        labelStatus: 1,
+        searchTimeCode: '',
+        fromSearch: '',
+        pageNum: 1,
+        pageSize: 10
+      },
+      list: [],
+      total: 0,
+      currentList: [],
+      currentTabsList: [],
+      searchDialog: false,
+      visibleKeyword: '',
+      addTagDialog: false,
+      selectionTagDialog: false,
+      tagForm: {
+        keyword: ''
+      },
+      tagFormRules: {
+        keyword: [{ required: true, message: '请输入标签名称', trigger: ['blur'] }]
+      },
+      exportKeywordList: [],
+      importDialog: false,
+      fileList: [],
+      importForm: {
+        fileUrl: ''
+      },
+      importFormRules: {
+        fileUrl: [{ required: true, message: '文件不能为空', trigger: ['change'] }]
+      },
+      supplierList: [
+        { frequency: '45454', keyword: '综合供应商公司似手打发送到', searchTime: '2022-09-16 10:37:12', parseTime: '2022-09-16 10:37:12' },
+        { frequency: '45454', keyword: '综合供应商公司似手打发送到', searchTime: '2022-09-16 10:37:12', parseTime: '2022-09-16 10:37:12' },
+        { frequency: '45454', keyword: '综合供应商公司似手打发送到', searchTime: '2022-09-16 10:37:12', parseTime: '2022-09-16 10:37:12' },
+        { frequency: '45454', keyword: '综合供应商公司似手打发送到', searchTime: '2022-09-16 10:37:12', parseTime: '2022-09-16 10:37:12' },
+        { frequency: '45454', keyword: '综合供应商公司似手打发送到', searchTime: '2022-09-16 10:37:12', parseTime: '2022-09-16 10:37:12' },
+        { frequency: '45454', keyword: '综合供应商公司似手打发送到', searchTime: '2022-09-16 10:37:12', parseTime: '2022-09-16 10:37:12' },
+        { frequency: '45454', keyword: '综合供应商公司似手打发送到', searchTime: '2022-09-16 10:37:12', parseTime: '2022-09-16 10:37:12' },
+        { frequency: '45454', keyword: '综合供应商公司似手打发送到', searchTime: '2022-09-16 10:37:12', parseTime: '2022-09-16 10:37:12' },
+        { frequency: '45454', keyword: '综合供应商公司似手打发送到', searchTime: '2022-09-16 10:37:12', parseTime: '2022-09-16 10:37:12' },
+        { frequency: '45454', keyword: '综合供应商公司似手打发送到', searchTime: '2022-09-16 10:37:12', parseTime: '2022-09-16 10:37:12' },
+        { frequency: '45454', keyword: '综合供应商公司似手打发送到', searchTime: '2022-09-16 10:37:12', parseTime: '2022-09-16 10:37:12' },
+        { frequency: '45454', keyword: '综合供应商公司似手打发送到', searchTime: '2022-09-16 10:37:12', parseTime: '2022-09-16 10:37:12' },
+        { frequency: '45454', keyword: '综合供应商公司似手打发送到', searchTime: '2022-09-16 10:37:12', parseTime: '2022-09-16 10:37:12' },
+        { frequency: '45454', keyword: '综合供应商公司似手打发送到', searchTime: '2022-09-16 10:37:12', parseTime: '2022-09-16 10:37:12' },
+        { frequency: '45454', keyword: '综合供应商公司似手打发送到', searchTime: '2022-09-16 10:37:12', parseTime: '2022-09-16 10:37:12' },
+        { frequency: '45454', keyword: '综合供应商公司似手打发送到', searchTime: '2022-09-16 10:37:12', parseTime: '2022-09-16 10:37:12' }
+      ]
+    }
+  },
+  computed: {
+    disabled() {
+      return this.currentList.length === 0
+    }
+  },
+  created() {
+    this.getList()
+  },
+  methods: {
+    // 获取关键词列表
+    getList() {
+      this.listQuery.pageNum = 1
+      if (this.time && this.time.length > 0) {
+        this.listQuery.beginTime = this.time[0]
+        this.listQuery.endTime = this.time[1]
+      } else {
+        this.listQuery.beginTime = ''
+        this.listQuery.endTime = ''
+      }
+      this.fetchKeywordList()
+    },
+
+    // 获取关键词列表
+    async fetchKeywordList() {
+      try {
+        this.isLoading = true
+        const res = await fetchKeywordList(this.listQuery)
+        this.list = res.data.results
+        this.total = res.data.totalRecord
+        this.isLoading = false
+      } catch (error) {
+        console.log(error)
+      }
+    },
+
+    // 选中标签项
+    handleSelectionTagChange(current) {
+      this.currentTabsList = current
+    },
+
+    // 选中列表项
+    handleSelectionChange(current) {
+      this.currentList = current
+    },
+
+    // 去搜索
+    handleSearch(keyword) {
+      this.visibleKeyword = keyword
+      this.searchDialog = true
+    },
+    // 添加标签
+    handleAddTag() {
+      this.selectionTagDialog = false
+      this.addTagDialog = true
+    },
+    // 添加标签取消
+    closeAddTagDialog() {
+      this.$refs.tagForm.resetFields()
+      this.addTagDialog = false
+    },
+
+    // 添加标签确定
+    async handleAddTagConfirm() {
+      try {
+        await this.$refs.tagForm.validate()
+        this.addTagSubmit()
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    // 选择标签确定
+    async handleSelectionTagConfirm() {
+      try {
+        await this.$refs.tagForm.validate()
+        this.addTagSubmit()
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    // 提交添加标签信息
+    async addTagSubmit() {
+      try {
+        await addKeyword(this.tagForm)
+        this.$message.success('添加标签成功')
+        this.getList()
+      } catch (error) {
+        console.log(error)
+      }
+      this.closeAddTagDialog()
+    },
+
+    // 删除标签
+    async handleDeleteTag(row) {
+      try {
+        await this.$confirm('确定删除已选标签?', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        })
+        this.deleteTagSubmit(row)
+      } catch (error) {
+        this.$message.info('已取消删除操作')
+      }
+    },
+
+    // 提交删除标签操作
+    async deleteTagSubmit(row) {
+      const keywords = row instanceof Event ? this.currentList : [].concat(row)
+      const ids = keywords.map((item) => item.id).join(',')
+      try {
+        await deleteKeyword({ id: ids })
+        this.$message.success('已忽略所选关键词')
+        this.getList()
+      } catch (error) {
+        console.log(error)
+      }
+    },
+
+    // 导入关键词
+    async handleImport() {},
+
+    // 导入关键词取消
+    closeImportDialog() {
+      this.$refs.importForm.resetFields()
+      this.importDialog = false
+    },
+
+    // 导入关键词确定
+    async handleImportConfirm() {
+      console.log(1)
+      try {
+        await this.$refs.importForm.validate()
+        this.importSumbit()
+      } catch (error) {
+        console.log(error)
+      }
+    },
+
+    async importSumbit() {
+      const file = this.fileList.length > 0 && this.fileList[0]
+      if (!file) return
+      try {
+        const formData = new FormData()
+        this.fileList = []
+        this.importDialog = false
+        formData.append('keywordFile', file.raw)
+        await importKeywordXlsx(formData)
+        this.$message.success('导入标签成功')
+        this.getList()
+      } catch (error) {
+        console.log(error)
+      }
+    },
+
+    // 导出
+    async handleExport() {
+      try {
+        await this.$confirm('确定将所选标签导出为xlsx?', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        })
+        // 导出数据格式化
+        const filterVal = ['index', 'keyword', 'frequency', 'fromSearch', 'searchTime', 'addTime']
+        const data = this.formatJson(filterVal, this.currentList.slice(0))
+        export_json_to_excel({
+          header: ['序号', '标签', '搜索次数', '来源', '最近搜索时间', '添加时间'],
+          data,
+          filename: '标签列表'
+        })
+      } catch (error) {
+        this.$message.info('已取消导出操作')
+      }
+    },
+
+    formatJson(filterVal, jsonData) {
+      return jsonData.map((v, index) =>
+        filterVal.map((key) => {
+          if (['searchTime', 'addTime'].includes(key)) {
+            return parseTime(v[key])
+          }
+          if (key === 'index') return index + 1
+          if (key === 'fromSearch') {
+            const t = ['单个添加', '系统推荐', '导入']
+            return t[v[key] - 1]
+          }
+          return v[key]
+        })
+      )
+    },
+
+    // 文件上传成功
+    handleUploadSuccess(response, file, fileList) {
+      this.importForm.fileUrl = response.url
+      this.fileList = fileList
+    },
+
+    // 上传文件删除
+    handleUploadRemove(file, fileList) {
+      this.fileList = fileList
+    },
+
+    handleUploadChange(file, fileList) {
+      this.fileList = fileList
+      this.importForm.fileUrl = file.name
+    },
+
+    indexMethod(index) {
+      return index + this.listQuery.pageSize * (this.listQuery.pageNum - 1) + 1
+    }
+  }
+}
+</script>
+
+<style></style>

+ 356 - 0
src/views/library/supplierTag/supplier-list.vue

@@ -0,0 +1,356 @@
+<template>
+  <div class="app-container">
+    <!-- 顶部操作区域 -->
+    <div class="filter-container">
+      <div class="filter-control">
+        <span>供应商名称:</span>
+        <el-input
+          v-model="listQuery.keyword"
+          placeholder="供应商名称"
+          clearable
+          @keyup.enter.native="getList"
+          @clear="getList"
+        />
+      </div>
+      <div class="filter-control">
+        <span>手机号:</span>
+        <el-input
+          v-model="listQuery.keyword"
+          placeholder="手机号"
+          clearable
+          style="width:160px;"
+          @keyup.enter.native="getList"
+          @clear="getList"
+        />
+      </div>
+      <div class="filter-control">
+        <span>联系人:</span>
+        <el-input
+          v-model="listQuery.keyword"
+          placeholder="联系人"
+          clearable
+          style="width:160px;"
+          @keyup.enter.native="getList"
+          @clear="getList"
+        />
+      </div>
+      <div class="filter-control">
+        <el-button type="primary" @click="getList">查询</el-button>
+        <el-button v-permission="'tag:list:add'" type="primary" @click="addSupplierDialog = true">添加供应商</el-button>
+      </div>
+    </div>
+    <!-- 关键词列表 -->
+    <el-table v-loading="isLoading" :data="list" border>
+      <el-table-column label="序号" :index="indexMethod" type="index" sortable="custom" align="center" width="100" />
+      <el-table-column prop="供应商名称" label="供应商名称" align="center" width="300" />
+      <el-table-column label="联系人" :index="indexMethod" type="index" sortable="custom" align="center" width="300" />
+      <el-table-column label="手机号" :index="indexMethod" type="index" sortable="custom" align="center" width="300" />
+      <el-table-column label="添加时间" :index="indexMethod" type="index" sortable="custom" align="center" width="300" />
+      <el-table-column label="操作" align="center">
+        <template slot-scope="{ row }">
+          <el-button type="primary" size="mini" @click="handleTagsList(row.id)">标签列表</el-button>
+          <el-button v-permission="'tag:list:del'" type="danger" size="mini" @click="handleDeleteSupper(row)">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 页码 -->
+    <pagination
+      :total="total"
+      :page.sync="listQuery.pageNum"
+      :limit.sync="listQuery.pageSize"
+      @pagination="fetchKeywordList"
+    />
+    <!-- 添加供应商 -->
+    <el-dialog
+      title="添加供应商"
+      :visible.sync="addSupplierDialog"
+      width="1000px"
+      :close-on-click-modal="false"
+      :show-close="false"
+    >
+      <div class="filter-container">
+        <div class="filter-control">
+          <span>供应商ID:</span>
+          <el-input
+            v-model="listQuery.keyword"
+            placeholder="供应商ID"
+            clearable
+            @keyup.enter.native="getList"
+            @clear="getList"
+          />
+        </div>
+        <div class="filter-control">
+          <span>供应商名称:</span>
+          <el-input
+            v-model="listQuery.keyword"
+            placeholder="供应商名称"
+            clearable
+            style="width:160px;"
+            @keyup.enter.native="getList"
+            @clear="getList"
+          />
+        </div>
+        <div class="filter-control">
+          <el-button type="primary" @click="getList">查询</el-button>
+        </div>
+      </div>
+      <el-table ref="table" v-loading="isLoading" :data="supplierList" height="400px" border @select="handleSelect">
+        <el-table-column type="selection" width="55" />
+        <el-table-column label="供应商ID" prop="id" align="center" width="100" />
+        <el-table-column label="供应商名称" prop="name" align="center" />
+        <el-table-column label="联系人" prop="user" align="center" />
+        <el-table-column label="手机号" prop="phone" align="center" />
+      </el-table>
+      <div slot="footer">
+        <el-button @click="addSupplierDialog = false">取 消</el-button>
+        <el-button type="primary" @click="handleAddTagConfirm">确 定</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { parseTime } from '@/utils'
+import { fetchKeywordList, joinKeywordLibrary } from '@/api/library/keyword'
+import { export_json_to_excel } from '@/vendor/Export2Excel'
+
+export default {
+  data() {
+    const pickerOptions = {
+      shortcuts: [
+        {
+          text: '近1年',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 365)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '近半年',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 183)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '近1月',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '近1周',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '昨天',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24)
+            picker.$emit('pick', [start, end])
+          }
+        }
+      ]
+    }
+    return {
+      activeName: 'first',
+      isLoading: true,
+      addSupplierDialog: false,
+      pickerOptions,
+      time: '',
+      supplierList: [
+        { id: '45454', name: '综合供应商公司似手打发送到', user: '黄志国', phone: '13855555555' },
+        { id: '45454', name: '综合供应商公司似手打发送到', user: '黄志国', phone: '13855555555' },
+        { id: '45454', name: '综合供应商公司似手打发送到', user: '黄志国', phone: '13855555555' },
+        { id: '45454', name: '综合供应商公司似手打发送到', user: '黄志国', phone: '13855555555' },
+        { id: '45454', name: '综合供应商公司似手打发送到', user: '黄志国', phone: '13855555555' },
+        { id: '45454', name: '综合供应商公司似手打发送到', user: '黄志国', phone: '13855555555' },
+        { id: '45454', name: '综合供应商公司似手打发送到', user: '黄志国', phone: '13855555555' },
+        { id: '45454', name: '综合供应商公司似手打发送到', user: '黄志国', phone: '13855555555' },
+        { id: '45454', name: '综合供应商公司似手打发送到', user: '黄志国', phone: '13855555555' },
+        { id: '45454', name: '综合供应商公司似手打发送到', user: '黄志国', phone: '13855555555' },
+        { id: '45454', name: '综合供应商公司似手打发送到', user: '黄志国', phone: '13855555555' },
+        { id: '45454', name: '综合供应商公司似手打发送到', user: '黄志国', phone: '13855555555' },
+        { id: '45454', name: '综合供应商公司似手打发送到', user: '黄志国', phone: '13855555555' },
+        { id: '45454', name: '综合供应商公司似手打发送到', user: '黄志国', phone: '13855555555' }
+      ],
+      listQuery: {
+        keyword: '',
+        beginTime: '',
+        endTime: '',
+        labelStatus: '',
+        searchTimeCode: '',
+        fromSearch: '',
+        tags: '',
+        pageNum: 1,
+        pageSize: 100
+      },
+      list: [],
+      total: 0,
+      currentList: [],
+      searchDialog: false,
+      visibleKeyword: '',
+      tagsOptions: [
+        { value: '黄金糕', label: '黄金糕' },
+        { value: '双皮奶', label: '双皮奶' },
+        { value: '蚵仔煎', label: '蚵仔煎' },
+        { value: '龙须面', label: '龙须面' },
+        { value: '北京烤鸭', label: '北京烤鸭' },
+        { value: '双黄蛋', label: '双黄蛋' }
+      ],
+      tagsList: [],
+      supplierRadio: null
+    }
+  },
+  computed: {
+
+  },
+  created() {
+    this.getList()
+  },
+  methods: {
+    // 确定选择供应商
+    handleAddTagConfirm() {
+
+    },
+    // 选择供应商
+    handleSelect(selection, row) {
+      this.$refs.table.clearSelection()
+      this.$refs.table.toggleRowSelection(row)
+      this.supplierRadio = row
+      console.log('supplierRadio', this.supplierRadio)
+    },
+    // 获取关键词列表
+    getList() {
+      this.listQuery.pageNum = 1
+      if (this.time && this.time.length > 0) {
+        this.listQuery.beginTime = this.time[0]
+        this.listQuery.endTime = this.time[1]
+      } else {
+        this.listQuery.beginTime = ''
+        this.listQuery.endTime = ''
+      }
+      this.fetchKeywordList()
+    },
+
+    // 获取关键词列表
+    async fetchKeywordList() {
+      try {
+        this.isLoading = true
+        const res = await fetchKeywordList(this.listQuery)
+        this.list = res.data.results
+        this.total = res.data.totalRecord
+        this.isLoading = false
+      } catch (error) {
+        console.log(error)
+      }
+    },
+
+    // 添加到标签库
+    async handleAddLibrary(row) {
+      try {
+        await this.$confirm('确定将所选标签加入到标签库?', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        })
+        this.addLibrarySubmit(row)
+      } catch (error) {
+        this.$message.info('已取消操作')
+      }
+    },
+    // 删除标签
+    async handleDeleteSupper(row) {
+      try {
+        await this.$confirm('确定删除吗?该供应商关联的标签也将一并删除。', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        })
+        this.deleteTagSubmit(row)
+      } catch (error) {
+        this.$message.info('已取消删除操作')
+      }
+    },
+    // 添加到标签库提交
+    async addLibrarySubmit(row) {
+      const keywords = row instanceof Event ? this.currentList : [].concat(row)
+      const ids = keywords.map((item) => item.id).join(',')
+      try {
+        await joinKeywordLibrary({ id: ids })
+        this.$message.success('添加关键词库成功')
+        keywords.forEach((item) => {
+          item.labelStatus = 1
+        })
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    // 查看详情
+    handleTagsList(id) {
+      this.$router.push({ path: '/library/supplierTag/list', query: { id: id }})
+    },
+
+    // 导出
+    async handleExport() {
+      try {
+        await this.$confirm('确定将所选标签导出为xlsx?', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        })
+        // 导出数据格式化
+        const filterVal = ['index', 'keyword', 'frequency', 'searchTime', 'labelStatus']
+        const data = this.formatJson(filterVal, this.currentList.slice(0))
+        export_json_to_excel({
+          header: ['序号', '标签', '搜索次数', '最近搜索时间', '标签库状态'],
+          data,
+          filename: '关键词列表'
+        })
+      } catch (error) {
+        this.$message.info('已取消导出操作')
+      }
+    },
+
+    formatJson(filterVal, jsonData) {
+      return jsonData.map((v, index) =>
+        filterVal.map((key) => {
+          if (['searchTime'].includes(key)) {
+            return parseTime(v[key])
+          }
+          if (key === 'index') return index + 1
+          if (key === 'labelStatus') {
+            const t = ['未添加', '已添加', '导入']
+            return t[v[key]]
+          }
+          return v[key]
+        })
+      )
+    },
+
+    indexMethod(index) {
+      return index + this.listQuery.pageSize * (this.listQuery.pageNum - 1) + 1
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+::v-deep{
+  thead .el-checkbox{display: none !important;}
+}
+</style>

+ 203 - 0
src/views/user/club/list.vue

@@ -0,0 +1,203 @@
+<template>
+  <div class="app-container">
+    机构列表
+  </div>
+</template>
+
+<script>
+import { parseTime } from '@/utils'
+import { fetchKeywordList, joinKeywordLibrary } from '@/api/library/keyword'
+import { export_json_to_excel } from '@/vendor/Export2Excel'
+
+export default {
+  data() {
+    const pickerOptions = {
+      shortcuts: [
+        {
+          text: '近1年',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 365)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '近半年',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 183)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '近1月',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '近1周',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '昨天',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24)
+            picker.$emit('pick', [start, end])
+          }
+        }
+      ]
+    }
+    return {
+      isLoading: true,
+      time: '',
+      listQuery: {
+        keyword: '',
+        beginTime: '',
+        endTime: '',
+        labelStatus: '',
+        searchTimeCode: '',
+        fromSearch: '',
+        pageNum: 1,
+        pageSize: 10
+      },
+      pickerOptions,
+      list: [],
+      total: 0,
+      currentList: [],
+      searchDialog: false,
+      visibleKeyword: ''
+    }
+  },
+  computed: {
+    disabled() {
+      return this.currentList.length === 0
+    }
+  },
+  created() {
+    this.getList()
+  },
+  methods: {
+    // 获取关键词列表
+    getList() {
+      this.listQuery.pageNum = 1
+      if (this.time && this.time.length > 0) {
+        this.listQuery.beginTime = this.time[0]
+        this.listQuery.endTime = this.time[1]
+      } else {
+        this.listQuery.beginTime = ''
+        this.listQuery.endTime = ''
+      }
+      this.fetchKeywordList()
+    },
+
+    // 获取关键词列表
+    async fetchKeywordList() {
+      try {
+        this.isLoading = true
+        const res = await fetchKeywordList(this.listQuery)
+        this.list = res.data.results
+        this.total = res.data.totalRecord
+        this.isLoading = false
+      } catch (error) {
+        console.log(error)
+      }
+    },
+
+    // 添加到标签库
+    async handleAddLibrary(row) {
+      try {
+        await this.$confirm('确定将所选标签加入到标签库?', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        })
+        this.addLibrarySubmit(row)
+      } catch (error) {
+        this.$message.info('已取消操作')
+      }
+    },
+
+    // 添加到标签库提交
+    async addLibrarySubmit(row) {
+      const keywords = row instanceof Event ? this.currentList : [].concat(row)
+      const ids = keywords.map((item) => item.id).join(',')
+      try {
+        await joinKeywordLibrary({ id: ids })
+        this.$message.success('添加关键词库成功')
+        keywords.forEach((item) => {
+          item.labelStatus = 1
+        })
+      } catch (error) {
+        console.log(error)
+      }
+    },
+
+    // 选中列表项
+    handleSelectionChange(current) {
+      this.currentList = current
+    },
+
+    // 去搜索
+    handleSearch(keyword) {
+      this.visibleKeyword = keyword
+      this.searchDialog = true
+    },
+
+    // 导出
+    async handleExport() {
+      try {
+        await this.$confirm('确定将所选标签导出为xlsx?', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        })
+        // 导出数据格式化
+        const filterVal = ['index', 'keyword', 'frequency', 'searchTime', 'labelStatus']
+        const data = this.formatJson(filterVal, this.currentList.slice(0))
+        export_json_to_excel({
+          header: ['序号', '标签', '搜索次数', '最近搜索时间', '标签库状态'],
+          data,
+          filename: '关键词列表'
+        })
+      } catch (error) {
+        this.$message.info('已取消导出操作')
+      }
+    },
+
+    formatJson(filterVal, jsonData) {
+      return jsonData.map((v, index) =>
+        filterVal.map((key) => {
+          if (['searchTime'].includes(key)) {
+            return parseTime(v[key])
+          }
+          if (key === 'index') return index + 1
+          if (key === 'labelStatus') {
+            const t = ['未添加', '已添加', '导入']
+            return t[v[key]]
+          }
+          return v[key]
+        })
+      )
+    },
+
+    indexMethod(index) {
+      return index + this.listQuery.pageSize * (this.listQuery.pageNum - 1) + 1
+    }
+  }
+}
+</script>
+
+<style></style>

+ 255 - 0
src/views/user/record/detail-list.vue

@@ -0,0 +1,255 @@
+<template>
+  <div class="app-container">
+    <!-- 顶部操作区域 -->
+    <div class="filter-container">
+      <!-- 关键词列表 -->
+      <el-table v-loading="isLoading" :data="list" border>
+        <el-table-column label="页面路径" :index="indexMethod" type="index" sortable="custom" align="center" width="300" />
+        <el-table-column label="访问来源" :index="indexMethod" type="index" sortable="custom" align="center" width="120" />
+        <el-table-column label="页面类型" :index="indexMethod" type="index" sortable="custom" align="center" width="100" />
+        <el-table-column label="公司类型" :index="indexMethod" type="index" sortable="custom" align="center" width="100" />
+        <el-table-column label="关联供应商" :index="indexMethod" type="index" sortable="custom" align="center" width="100" />
+        <el-table-column label="页面标签" :index="indexMethod" type="index" sortable="custom" align="center" width="150" />
+        <el-table-column label="商品图片" :index="indexMethod" type="index" sortable="custom" align="center" width="100">
+          <template>
+            <el-popover
+              placement="top-start"
+              title=""
+              width="180"
+              trigger="hover"
+            >
+              <img src="https://img1.baidu.com/it/u=2427424503,947601508&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500" alt="" style="width:150px;height:150px;">
+              <img slot="reference" src="https://img1.baidu.com/it/u=2427424503,947601508&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500" alt="" style="width:30px;height:30px;">
+            </el-popover>
+          </template>
+        </el-table-column>
+        <el-table-column label="商品ID" :index="indexMethod" type="index" sortable="custom" align="center" width="100" />
+        <el-table-column label="商品名称" :index="indexMethod" type="index" sortable="custom" align="center" width="300" />
+        <el-table-column label="访问时间" :index="indexMethod" type="index" sortable="custom" align="center" width="200" />
+        <el-table-column label="访问时长" :index="indexMethod" type="index" sortable="custom" align="center" />
+      </el-table>
+      <!-- 页码 -->
+      <pagination
+        :total="total"
+        :page.sync="listQuery.pageNum"
+        :limit.sync="listQuery.pageSize"
+        @pagination="fetchKeywordList"
+      />
+    </div>
+  </div></template>
+
+<script>
+import { parseTime } from '@/utils'
+import { fetchKeywordList, joinKeywordLibrary } from '@/api/library/keyword'
+import { export_json_to_excel } from '@/vendor/Export2Excel'
+
+export default {
+  data() {
+    const pickerOptions = {
+      shortcuts: [
+        {
+          text: '近1年',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 365)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '近半年',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 183)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '近1月',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '近1周',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '昨天',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24)
+            picker.$emit('pick', [start, end])
+          }
+        }
+      ]
+    }
+    return {
+      activeName: 'first',
+      isLoading: true,
+      pickerOptions,
+      time: '',
+      listQuery: {
+        keyword: '',
+        beginTime: '',
+        endTime: '',
+        labelStatus: '',
+        searchTimeCode: '',
+        fromSearch: '',
+        tags: '',
+        pageNum: 1,
+        pageSize: 100
+      },
+      list: [],
+      total: 0,
+      currentList: [],
+      searchDialog: false,
+      visibleKeyword: '',
+      tagsOptions: [
+        { value: '黄金糕', label: '黄金糕' },
+        { value: '双皮奶', label: '双皮奶' },
+        { value: '蚵仔煎', label: '蚵仔煎' },
+        { value: '龙须面', label: '龙须面' },
+        { value: '北京烤鸭', label: '北京烤鸭' },
+        { value: '双黄蛋', label: '双黄蛋' }
+      ],
+      tagsList: [],
+      tabsCurrent: 0
+    }
+  },
+  computed: {
+
+  },
+  created() {
+    this.getList()
+  },
+  methods: {
+    // tab切换
+    handleClick(tab, event) {
+      if (tab.name === 'first') {
+        console.log('data', '今日数据')
+        this.tabsCurrent = 0
+      } else if (tab.name === 'second') {
+        console.log('data', '以往数据')
+        this.tabsCurrent = 1
+      }
+    },
+    handleTagsOptions(arr) {
+      this.listQuery.tags = arr.join(',')
+      this.getList()
+      console.log('tags', this.listQuery.tags)
+    },
+    // 获取关键词列表
+    getList() {
+      this.listQuery.pageNum = 1
+      if (this.time && this.time.length > 0) {
+        this.listQuery.beginTime = this.time[0]
+        this.listQuery.endTime = this.time[1]
+      } else {
+        this.listQuery.beginTime = ''
+        this.listQuery.endTime = ''
+      }
+      this.fetchKeywordList()
+    },
+
+    // 获取关键词列表
+    async fetchKeywordList() {
+      try {
+        this.isLoading = true
+        const res = await fetchKeywordList(this.listQuery)
+        this.list = res.data.results
+        this.total = res.data.totalRecord
+        this.isLoading = false
+      } catch (error) {
+        console.log(error)
+      }
+    },
+
+    // 添加到标签库
+    async handleAddLibrary(row) {
+      try {
+        await this.$confirm('确定将所选标签加入到标签库?', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        })
+        this.addLibrarySubmit(row)
+      } catch (error) {
+        this.$message.info('已取消操作')
+      }
+    },
+
+    // 添加到标签库提交
+    async addLibrarySubmit(row) {
+      const keywords = row instanceof Event ? this.currentList : [].concat(row)
+      const ids = keywords.map((item) => item.id).join(',')
+      try {
+        await joinKeywordLibrary({ id: ids })
+        this.$message.success('添加关键词库成功')
+        keywords.forEach((item) => {
+          item.labelStatus = 1
+        })
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    // 查看详情
+    handleRecordDetail(id) {
+      this.$router.push({ path: '/user/record/detail', query: { id: id }})
+    },
+
+    // 导出
+    async handleExport() {
+      try {
+        await this.$confirm('确定将所选标签导出为xlsx?', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        })
+        // 导出数据格式化
+        const filterVal = ['index', 'keyword', 'frequency', 'searchTime', 'labelStatus']
+        const data = this.formatJson(filterVal, this.currentList.slice(0))
+        export_json_to_excel({
+          header: ['序号', '标签', '搜索次数', '最近搜索时间', '标签库状态'],
+          data,
+          filename: '关键词列表'
+        })
+      } catch (error) {
+        this.$message.info('已取消导出操作')
+      }
+    },
+
+    formatJson(filterVal, jsonData) {
+      return jsonData.map((v, index) =>
+        filterVal.map((key) => {
+          if (['searchTime'].includes(key)) {
+            return parseTime(v[key])
+          }
+          if (key === 'index') return index + 1
+          if (key === 'labelStatus') {
+            const t = ['未添加', '已添加', '导入']
+            return t[v[key]]
+          }
+          return v[key]
+        })
+      )
+    },
+
+    indexMethod(index) {
+      return index + this.listQuery.pageSize * (this.listQuery.pageNum - 1) + 1
+    }
+  }
+}
+</script>
+
+<style></style>

+ 337 - 0
src/views/user/record/list.vue

@@ -0,0 +1,337 @@
+<template>
+  <div class="app-container">
+    <!-- 顶部操作区域 -->
+    <el-tabs v-model="activeName" type="border-card" @tab-click="handleClick">
+      <el-tab-pane label="今日数据" name="first" />
+      <el-tab-pane label="过往数据" name="second" />
+      <div class="filter-container">
+        <div class="filter-control">
+          <span>公司名称:</span>
+          <el-input
+            v-model="listQuery.corporateName"
+            placeholder="公司名称"
+            clearable
+            @keyup.enter.native="getList"
+            @clear="getList"
+          />
+        </div>
+        <div class="filter-control">
+          <span>IP:</span>
+          <el-input
+            v-model="listQuery.ip"
+            placeholder="IP"
+            clearable
+            @keyup.enter.native="getList"
+            @clear="getList"
+          />
+        </div>
+        <div class="filter-control">
+          <span>手机号:</span>
+          <el-input
+            v-model="listQuery.phoneNumber"
+            placeholder="手机号"
+            clearable
+            style="width:160px;"
+            @keyup.enter.native="getList"
+            @clear="getList"
+          />
+        </div>
+        <div class="filter-control">
+          <span>联系人:</span>
+          <el-input
+            v-model="listQuery.contacts"
+            placeholder="联系人"
+            clearable
+            style="width:160px;"
+            @keyup.enter.native="getList"
+            @clear="getList"
+          />
+        </div>
+        <div class="filter-control">
+          <span>协销:</span>
+          <el-input
+            v-model="listQuery.spName"
+            placeholder="协销"
+            clearable
+            style="width:120px;"
+            @keyup.enter.native="getList"
+            @clear="getList"
+          />
+        </div>
+        <div class="filter-control">
+          <span>公司类型:</span>
+          <el-select v-model="listQuery.companyType" style="width:120px;" clearable @change="getList">
+            <el-option :value="0" label="请选择" />
+            <el-option :value="1" label="机构" />
+            <el-option :value="2" label="供应商" />
+            <el-option :value="3" label="游客" />
+          </el-select>
+        </div>
+        <div class="filter-control">
+          <span>访问客户端:</span>
+          <el-select v-model="listQuery.accessClient" style="width:120px;" clearable @change="getList">
+            <el-option :value="0" label="请选择" />
+            <el-option :value="1" label="网站" />
+            <el-option :value="2" label="小程序" />
+          </el-select>
+        </div>
+        <div class="filter-control">
+          <span>关联供应商:</span>
+          <el-select v-model="listQuery.accessClient" clearable @change="getList">
+            <el-option :value="0" label="请选择" />
+            <el-option :value="1" label="网站" />
+            <el-option :value="2" label="小程序" />
+          </el-select>
+        </div>
+        <div class="filter-control">
+          <span>标签:</span>
+          <el-select v-model="tagsList" multiple filterable placeholder="请选择" @change="handleTagsOptions">
+            <el-option
+              v-for="item in tagsOptions"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </div>
+        <div v-if="tabsCurrent === 1" class="filter-control">
+          <span>访问日期:</span>
+          <el-date-picker
+            v-model="time"
+            type="daterange"
+            unlink-panels
+            range-separator="至"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            :picker-options="pickerOptions"
+            @change="getList"
+          />
+        </div>
+        <div class="filter-control">
+          <el-button type="primary" @click="getList">查询</el-button>
+          <el-button v-permission="'keyword:list:export'" type="primary" @click="handleExport">
+            导出
+          </el-button>
+        </div>
+      </div>
+      <!-- 关键词列表 -->
+      <el-table v-loading="isLoading" :data="list" border>
+        <el-table-column label="IP" :index="indexMethod" type="index" sortable="custom" align="center" />
+        <el-table-column label="地区" :index="indexMethod" type="index" sortable="custom" align="center" width="200" />
+        <el-table-column label="访问客户端" :index="indexMethod" type="index" sortable="custom" align="center" width="100" />
+        <el-table-column label="公司类型" :index="indexMethod" type="index" sortable="custom" align="center" width="100" />
+        <el-table-column prop="公司名称" label="公司名称" align="center" />
+        <el-table-column label="联系人" :index="indexMethod" type="index" sortable="custom" align="center" width="100" />
+        <el-table-column label="手机号" :index="indexMethod" type="index" sortable="custom" align="center" width="100" />
+        <el-table-column label="所属协销" :index="indexMethod" type="index" sortable="custom" align="center" width="100" />
+        <el-table-column label="访问页面数量" :index="indexMethod" type="index" sortable="custom" align="center" width="80" />
+        <el-table-column label="总时长" :index="indexMethod" type="index" sortable="custom" align="center" />
+        <el-table-column label="访问日期" :index="indexMethod" type="index" sortable="custom" align="center" />
+        <el-table-column label="最后访问时间" :index="indexMethod" type="index" sortable="custom" align="center" />
+        <el-table-column label="操作" align="center" width="120">
+          <template slot-scope="{ row }">
+            <el-button type="primary" size="mini" @click="handleRecordDetail(row.id)">查看详情</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <!-- 页码 -->
+      <pagination
+        :total="total"
+        :page.sync="listQuery.pageNum"
+        :limit.sync="listQuery.pageSize"
+        @pagination="fetchRecordList"
+      />
+    </el-tabs>
+  </div>
+</template>
+
+<script>
+import { parseTime } from '@/utils'
+import { fetchRecordList } from '@/api/user/record/record'
+import { export_json_to_excel } from '@/vendor/Export2Excel'
+
+export default {
+  data() {
+    const pickerOptions = {
+      shortcuts: [
+        {
+          text: '近1年',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 365)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '近半年',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 183)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '近1月',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '近1周',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '昨天',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24)
+            picker.$emit('pick', [start, end])
+          }
+        }
+      ]
+    }
+    return {
+      activeName: 'first',
+      isLoading: true,
+      pickerOptions,
+      time: '',
+      listQuery: {
+        todayType: 0, // 0 今日数据 1 以往数据
+        ip: '', // 访问人ID
+        corporateName: '', // 公司名称
+        companyType: '', // 公司类型
+        accessClient: '', // 访问客户端
+        contacts: '', //  联系人
+        phoneNumber: '', // 手机号
+        spName: '', // 协销
+        startTime: '',
+        endTime: '',
+        pageNum: 1,
+        pageSize: 100
+      },
+      list: [],
+      total: 0,
+      currentList: [],
+      searchDialog: false,
+      visibleKeyword: '',
+      tagsOptions: [
+        { value: '黄金糕', label: '黄金糕' },
+        { value: '双皮奶', label: '双皮奶' },
+        { value: '蚵仔煎', label: '蚵仔煎' },
+        { value: '龙须面', label: '龙须面' },
+        { value: '北京烤鸭', label: '北京烤鸭' },
+        { value: '双黄蛋', label: '双黄蛋' }
+      ],
+      tagsList: [],
+      tabsCurrent: 0
+    }
+  },
+  computed: {
+
+  },
+  created() {
+    this.getList()
+  },
+  methods: {
+    // tab切换
+    handleClick(tab, event) {
+      if (tab.name === 'first') {
+        console.log('data', '今日数据')
+        this.tabsCurrent = this.listQuery.todayType = 0
+      } else if (tab.name === 'second') {
+        console.log('data', '以往数据')
+        this.tabsCurrent = this.listQuery.todayType = 1
+      }
+    },
+    handleTagsOptions(arr) {
+      this.listQuery.tags = arr.join(',')
+      this.getList()
+      console.log('tags', this.listQuery.tags)
+    },
+    // 获取关键词列表
+    getList() {
+      this.listQuery.pageNum = 1
+      if (this.time && this.time.length > 0) {
+        this.listQuery.startTime = this.time[0]
+        this.listQuery.endTime = this.time[1]
+      } else {
+        this.listQuery.startTime = ''
+        this.listQuery.endTime = ''
+      }
+      this.fetchRecordList()
+    },
+
+    // 获取关键词列表
+    async fetchRecordList() {
+      try {
+        this.isLoading = true
+        const res = await fetchRecordList(this.listQuery)
+        this.list = res.data.results
+        this.total = res.data.totalRecord
+        this.isLoading = false
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    // 查看详情
+    handleRecordDetail(id) {
+      this.$router.push({ path: '/user/record/detail', query: { id: id }})
+    },
+
+    // 导出
+    async handleExport() {
+      try {
+        await this.$confirm('确定将所选标签导出为xlsx?', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        })
+        // 导出数据格式化
+        const filterVal = ['index', 'keyword', 'frequency', 'searchTime', 'labelStatus']
+        const data = this.formatJson(filterVal, this.currentList.slice(0))
+        export_json_to_excel({
+          header: ['序号', '标签', '搜索次数', '最近搜索时间', '标签库状态'],
+          data,
+          filename: '关键词列表'
+        })
+      } catch (error) {
+        this.$message.info('已取消导出操作')
+      }
+    },
+
+    formatJson(filterVal, jsonData) {
+      return jsonData.map((v, index) =>
+        filterVal.map((key) => {
+          if (['searchTime'].includes(key)) {
+            return parseTime(v[key])
+          }
+          if (key === 'index') return index + 1
+          if (key === 'labelStatus') {
+            const t = ['未添加', '已添加', '导入']
+            return t[v[key]]
+          }
+          return v[key]
+        })
+      )
+    },
+
+    indexMethod(index) {
+      return index + this.listQuery.pageSize * (this.listQuery.pageNum - 1) + 1
+    }
+  }
+}
+</script>
+
+<style></style>

+ 204 - 0
src/views/user/supplier/list.vue

@@ -0,0 +1,204 @@
+<template>
+  <div class="app-container">
+    <!-- 顶部操作区域 -->
+    供应商列表
+  </div>
+</template>
+
+<script>
+import { parseTime } from '@/utils'
+import { fetchKeywordList, joinKeywordLibrary } from '@/api/library/keyword'
+import { export_json_to_excel } from '@/vendor/Export2Excel'
+
+export default {
+  data() {
+    const pickerOptions = {
+      shortcuts: [
+        {
+          text: '近1年',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 365)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '近半年',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 183)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '近1月',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '近1周',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
+            picker.$emit('pick', [start, end])
+          }
+        },
+        {
+          text: '昨天',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24)
+            picker.$emit('pick', [start, end])
+          }
+        }
+      ]
+    }
+    return {
+      isLoading: true,
+      time: '',
+      listQuery: {
+        keyword: '',
+        beginTime: '',
+        endTime: '',
+        labelStatus: '',
+        searchTimeCode: '',
+        fromSearch: '',
+        pageNum: 1,
+        pageSize: 10
+      },
+      pickerOptions,
+      list: [],
+      total: 0,
+      currentList: [],
+      searchDialog: false,
+      visibleKeyword: ''
+    }
+  },
+  computed: {
+    disabled() {
+      return this.currentList.length === 0
+    }
+  },
+  created() {
+    this.getList()
+  },
+  methods: {
+    // 获取关键词列表
+    getList() {
+      this.listQuery.pageNum = 1
+      if (this.time && this.time.length > 0) {
+        this.listQuery.beginTime = this.time[0]
+        this.listQuery.endTime = this.time[1]
+      } else {
+        this.listQuery.beginTime = ''
+        this.listQuery.endTime = ''
+      }
+      this.fetchKeywordList()
+    },
+
+    // 获取关键词列表
+    async fetchKeywordList() {
+      try {
+        this.isLoading = true
+        const res = await fetchKeywordList(this.listQuery)
+        this.list = res.data.results
+        this.total = res.data.totalRecord
+        this.isLoading = false
+      } catch (error) {
+        console.log(error)
+      }
+    },
+
+    // 添加到标签库
+    async handleAddLibrary(row) {
+      try {
+        await this.$confirm('确定将所选标签加入到标签库?', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        })
+        this.addLibrarySubmit(row)
+      } catch (error) {
+        this.$message.info('已取消操作')
+      }
+    },
+
+    // 添加到标签库提交
+    async addLibrarySubmit(row) {
+      const keywords = row instanceof Event ? this.currentList : [].concat(row)
+      const ids = keywords.map((item) => item.id).join(',')
+      try {
+        await joinKeywordLibrary({ id: ids })
+        this.$message.success('添加关键词库成功')
+        keywords.forEach((item) => {
+          item.labelStatus = 1
+        })
+      } catch (error) {
+        console.log(error)
+      }
+    },
+
+    // 选中列表项
+    handleSelectionChange(current) {
+      this.currentList = current
+    },
+
+    // 去搜索
+    handleSearch(keyword) {
+      this.visibleKeyword = keyword
+      this.searchDialog = true
+    },
+
+    // 导出
+    async handleExport() {
+      try {
+        await this.$confirm('确定将所选标签导出为xlsx?', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        })
+        // 导出数据格式化
+        const filterVal = ['index', 'keyword', 'frequency', 'searchTime', 'labelStatus']
+        const data = this.formatJson(filterVal, this.currentList.slice(0))
+        export_json_to_excel({
+          header: ['序号', '标签', '搜索次数', '最近搜索时间', '标签库状态'],
+          data,
+          filename: '关键词列表'
+        })
+      } catch (error) {
+        this.$message.info('已取消导出操作')
+      }
+    },
+
+    formatJson(filterVal, jsonData) {
+      return jsonData.map((v, index) =>
+        filterVal.map((key) => {
+          if (['searchTime'].includes(key)) {
+            return parseTime(v[key])
+          }
+          if (key === 'index') return index + 1
+          if (key === 'labelStatus') {
+            const t = ['未添加', '已添加', '导入']
+            return t[v[key]]
+          }
+          return v[key]
+        })
+      )
+    },
+
+    indexMethod(index) {
+      return index + this.listQuery.pageSize * (this.listQuery.pageNum - 1) + 1
+    }
+  }
+}
+</script>
+
+<style></style>