jwyu 9 tháng trước cách đây
mục cha
commit
d048731296

+ 1 - 1
.env.test

@@ -1,5 +1,5 @@
 # 接口地址
-VITE_APP_API_URL="http://8.136.199.33:8705/adminapi/"
+VITE_APP_API_URL="/adminapi"
 # 路由根地址
 VITE_APP_BASE_URL="/"
 # 打包输入文件名

+ 2 - 0
src/api/customer/index.js

@@ -1,6 +1,8 @@
 import apiCustomerUser from './modules/user'
+import apiCustomerStatistic from './modules/statistic'
 
 export {
   apiCustomerUser,
+  apiCustomerStatistic,
 }
 

+ 17 - 0
src/api/customer/modules/statistic.js

@@ -0,0 +1,17 @@
+import { get, post } from "@/api/index";
+
+export default {
+  //用户阅读统计列表数据
+  readRecordList: params => {
+    return get("/read/list", params);
+  },
+  // 用户阅读统计详情数据
+  readRecordInfo: params => {
+    return get("/read/detail", params);
+  },
+  // 用户阅读统计图数据
+  readRecordChartData:params=>{
+    return get('/read/chart/info',params)
+  }
+  
+};

+ 1 - 1
src/api/system/message.js

@@ -5,7 +5,7 @@ export default {
   list: () => {
     return get("/sys_message/list", {});
   },
-  // 手机号区号
+  // 消息已读
   msgRead: (params) => {
     return post("/sys_message/read", params);
   },

+ 13 - 10
src/components/SelectPermission.vue

@@ -14,20 +14,23 @@ const emits=defineEmits(['change'])
 const options=ref([])
 function formatData(arr){
   arr.forEach(item => {
-    item.label=item.SysUserId?item.SysRealName:item.SysDepartmentName
-    item.value=item.SysUserId?item.SysUserId:item.SysDepartmentId
-    if(item.ChildrenList){
-      formatData(item.ChildrenList)
+    let temarr=[]
+    if(item.PublicChild){
+      temarr=[...temarr,...item.PublicChild]
     }
+    if(item.PrivateChild){
+      temarr=[...temarr,...item.PrivateChild]
+    }
+    item.children=temarr
   });
   return arr
 }
 
 async function getData(){
-  const res=await apiCustomerUser.userPermissionData()//true显示所有销售(包含被禁用的)false表示仅显示启用的用户,默认false
+  const res=await apiCustomerUser.userPermissionData()
   if(res.Ret!=200) return
-  // const arr=res.Data.List||[]
-  // options.value=formatData(arr)
+  const arr=res.Data.List||[]
+  options.value=formatData(arr)
 }
 getData()
 
@@ -40,9 +43,9 @@ function handleChange(){
   <el-cascader
     :options="options"
     :props="{
-      value: 'value',
-      label: 'label',
-      children: 'ChildrenList',
+      value: 'ChartPermissionId',
+      label: 'PermissionName',
+      children: 'children',
       ...props.props
     }"
     v-model="model"

+ 24 - 18
src/directives/buttonPermission.js

@@ -1,27 +1,33 @@
 import {usePermission} from '@/hooks/permission'
 import { watchEffect } from 'vue';
 
-const {authList} =usePermission()
+const {authCodeOpts} =usePermission()
 
+function checkPermission(el,binding) {
+  let {value} = binding
+  if(value && typeof(value)=='string'){
+    // 使用watchEffect来监听authList的变化
+    watchEffect(() => {
+      // 如果authList还未获取到,则直接返回
+      if (!authCodeOpts.value) return;
+
+      // 拿出所有按钮的code
+      let buttonCodes = authCodeOpts.value
+      if (!buttonCodes.includes(value)) {
+        // 没有权限,删除dom
+        el.parentNode && el.parentNode.removeChild(el);
+      }
+    });
+  }else{
+    throw new Error('permission指令只接受字符串类型')
+  }
+}
 
 export const permission={
+  inserted(el,binding){
+    checkPermission(el,binding)
+  },
   updated(el,binding) {
-    let {value} = binding
-    if(value && typeof(value)=='string'){
-      // 使用watchEffect来监听authList的变化
-      watchEffect(() => {
-        // 如果authList还未获取到,则直接返回
-        if (!authList.value) return;
-
-        // 拿出所有按钮的code
-        let buttonCodes = authList.value.map(item => item.ButtonCode);
-        if (!buttonCodes.includes(value)) {
-          // 没有权限,删除dom
-          el.parentNode && el.parentNode.removeChild(el);
-        }
-      });
-    }else{
-      throw new Error('permission指令只接受字符串类型')
-    }
+    checkPermission(el,binding)
   },
 }

+ 9 - 1
src/hooks/permission/index.js

@@ -1,7 +1,8 @@
 import {ref} from 'vue'
 import {apiSystemUser} from '@/api/system'
 
-let authList=ref([])
+const authList=ref([])
+const authCodeOpts=ref([])
 
 export function usePermission(){
 
@@ -9,14 +10,21 @@ export function usePermission(){
     apiSystemUser.userPermissionData().then(res=>{
       if(res.Ret===200){
         authList.value=res.Data||[]
+        authCodeOpts.value=authList.value.map(item => item.ButtonCode);
       }
     })
   }
 
+  function hasPermission(btn){
+    return authCodeOpts.value.includes(btn)
+  }
+
 
 
   return {
     authList,
+    authCodeOpts,
     getPermissionData,
+    hasPermission,
   }
 }

+ 5 - 2
src/layout/components/HeaderWrap.vue

@@ -2,6 +2,7 @@
 import { useLayoutState } from '../hooks/index'
 import { useRoute, useRouter } from 'vue-router'
 import NoticeWrap from './NoticeWrap.vue'
+import {apiSystemMessage} from '@/api/system'
 
 const { menuClose, menuCloseChange } = useLayoutState()
 
@@ -21,7 +22,9 @@ const breadcrumbArr=computed(()=>{
 })
 
 function handleReadNotice(){
-  
+  apiSystemMessage.msgRead().then(res=>{
+    
+  })
 }
 
 
@@ -43,7 +46,7 @@ function handleReadNotice(){
     </el-breadcrumb>
     <div class="content"></div>
     <!-- 通知 -->
-    <el-popover placement="bottom" :width="400" trigger="click" @show="handleReadNotice">
+    <el-popover placement="bottom" :width="500" trigger="click" @show="handleReadNotice" v-if="hasPermission('message:info')">
       <template #reference>
         <svg-icon name="notification-filled" size="18px"></svg-icon>
       </template>

+ 37 - 12
src/layout/components/NoticeWrap.vue

@@ -1,37 +1,62 @@
 <script setup>
-import {apiSystemMessage} from '@/api/system'
+import { apiSystemMessage } from '@/api/system'
+import { useRouter } from 'vue-router'
 
-const list=ref([])
-function getMsgList(){
-  apiSystemMessage.list().then(res=>{
-    if(res.Ret===200){
-      list.value=res.Data.List||[]
+const router=useRouter()
+
+const list = ref([])
+function getMsgList() {
+  apiSystemMessage.list().then(res => {
+    if (res.Ret === 200) {
+      list.value = res.Data.List || []
     }
   })
 }
 getMsgList()
 
+function handleGoCustomerDetail(e){
+  router.push('/customer/userDetail?id=' + e.UserId)
+}
+
 
 </script>
 
 <template>
   <div class="notice-wrap">
     <div class="title">客户到期提醒</div>
-    <div class="list">
-      <div class="item" v-for="item in list" :key="item"></div>
+    <div class="list-wrap">
+      <div
+        :class="['item', item.IsRead ? 'item-read' : '']"
+        v-for="item in list"
+        :key="item.SysMessageReportId"
+        @click="handleGoCustomerDetail(item)"
+      >
+        <span style="margin-right: 20px">{{ item.Content }}</span>
+        <span>{{ item.CreateTime }}</span>
+      </div>
     </div>
   </div>
 </template>
 
 <style lang="scss" scoped>
-.notice-wrap{
-  .title{
+.notice-wrap {
+  .title {
     padding: 20px 0;
-    border-bottom: 1px solid #E7E7E7;
+    border-bottom: 1px solid #e7e7e7;
   }
-  .list-wrap{
+  .list-wrap {
     max-height: 500px;
     overflow-y: auto;
+    .item {
+      padding: 10px;
+      cursor: pointer;
+      span {
+        display: inline-block;
+      }
+    }
+    .item-read {
+      color: #999;
+    }
   }
 }
 </style>

+ 6 - 0
src/main.js

@@ -10,12 +10,18 @@ import '@/styles/element.scss'
 import 'virtual:svg-icons-register'
 import registerGlobalComponents from '@/components/globalComponents';
 import {formatTime} from '@/utils/common'
+import {usePermission} from '@/hooks/permission'
+
+const {hasPermission}=usePermission()
 
 const app= createApp(App)
 
 // 挂载一个全局格式化时间方法
 app.config.globalProperties.formatTime=formatTime
 
+// 挂载一个全局的判断权限的方法
+app.config.globalProperties.hasPermission=hasPermission
+
 // 注册全局指令
 import * as directives from '@/directives'
 Object.keys(directives).forEach(key =>{

+ 144 - 150
src/views/customer/components/PermissionTable.vue

@@ -1,191 +1,176 @@
 <script setup>
 import { apiCustomerUser } from '@/api/customer'
+import { watch } from 'vue'
 
-const props=defineProps({
-  userId:{
-    type:[Number,String],
-    default:''
+const props = defineProps({
+  userId: {
+    type: [Number, String],
+    default: ''
   }
 })
 
-const checkedIds = defineModel('checkedIds',{type:Array,default:[]})
+const checkedIds = defineModel('checkedIds', { type: Array, default: [] })
 
-const PublicAuth = ref([])
-const PrivateAuth = ref([])
+const opts = ref([])
 async function getAuthData() {
   const res = await apiCustomerUser.userPermissionData({
-    UserId:props.userId||''
+    UserId: props.userId || ''
   })
   if (res.Ret !== 200) return
-  const pubArr = res.Data.PublicList || []
-  const priArr = res.Data.PrivateList || []
-  const selectArr=res.Data.SelectedList||[]
-  PublicAuth.value = setData(pubArr, 'public',selectArr)
-  PrivateAuth.value = setData(priArr,'private',selectArr)
+  opts.value = res.Data.List || []
+  const selectArr = res.Data.SelectedList || []
+  if (selectArr.length === 0) {
+    opts.value.forEach(item => {
+      item.PublicChild && item.PublicChild.forEach(_i => {
+        checkedIds.value.push(_i.ChartPermissionId)
+      })
+    });
+  } else {
+    checkedIds.value = res.Data.SelectedList
+  }
 
 }
 getAuthData()
 
-function setData(data, type,selectArr) {
-  let arr = []
-  data.forEach(item => {
-    item.Child?.forEach((_item, index) => {
-      arr.push({
-        ..._item,
-        parent_permission_name: item.permission_name,
-        rowSpan: index == 0 ? item.Child.length : 0,
-      })
-      if (type==='public') {
-        if(selectArr.length===0){
-          checkedIds.value.push(_item.chart_permission_id)
-        }else{
-          checkedIds.value=selectArr
-        }
-      }
-    })
-  });
-
-  return arr
+// 二级品种选中态改变
+function handleSelectChange(checked, item) {
+  console.log('二级品种选中态改变');
+  if (checked) {
+    checkedIds.value.push(item.ChartPermissionId)
+  } else {
+    checkedIds.value = checkedIds.value.filter(i => i !== item.ChartPermissionId)
+  }
 }
 
 // 一级品种选中态改变
 function handleChangeLevel1(checked, item) {
+  console.log('一级品种选中态改变');
   if (checked) {
-    console.log(item.parent_id);
-    PrivateAuth.value.forEach(_e => {
-      if (_e.parent_id === item.parent_id) {
-        // 将该一级品种下的二级品种id全选中
-        if (!checkedIds.value.includes(_e.chart_permission_id)) {
-          checkedIds.value.push(_e.chart_permission_id)
-        }
+    item.PrivateChild && item.PrivateChild.forEach(_i => {
+      if (!checkedIds.value.includes(_i.ChartPermissionId)) {
+        checkedIds.value.push(_i.ChartPermissionId)
       }
     })
-  } else {//取消全选
+  } else {
+    //取消全选
     let ids = []
-    PrivateAuth.value.forEach(_e => {
-      if (_e.parent_id === item.parent_id) {
-        ids.push(_e.chart_permission_id)
-      }
+    item.PrivateChild && item.PrivateChild.forEach(_e => {
+      ids.push(_e.ChartPermissionId)
     })
     checkedIds.value = checkedIds.value.filter(_e => !ids.includes(_e))
   }
 }
 
-// 二级品种选中态改变
-function handleSelectChange(checked, item) {
-  if (checked) {
-    checkedIds.value.push(item.chart_permission_id)
-  } else {
-    checkedIds.value = checkedIds.value.filter(i => i !== item.chart_permission_id)
-  }
-}
-
 // 控制一级品种的状态 0全没选中 1部分选中 2全选中
 function level1Status(item) {
   let allCount = 0//一级品种总共有多少二级
   let count = 0//当前有多少该一级品种下面的二级是选中的
-  PrivateAuth.value.forEach(_e => {
-    if (_e.parent_id === item.parent_id) {
-      allCount++
-      if(checkedIds.value.includes(_e.chart_permission_id)){
+  if (item.PublicChild) {
+    allCount = allCount + item.PublicChild.length
+    count = count + item.PublicChild.length //公有的都选中
+  }
+  if (item.PrivateChild) {
+    allCount = allCount + item.PrivateChild.length
+    item.PrivateChild.forEach(_i => {
+      if (checkedIds.value.includes(_i.ChartPermissionId)) {
         count++
       }
-    }
-  })
-  if(count===0){//全没选中
+    })
+  }
+
+  if (count === 0) {//全没选中
     return 0
-  }else if(count===allCount){//全选中了
+  } else if (count === allCount) {//全选中了
     return 2
-  }else{//部分选中
+  } else {//部分选中
     return 1
   }
 }
 
-const allSelectStatus=computed(()=>{
-  let flag=1
-  if(PrivateAuth.value.length===(checkedIds.value.length-PublicAuth.value.length)){
-    flag=2
-  }
-  return flag
+//全选状态 0全没选中 1部分选中 2全选中
+const allSelectStatus = computed(() => {
+
+  let count = 0 //总共有多少二级品种
+  opts.value.forEach(item => {
+    if (item.PublicChild) {
+      count = count + item.PublicChild.length
+    }
+    if (item.PrivateChild) {
+      count = count + item.PrivateChild.length
+    }
+  })
+
+  if (checkedIds.value.length === 0) return 0
+  if (checkedIds.value.length > 0 && checkedIds.value.length < count) return 1
+  if (checkedIds.value.length === count) return 2
 })
 
-function handleChangeAll(checked){
-  if(checked){
-    PrivateAuth.value.forEach(_e=>{
-      if(!checkedIds.value.includes(_e.chart_permission_id)){
-        checkedIds.value.push(_e.chart_permission_id)
-      }
+// 全选状态改变
+function handleChangeAll(checked) {
+  let arr=[]
+  opts.value.forEach(item => {
+    item.PublicChild?.forEach(_i=>{
+      arr.push(_i.ChartPermissionId)
     })
-  }else{
-    let allPrivateAuthIds=PrivateAuth.value.map(_e=>_e.chart_permission_id)
-    checkedIds.value=checkedIds.value.filter(_e=>!allPrivateAuthIds.includes(_e))
-  }
+    if (checked){//权限将所有私有权限选中
+      item.PrivateChild?.forEach(_i=>{
+        arr.push(_i.ChartPermissionId)
+      })
+    }
+  })
+  checkedIds.value=arr
 }
 
 </script>
 
 <template>
   <div class="permission-table-wrap">
-    <table
-      class="el-table table-box"
-      width="auto"
-      border="0"
-      cellspacing="0"
-      cellpadding="0"
+    <div class="item-wrap">
+      <div class="checkall-box">
+        <el-checkbox
+          :model-value="allSelectStatus === 2"
+          :indeterminate="allSelectStatus === 1"
+          @change="handleChangeAll"
+          >全选</el-checkbox
+        >
+      </div>
+    </div>
+    <div
+      class="item-wrap"
+      v-for="level1 in opts"
+      :key="level1.ChartPermissionId"
     >
-      <thead>
-        <td>权限分类</td>
-        <td>一级品种</td>
-        <td>二级品种</td>
-        <td>
-          <el-checkbox
-            :model-value="allSelectStatus===2"
-            :indeterminate="allSelectStatus===1"
-            @change="handleChangeAll"
-          >全选</el-checkbox>
-        </td>
-      </thead>
-      <tbody>
-        <!-- 公有 -->
-        <tr v-for="(item, index) in PublicAuth" :key="item.chart_permission_id">
-          <td :rowspan="PublicAuth.length" v-if="index === 0">公有</td>
-          <td :rowspan="item.rowSpan" v-if="item.rowSpan > 0">
-            <span>{{ item.parent_permission_name }}</span>
-            <!-- <el-icon><i-ep-ArrowDown /></el-icon> -->
-          </td>
-          <td>{{ item.permission_name }}</td>
-          <td>
-            <el-checkbox
-              :model-value="checkedIds.includes(item.chart_permission_id)"
-              disabled
-            />
-          </td>
-        </tr>
-        <!-- 私有 -->
-        <tr
-          v-for="(item, index) in PrivateAuth"
-          :key="item.chart_permission_id"
+      <div class="checkall-box">
+        <el-checkbox
+          @change="handleChangeLevel1($event, level1)"
+          :model-value="level1Status(level1) === 2"
+          :indeterminate="level1Status(level1) === 1"
+          >{{ level1.PermissionName }}</el-checkbox
         >
-          <td :rowspan="PrivateAuth.length" v-if="index === 0">私有</td>
-          <td :rowspan="item.rowSpan" v-if="item.rowSpan > 0">
-            <el-checkbox
-              :model-value="level1Status(item) === 2"
-              :indeterminate="level1Status(item) === 1"
-              @change="handleChangeLevel1($event, item)"
-              >{{ item.parent_permission_name }}
-            </el-checkbox>
-            <!-- <el-icon><i-ep-ArrowDown /></el-icon> -->
-          </td>
-          <td>{{ item.permission_name }}</td>
-          <td>
-            <el-checkbox
-              :model-value="checkedIds.includes(item.chart_permission_id)"
-              @change="handleSelectChange($event, item)"
-            />
-          </td>
-        </tr>
-      </tbody>
-    </table>
+      </div>
+      <!-- 公有权限 -->
+      <div class="child-box" v-if="level1.PublicChild">
+        <span class="label-tag pub-tag">公有</span>
+        <el-checkbox
+          :model-value="checkedIds.includes(pubitem.ChartPermissionId)"
+          disabled
+          v-for="pubitem in level1.PublicChild"
+          :key="pubitem.ChartPermissionId"
+          >{{ pubitem.PermissionName }}</el-checkbox
+        >
+      </div>
+      <!-- 私有权限 -->
+      <div class="child-box" v-if="level1.PrivateChild">
+        <span class="label-tag pri-tag">私有</span>
+        <el-checkbox
+          :model-value="checkedIds.includes(priitem.ChartPermissionId)"
+          v-for="priitem in level1.PrivateChild"
+          :key="priitem.ChartPermissionId"
+          @change="handleSelectChange($event, priitem)"
+          >{{ priitem.PermissionName }}</el-checkbox
+        >
+      </div>
+    </div>
   </div>
 </template>
 
@@ -193,20 +178,29 @@ function handleChangeAll(checked){
 .permission-table-wrap {
   margin-top: 10px;
   margin-left: 60px;
-  .table-box {
-    max-width: 800px;
-    width: 100%;
-    border-left: 1px solid #dcdfe6;
-    border-bottom: 1px solid #dcdfe6;
-    td {
-      text-align: center;
-      padding: 15px 0;
-      border-top: 1px solid #dcdfe6;
-      border-right: 1px solid #dcdfe6;
+  border: 1px solid #ebeff6;
+  .item-wrap {
+    border-bottom: 1px solid #ebeff6;
+    .checkall-box {
+      border-bottom: 1px solid #ebeff6;
+      padding: 14px 16px;
+      background-color: #f9faff;
     }
-    thead {
-      color: var(--el-table-header-text-color);
-      background-color: var(--el-table-header-bg-color);
+    .child-box {
+      padding: 14px 16px;
+      .label-tag {
+        display: inline-block;
+        padding: 2px 3px;
+        margin-right: 10px;
+      }
+      .pub-tag {
+        color: #436cff;
+        background-color: #d1e4ff;
+      }
+      .pri-tag {
+        color: #edab29;
+        background-color: #fffaeb;
+      }
     }
   }
 }

+ 70 - 26
src/views/customer/reportStatistic/Chart.vue

@@ -1,36 +1,82 @@
 <script setup>
-import { Search,Switch } from '@element-plus/icons-vue'
-import {lineChartRender,pieChartRender} from './utils/chartRender'
+import { Search, Switch } from '@element-plus/icons-vue'
+import { lineChartRender, pieChartRender } from './utils/chartRender'
+import { apiCustomerStatistic } from '@/api/customer'
 
-const emits=defineEmits(['change'])
+const emits = defineEmits(['change'])
 
-const filterState=reactive({
-  permission:'',
-  type:'',
-  time:'',
-  keyword:''
+const filterState = reactive({
+  permission: '',
+  type: '',
+  time: '',
+  keyword: ''
 })
 
-function getLineChartData(){
-  const data=[]
-  lineChartRender(data)
+async function getLineChartData() {
+  const res = await apiCustomerStatistic.readRecordChartData({
+    StartDate: filterState.time ? filterState.time[0] : '',
+    EndDate: filterState.time ? filterState.time[1] : '',
+    ClassifyIds: '',
+    ChartPermissionIds: filterState.permission?filterState.permission.join(','):''
+  })
+  if (res.Ret === 200) {
+    const arr = res.Data.ReadCntList || []
+    let data = {
+      time: [],
+      value: []
+    }
+    arr.forEach(item => {
+      data.time.push(item.CreateDate)
+      data.value.push(item.Count)
+    })
+    lineChartRender(data)
+
+    const pArr=res.Data.PermissionCntList || []
+    getPieChartData(pArr)
+  }
+
 }
 
-function getPieChartData(){
-  
+function getPieChartData(data) {
+  let arr = data.map(item => {
+    return {
+      name: item.PermissionName,
+      y: item.Count
+    }
+  })
+  pieChartRender(arr)
 }
 
-onMounted(()=>{
+onMounted(() => {
   getLineChartData()
-  getPieChartData()
+  // getPieChartData()
 })
 
+function handleRefreshData(){
+  getLineChartData()
+}
+
 </script>
 
 <template>
   <div class="user-report-statistic-chart-page">
     <div class="flex filter-wrap">
-      <el-button type="primary" :icon="Switch" link @click="emits('change','chart')">数据表</el-button>
+      <el-button
+        type="primary"
+        :icon="Switch"
+        link
+        @click="emits('change', 'chart')"
+        >数据表</el-button
+      >
+      <select-permission
+        v-model="filterState.permission"
+        :props="{
+          emitPath: false,
+          multiple: true,
+        }"
+        clearable
+        @change="handleRefreshData"
+      />
       <span style="width: 235px">
         <el-date-picker
           style="width: 235px"
@@ -40,6 +86,7 @@ onMounted(()=>{
           start-placeholder="开始时间"
           end-placeholder="结束时间"
           value-format="YYYY-MM-DD"
+          @change="handleRefreshData"
         />
       </span>
       <el-input
@@ -47,19 +94,16 @@ onMounted(()=>{
         v-model="filterState.keyword"
         :prefix-icon="Search"
         clearable
-        style="max-width: 359px;margin-left:auto"
+        style="max-width: 359px; margin-left: auto"
       />
-      
-
-      
     </div>
     <div class="content-wrap">
       <div class="bg-white item-wrap">
-        <h3 style="text-align:center">用户阅读量统计图</h3>
+        <h3 style="text-align: center">用户阅读量统计图</h3>
         <div class="chart-box" id="chartBox1"></div>
       </div>
       <div class="bg-white item-wrap">
-        <h3 style="text-align:center">用户阅读品种分布图</h3>
+        <h3 style="text-align: center">用户阅读品种分布图</h3>
         <div class="chart-box" id="chartBox2"></div>
       </div>
     </div>
@@ -67,20 +111,20 @@ onMounted(()=>{
 </template>
 
 <style lang="scss" scoped>
-.filter-wrap{
+.filter-wrap {
   gap: 10px;
 }
-.content-wrap{
+.content-wrap {
   display: flex;
   gap: 0 30px;
-  .item-wrap{
+  .item-wrap {
     margin-top: 20px;
     flex: 1;
     height: calc(100vh - 200px);
     padding-top: 30px;
     padding-left: 30px;
     padding-right: 30px;
-    .chart-box{
+    .chart-box {
       margin-top: 80px;
       height: 400px;
     }

+ 11 - 4
src/views/customer/reportStatistic/List.vue

@@ -1,6 +1,6 @@
 <script setup>
 import { Search,Switch } from '@element-plus/icons-vue'
-import { apiCustomerUser } from '@/api/customer'
+import { apiCustomerStatistic } from '@/api/customer'
 import UserStatisticDetail from './components/UserStatisticDetail.vue'
 
 const emits=defineEmits(['change'])
@@ -38,12 +38,12 @@ const tableColOpt = [
   },
   {
     label: '最近一次登录时间',
-    key: '',
+    key: 'RegisterTime',
     sort: true
   },
   {
     label: '累计阅读次数',
-    key: '',
+    key: 'ReadCnt',
     sort: true
   }
 ]
@@ -54,10 +54,11 @@ const tableLoading = ref(false)
 const totals = ref(0)
 async function getUserList() {
   tableLoading.value = true
-  const res = await apiCustomerUser.userList({
+  const res = await apiCustomerStatistic.readRecordList({
     PageSize: pageSize.value,
     CurrentIndex: page.value,
     KeyWord: filterState.keyword,
+    SellerId:filterState.seller?filterState.seller.join(','):'',
     Status: filterState.status,
     IsRegistered: filterState.register,
     IsSubscribed: filterState.subscribe,
@@ -85,6 +86,10 @@ function handleTableSort(e) {
 
 const showDetail=ref(false)
 const activeUserId=ref(0)
+function handleShowDetail(e){
+  activeUserId.value=e.UserId
+  showDetail.value=true
+}
 
 </script>
 
@@ -99,6 +104,7 @@ const activeUserId=ref(0)
           emitPath: false,
           multiple: true,
         }"
+        clearable
       />
       <el-select
         placeholder="用户状态"
@@ -178,6 +184,7 @@ const activeUserId=ref(0)
               :style="{ color: !row.Status ? '#f00' : '' }"
               >{{ row.Status ? "启用" : "禁用" }}</span
             >
+            <el-button v-else-if="column.key === 'ReadCnt'&&row.ReadCnt>0" link type="primary" @click="handleShowDetail(row)">{{row.ReadCnt}}</el-button>
             <span v-else>{{ row[column.key] }}</span>
           </template>
         </el-table-column>

+ 41 - 29
src/views/customer/reportStatistic/components/UserStatisticDetail.vue

@@ -1,4 +1,6 @@
 <script setup>
+import { apiCustomerStatistic } from '@/api/customer'
+import { watch } from 'vue'
 
 const show = defineModel('show', { type: Boolean, default: false })
 
@@ -9,26 +11,26 @@ const props = defineProps({
   }
 })
 
-const tableColOpt=[
+const tableColOpt = [
   {
     label: '标题',
-    key: ''
+    key: 'ReportTitle'
   },
   {
     label: '品种',
-    key: ''
+    key: 'ChartPermissionName'
   },
   {
     label: '报告类型',
-    key: ''
+    key: 'ClassifyName2'
   },
   {
     label: '阅读时间',
-    key: ''
+    key: 'CreateTime'
   },
   {
     label: '停留时间',
-    key: ''
+    key: 'StayTime'
   },
 ]
 const list = ref([])
@@ -36,32 +38,39 @@ const page = ref(1)
 const pageSize = ref(10)
 const tableLoading = ref(false)
 const totals = ref(0)
+const permissionIds = ref('')
 async function getList() {
-  // tableLoading.value = true
-  // const res = await apiCustomerUser.userList({
-  //   PageSize: pageSize.value,
-  //   CurrentIndex: page.value,
-  //   KeyWord: filterState.keyword,
-  //   Status: filterState.status,
-  //   IsRegistered: filterState.register,
-  //   IsSubscribed: filterState.subscribe,
-  //   RegisterStartDate: filterState.regsiterTime ? filterState.regsiterTime[0] : '',
-  //   RegisterEndDate: filterState.regsiterTime ? filterState.regsiterTime[1] : '',
-  //   CreateStartDate: filterState.createTime ? filterState.createTime[0] : '',
-  //   CreateEndDate: filterState.createTime ? filterState.createTime[1] : '',
-  // })
-  // tableLoading.value = false
-  // if (res.Ret === 200) {
-  //   userList.value = res.Data.List || []
-  //   totals.value = res.Data.Paging.Totals
-  // }
+  tableLoading.value = true
+  const res = await apiCustomerStatistic.readRecordInfo({
+    UserId: props.userId,
+    PageSize: pageSize.value,
+    CurrentIndex: page.value,
+    ChartPermissionIds: permissionIds.value?permissionIds.value.join(','):'',
+  })
+  tableLoading.value = false
+  if (res.Ret === 200) {
+    list.value = res.Data.List || []
+    totals.value = res.Data.Paging.Totals
+  }
 }
-getList()
 function handlePageChange(e) {
   page.value = e
   getList()
 }
 
+function handleRefreshList() {
+  page.value = 1
+  list.value = []
+  getList()
+}
+
+watch(() => show.value, (n) => {
+  if (n) {
+    permissionIds.value=''
+    handleRefreshList()
+  }
+})
+
 </script>
 
 <template>
@@ -69,13 +78,16 @@ function handlePageChange(e) {
     <div class="user-statistic-detail-wrap">
       <div>
         <select-permission
+          v-model="permissionIds"
           :props="{
             emitPath: false,
             multiple: true,
           }"
+          clearable
+          @change="handleRefreshList"
         />
       </div>
-      <div class="total-text">公有233条阅读记录</div>
+      <div class="total-text">公有{{ totals }}条阅读记录</div>
       <el-table
         :data="list"
         border
@@ -97,15 +109,15 @@ function handlePageChange(e) {
         :page-size="pageSize"
         :total="totals"
         @current-change="handlePageChange"
-        style="margin-top: 30px;justify-content: flex-end;"
+        style="margin-top: 30px; justify-content: flex-end"
       />
     </div>
   </el-dialog>
 </template>
 
 <style lang="scss" scoped>
-.user-statistic-detail-wrap{
-  .total-text{
+.user-statistic-detail-wrap {
+  .total-text {
     color: #000;
     margin-top: 30px;
     margin-bottom: 10px;

+ 85 - 141
src/views/customer/reportStatistic/utils/chartRender.js

@@ -1,151 +1,81 @@
 import Highcharts from "highcharts/highstock";
 
 const chartBaseConfig = {
-  // colors: ['#27A0FF', '#EC6E47', '#E5AF4D', '#27F7E4', '#1C3AFF', '#20BE67'],
-  chart: {
-    backgroundColor: "transparent",
+  title: {
+    text: "",
   },
-  credits: {
-    enabled: false
+  accessibility: {
+    enabled: false,
   },
-  yAxis: {
-    gridLineColor: "rgba(186, 231, 255, 0.102)",
-    labels: {
-      style: {
-        color: "#E6F7FF",
-      },
+  chart: {
+    style: {
+      fontSize: 16,
     },
   },
-  title: {
-    text: '',
+  credits: {
+    enabled: false,
   },
   legend: {
-    enabled: true,
-    itemDistance: 2,
-    itemStyle: {
-      color: "#333",
-    },
-    margin: 5,
-    padding: 2,
-    verticalAlign:'top'
-  },
-  xAxis: {
-    lineColor: "#BAE7FF",
-    labels: {
-      style: {
-        color: "rgba(230, 247, 255, 0.502)",
-      },
-    },
-    tickPosition: "inside",
-    tickLength: 3,
-    tickWidth: 1,
+    align: "center",
+    verticalAlign: "top",
   },
 };
 
 export function lineChartRender(data) {
   let options = {
-    ...chartBaseConfig,
+    // colors: ["#436DFF", "#25DEDB"],
+    plotOptions: {
+      series: {
+        marker: {
+          fillColor: "#FFFFFF",
+          lineWidth: 2,
+          lineColor: null,
+        },
+      },
+    },
     xAxis: {
-      tickPosition: "inside",
-      lineColor: "#bfbfbf",
-      tickColor: "#bfbfbf",
-      tickLength: 5,
-      type: "datetime",
-      ordinal: false,
-      dateTimeLabelFormats: {
-        day: "%y/%m",
-        week: "%y/%m",
-        month: "%y/%m",
-        year: "%y/%m",
+      categories: data.time,
+      labels: {
+        style: {
+          color: "#999",
+        },
       },
-
-      labels: {},
+      tickWidth: 1,
+      tickLength: 5,
     },
     yAxis: [
       {
+        gridLineWidth: 0,
+        lineWidth: 1,
+        lineColor: "#666666",
+        tickWidth: 1,
+        tickLength: 5,
+        labels: {
+          style: {
+            color: "#999",
+          },
+        },
         title: {
-          text: "",
+          text: "",
           align: "high",
+          style: {
+            color: "#999",
+          },
+          y: -10,
+          offset: 0,
           rotation: 0,
-          y: -12,
-          x: 0,
-          textAlign: "left",
-          reserveSpace: false,
-          style: {},
-        },
-        labels: {
-          align: "center",
         },
-        lineWidth: 1,
-        lineColor: "#bfbfbf",
-        tickColor: "#bfbfbf",
-        visible: true,
-        gridLineWidth: 0,
-        tickWidth: 1,
-        tickLength: 5,
-        tickPosition: "inside",
-        endOnTick: false,
-        startOnTick: false,
-        showLastLabel: true,
       },
     ],
     series: [
       {
-        data: [
-          [1618502400000, 50076],
-          [1618588800000, 50077],
-          [1618675200000, 50078],
-          [1618761600000, 50079],
-          [1618848000000, 50080],
-          [1618934400000, 50081],
-          [1619020800000, 50082],
-          [1619107200000, 50083],
-          [1619193600000, 50084],
-          [1619280000000, 50085],
-          [1619366400000, 50086],
-          [1619452800000, 50087],
-          [1619539200000, 50088],
-          [1650038400000, 50076],
-          [1650124800000, 50077],
-          [1650211200000, 50078],
-          [1650297600000, 50079],
-          [1650384000000, 50080],
-          [1650470400000, 50081],
-          [1650556800000, 50082],
-          [1650643200000, 50083],
-          [1650729600000, 50084],
-          [1650816000000, 50085],
-          [1650902400000, 50086],
-          [1650988800000, 50087],
-          [1651075200000, 50088],
-          [1681574400000, 50076],
-          [1681660800000, 50077],
-          [1681747200000, 50078],
-          [1681833600000, 50079],
-          [1681920000000, 50080],
-          [1682006400000, 50081],
-          [1682092800000, 50082],
-          [1682179200000, 50083],
-          [1682265600000, 50084],
-          [1682352000000, 50085],
-          [1682438400000, 50086],
-          [1682524800000, 50087],
-          [1682611200000, 50088],
-          [1713196800000, 50076],
-          [1713283200000, 50077],
-          [1713369600000, 50078],
-          [1713456000000, 50079],
-          [1713542400000, 50080],
-          [1713628800000, 50081],
-          [1713715200000, 50082],
-          [1713801600000, 50083],
-        ],
-        type: "spline",
+        data: data.value,
+        type: "line",
+        name: "阅读量",
         yAxis: 0,
-        name: "测试没有编码2",
-        lineWidth: 1,
       },
     ],
+    ...chartBaseConfig,
   };
   Highcharts.chart("chartBox1", options);
 }
@@ -153,41 +83,55 @@ export function lineChartRender(data) {
 export function pieChartRender(data) {
   let options = {
     ...chartBaseConfig,
+    // colors: ["#59F7CA", "#62F2F8", "#F2B949", "#FF8838", "#FDFDFD", "#4A7CE9"],
+    credits: {
+      enabled: false,
+    },
+    chart: {
+      ...chartBaseConfig.chart,
+    },
+    title: {
+      text: "",
+    },
     legend: {
       ...chartBaseConfig.legend,
-      align: 'right',
-      verticalAlign: 'top',
-      layout: 'vertical',
+      align: "right",
+      verticalAlign: "top",
+      layout: "vertical",
       x: 0,
-      y: 0
+      y: 0,
     },
     plotOptions: {
       pie: {
+        allowPointSelect: true,
+        cursor: "pointer",
         dataLabels: {
-          enabled: false,
-          distance: 20,
-          format: '{y}%',
-          style: {
-            color: 'white',
-            textOutline: false
-          }
+          enabled: true,
+          distance: -10,
+          format: "",
         },
-        startAngle: -90, // 圆环的开始角度
-        endAngle: 90,    // 圆环的结束角度
-        center: ['50%', '75%'],
-        showInLegend: true
-      }
+        showInLegend: true,
+      },
     },
     series: [
       {
-        type: 'pie',
-        size:'170%',
-        innerSize: '50%',
-        name: '占比',
-        // colorByPoint: true,
+        type: "pie",
+        innerSize: "80%",
+        name: "占比",
+        colorByPoint: true,
         borderRadius: 0,
-        data: []
-      }
+        data: data,
+        // data: [{
+        //   name: '1',
+        //   y: 60,
+        // }, {
+        //   name: '2',
+        //   y: 20
+        // }, {
+        //   name: '3',
+        //   y: 10
+        // }]
+      },
     ],
   };
   Highcharts.chart("chartBox2", options);

+ 3 - 3
src/views/system/AuthSet.vue

@@ -77,8 +77,8 @@ function handleSave(){
         </el-select>
       </div>
       <div>
-        <el-button type="primary" plain @click="$router.back()">取消</el-button>
-        <el-button type="primary" :disabled="loading" @click="handleSave">保存</el-button>
+        <el-button v-permission="'permission:cancel'" type="primary" plain @click="$router.back()">取消</el-button>
+        <el-button v-permission="'permission:save'" type="primary" :disabled="loading" @click="handleSave">保存</el-button>
       </div>
     </div>
     <div class="shadow-box content-box" v-loading="loading" element-loading-text="loading...">
@@ -88,7 +88,7 @@ function handleSave(){
         :props="{ label: 'Name', children: 'Children', disabled: 'Disabled' }"
         :default-expand-all="false"
         show-checkbox
-        node-key="MenuId"
+        node-key="SysMenuId"
         :default-checked-keys="defaultCheckedKeys"
       >
       </el-tree>

+ 3 - 3
src/views/system/RoleList.vue

@@ -74,7 +74,7 @@ async function handleDel(e) {
 <template>
   <div class="system-rolelist-page">
     <div class="shadow-box top-box">
-      <el-button type="primary" @click="handleEdit">添加角色</el-button>
+      <el-button type="primary" @click="handleEdit" v-permission="'role:add'">添加角色</el-button>
     </div>
     <div class="shadow-box content-box">
       <el-table
@@ -89,10 +89,10 @@ async function handleDel(e) {
         <el-table-column prop="SysRoleName" label="角色" />
         <el-table-column prop="opt" label="操作">
           <template #default="{ row }">
-            <el-button link type="primary" @click="handleEdit(row)"
+            <el-button v-permission="'role:add'" link type="primary" @click="handleEdit(row)"
               >编辑</el-button
             >
-            <el-button link type="danger" @click="handleDel(row)"
+            <el-button v-permission="'role:delete'" link type="danger" @click="handleDel(row)"
               >删除</el-button
             >
           </template>

+ 20 - 5
src/views/system/userList/Index.vue

@@ -119,7 +119,11 @@ async function changeStatus(e) {
     <DepartWrap v-model:departActive="departId" @change="refreshUserList" />
     <div class="user-wrap">
       <div class="user-top-box">
-        <el-button type="primary" :icon="Plus" @click="handleAddUser(null)"
+        <el-button
+          v-permission="'sysUser:add'"
+          type="primary"
+          :icon="Plus"
+          @click="handleAddUser(null)"
           >添加用户</el-button
         >
         <el-input
@@ -173,24 +177,35 @@ async function changeStatus(e) {
           </el-table-column>
           <el-table-column label="操作" width="260">
             <template #default="{ row }">
-              <el-button type="primary" link @click.stop="handleAddUser(row)"
+              <el-button
+                type="primary"
+                v-permission="'sysUser:edit'"
+                link
+                @click.stop="handleAddUser(row)"
                 >编辑</el-button
               >
               <el-button
+                v-permission="'sysUser:resetPassword'"
                 type="primary"
                 link
                 @click.stop="openResetPassDialog(row)"
                 >重置密码</el-button
               >
               <el-button
+                v-permission="'department:move'"
                 type="primary"
                 link
                 @click.stop="openMoveDepartDialog(row)"
                 >移动分组</el-button
               >
-              <el-button :type="row.IsEnabled ?'danger':'primary'" link @click.stop="changeStatus(row)">{{
-                row.IsEnabled ? "禁用" : "启用"
-              }}</el-button>
+              <el-button
+                v-permission="'sysUser:editEnabled'"
+                v-if="row.SysUserName !== 'admin'"
+                :type="row.IsEnabled ? 'danger' : 'primary'"
+                link
+                @click.stop="changeStatus(row)"
+                >{{ row.IsEnabled ? "禁用" : "启用" }}</el-button
+              >
               <!-- </div> -->
             </template>
           </el-table-column>

+ 11 - 5
src/views/system/userList/components/DepartWrap.vue

@@ -177,10 +177,10 @@ function handleDepartOpt(node, data, type) {
     ElMessageBox.confirm(
       '确定要删除该部门分组吗?',
       '提示'
-    ).then(()=>{
+    ).then(() => {
       handleDepartDel(data)
-    }).catch(()=>{})
-    
+    }).catch(() => { })
+
     return
   }
   if (type === 'add') {
@@ -233,6 +233,7 @@ function getFormTitle(e) {
       <span>部门管理</span>
     </div>
     <el-button
+      v-permission="'department:add'"
       type="primary"
       block
       :icon="Plus"
@@ -270,6 +271,7 @@ function getFormTitle(e) {
               <svg-icon
                 name="edit2"
                 size="16px"
+                v-if="hasPermission('department:edit')"
                 @click.stop="handleDepartOpt(node, data, 'edit')"
               ></svg-icon>
               <el-dropdown>
@@ -280,9 +282,13 @@ function getFormTitle(e) {
                 ></svg-icon>
                 <template #dropdown>
                   <el-dropdown-menu>
-                    <el-dropdown-item @click.stop="handleDepartOpt(node, data, 'del')">删除部门</el-dropdown-item>
                     <el-dropdown-item
-                      v-if="node.level < 3"
+                      v-if="hasPermission('department:delete')"
+                      @click.stop="handleDepartOpt(node, data, 'del')"
+                      >删除部门</el-dropdown-item
+                    >
+                    <el-dropdown-item
+                      v-if="node.level < 3&&hasPermission('department:add')"
                       @click.stop="handleDepartOpt(node, data, 'add')"
                       >添加部门分组</el-dropdown-item
                     >

+ 6 - 4
src/views/system/userList/components/EditUser.vue

@@ -188,9 +188,10 @@ async function handleSave() {
           v-model="formState.account"
           placeholder="建议使用邮箱前缀或者手机号码"
           clearable
+          :disabled="props.data.SysUserName==='admin'"
         />
       </el-form-item>
-      <el-form-item label="登录密码" prop="pwd" v-if="!props.data">
+      <el-form-item label="登录密码" prop="pwd" v-if="!props.data||props.data.SysUserName!=='admin'">
         <el-input
           v-model.trim="formState.pwd"
           placeholder="6-12位数字与字母的组合"
@@ -203,6 +204,7 @@ async function handleSave() {
           v-model="formState.name"
           placeholder="请输入用户名称"
           clearable
+          :disabled="props.data.SysUserName==='admin'"
         />
       </el-form-item>
       <el-form-item label="手机号码" prop="mobile" class="mobile-input-item">
@@ -227,7 +229,7 @@ async function handleSave() {
           style="width: 60%; margin-left: 5%"
         />
       </el-form-item>
-      <el-form-item label="所属部门" prop="depart"  >
+      <el-form-item label="所属部门" prop="depart" v-if="props.data.SysUserName!=='admin'">
         <el-cascader
           style="width: 100%"
           :options="departArr"
@@ -248,7 +250,7 @@ async function handleSave() {
         <el-input v-model="formState.email" placeholder="请输入邮箱" />
       </el-form-item>
       <el-form-item label="分配角色" prop="role">
-        <el-select v-model="formState.role" placeholder="分配角色">
+        <el-select v-model="formState.role" placeholder="分配角色" :disabled="props.data.SysUserName==='admin'">
           <el-option
             v-for="item in roleArr"
             :key="item.SysRoleId"
@@ -267,7 +269,7 @@ async function handleSave() {
           />
         </div>
       </el-form-item>
-      <el-form-item label="状态" prop="status">
+      <el-form-item label="状态" prop="status" v-if="props.data.SysUserName!=='admin'">
         <el-radio-group v-model="formState.status">
           <el-radio :value="true">启用</el-radio>
           <el-radio :value="false">禁用</el-radio>