Browse Source

忘记密码,工作台

Karsa 1 year ago
parent
commit
bbe5b6a141

+ 2 - 0
package.json

@@ -15,6 +15,8 @@
     "axios": "^1.6.7",
     "crypto-js": "^4.2.0",
     "element-plus": "2.4.4",
+    "highcharts": "11.2.0",
+    "moment": "^2.30.1",
     "pinia": "^2.1.7",
     "vue": "^3.4.19",
     "vue-router": "^4.3.0",

File diff suppressed because it is too large
+ 117 - 371
pnpm-lock.yaml


+ 81 - 0
src/common/js/util.js

@@ -0,0 +1,81 @@
+var SIGN_REGEXP = /([yMdhsm])(\1*)/g;
+var DEFAULT_PATTERN = 'yyyy-MM-dd';
+
+function padding(s, len) {
+    var len = len - (s + '').length;
+    for (var i = 0; i < len; i++) { s = '0' + s; }
+    return s;
+};
+
+export function filterMoney(value, num = 0) {
+    num = num > 0 && num <= 20 ? num : 0;
+    value = parseFloat((value + "").replace(/[^\d\.-]/g, ""))+ ""; //将金额转成比如 123.45的字符串
+    var valueArr = value.split(".")[0].split("").reverse() //将字符串的数变成数组
+    let valueString = "";
+    for (let i = 0; i < valueArr.length; i++) {
+        valueString += valueArr[i] + ((i + 1) % 3 == 0 && (i + 1) != valueArr.length ? "," : ""); //循环 取数值并在每三位加个','
+    }
+    const money = valueString.split("").reverse().join(""); //拼接上小数位
+    return money
+}
+
+export default {
+    getQueryStringByName: function (name) {
+        var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
+        var r = window.location.search.substr(1).match(reg);
+        var context = "";
+        if (r != null)
+            context = r[2];
+        reg = null;
+        r = null;
+        return context == null || context == "" || context == "undefined" ? "" : context;
+    },
+    //时间撮格式化
+    formatDatee(time){
+        let date = new Date(time), //时间戳为10位需*1000,时间戳为13位的话不需乘1000
+            Y = date.getFullYear() + '-',
+            M = (date.getMonth()+1 < 10 ? '0'+(date.getMonth()+1) : date.getMonth()+1) + '-',
+            D = date.getDate() < 10 ? '0'+ date.getDate() + ' ' : date.getDate() + ' ',
+            h = date.getHours() + ':',
+            m = date.getMinutes() < 10 ? '0'+ date.getMinutes() + ':' : date.getMinutes() + ':',
+            s = date.getSeconds() < 10 ? '0'+ date.getSeconds() : date.getSeconds();
+        return Y + M + D + h + m + s;
+    },
+    formatDate: {
+        format: function (date, pattern) {
+            pattern = pattern || DEFAULT_PATTERN;
+            return pattern.replace(SIGN_REGEXP, function ($0) {
+                switch ($0.charAt(0)) {
+                    case 'y': return padding(date.getFullYear(), $0.length);
+                    case 'M': return padding(date.getMonth() + 1, $0.length);
+                    case 'd': return padding(date.getDate(), $0.length);
+                    case 'w': return date.getDay() + 1;
+                    case 'h': return padding(date.getHours(), $0.length);
+                    case 'm': return padding(date.getMinutes(), $0.length);
+                    case 's': return padding(date.getSeconds(), $0.length);
+                }
+            });
+        },
+        parse: function (dateString, pattern) {
+            var matchs1 = pattern.match(SIGN_REGEXP);
+            var matchs2 = dateString.match(/(\d)+/g);
+            if (matchs1.length == matchs2.length) {
+                var _date = new Date(1970, 0, 1);
+                for (var i = 0; i < matchs1.length; i++) {
+                    var _int = parseInt(matchs2[i]);
+                    var sign = matchs1[i];
+                    switch (sign.charAt(0)) {
+                        case 'y': _date.setFullYear(_int); break;
+                        case 'M': _date.setMonth(_int - 1); break;
+                        case 'd': _date.setDate(_int); break;
+                        case 'h': _date.setHours(_int); break;
+                        case 'm': _date.setMinutes(_int); break;
+                        case 's': _date.setSeconds(_int); break;
+                    }
+                }
+                return _date;
+            }
+            return null;
+        }
+    },
+};

+ 0 - 2
src/hooks/login/use-login.js

@@ -87,11 +87,9 @@ export function useLoginSuccess(res) {
   setUserInfo(res.Data);
 
   getOtherRolePath('myCalendar').then(path => {
-    nextTick(() => {
       // path&&router.push({ path });
       //先跳到主面板
       path&&router.push({ path:'/dashboard' });
-    })
   });
   
 }

+ 1 - 1
src/styles/global.scss

@@ -144,7 +144,7 @@ iframe {
   color: #666 !important;
 }
 .el-form-item__label {
-  font-size: 16px;
+  font-size: 15px;
 }
 .el-collapse-item__header {
   border: none;

+ 145 - 3
src/views/Resetpassword.vue

@@ -1,10 +1,152 @@
 <script setup>
-import { ref } from 'vue'
+import { reactive, ref } from 'vue'
+import { checkPassWord } from '@/utils/commonOptions';
+import md5 from "@/utils/md5.js";
+import { modifyPwd } from "@/api/api.js";
+import { ElMessage } from 'element-plus'
+import { useRouter } from 'vue-router'
+
+const $router = useRouter()
+
+
+const addForm = reactive({
+  OldPwd: "",
+  NewPwd: "",
+  twoNewPwd: "",
+})
+const rules = {
+  OldPwd: [
+    {
+      required: true,
+      message: "请输入原密码",
+      trigger: "blur",
+    },
+  ],
+  NewPwd: [
+      {
+        validator:(rule,value,callback)=>{
+            if(!checkPassWord(value)){
+                callback(new Error('密码要求8位及以上,包含数字、大写字母、小写字母、特殊字符中的三个类型'))
+            }else{
+                callback()
+            }
+        }
+      },
+    {
+      required: true,
+      message: "请输入确认密码",
+      trigger: "blur",
+    },
+  ],
+  twoNewPwd: [
+    {
+      required: true,
+      message: "请输入确认密码",
+      trigger: "blur",
+    },
+  ],
+}
+
+const formRef = ref(null)
+async function addSubmit() {
+  await formRef.value.validate()
+
+  if (addForm.NewPwd != addForm.twoNewPwd) {
+    ElMessage.warning("新密码两次输入不一致,请核对!");
+    return false;
+  }
+
+  const res = await modifyPwd({
+    OldPwd: md5.hex_md5(addForm.OldPwd),
+    NewPwd: md5.hex_md5(addForm.NewPwd),
+  })
+  if (res.Ret == 200) {
+    ElMessage.success("修改密码成功,请重新登录!");
+    setTimeout(function () {
+      localStorage.setItem("auth", "");
+      localStorage.setItem("userName", "");
+      localStorage.setItem("Role", "");
+      localStorage.setItem("RoleType", "");
+      localStorage.setItem("AdminId", "");
+      localStorage.setItem("AdminName", "");
+      localStorage.setItem("RoleIdentity", "");
+      localStorage.setItem("ManageType", "");
+      $router.push({ path: "/login" });
+    }, 1000);
+  }
+}
+
+function historyBack() {
+  $router.go(-1)
+}
 
 </script>
 <template>
-  <div>修改密码</div>
+  <el-card class="box-card">
+    <template #header>
+      <div class="clearfix">
+        <b>修改密码</b>
+      </div>
+    </template>
+
+    <el-form
+      :model="addForm"
+      :rules="rules"
+      ref="formRef"
+      label-width="120px"
+      style="width: 500px"
+    >
+      <el-form-item label="原密码" prop="OldPwd">
+        <el-input
+          v-model="addForm.OldPwd"
+          type="password"
+          clearable
+          maxlength="20"
+          placeholder="请输入不超过20个字符"
+          show-password
+        />
+      </el-form-item>
+      <el-form-item label="新密码" prop="NewPwd">
+        <el-input
+          type="password"
+          v-model="addForm.NewPwd"
+          placeholder="请输入长度不超过20个字符"
+          maxlength="20"
+          show-password
+          clearable
+        >
+        </el-input>
+      </el-form-item>
+      <el-form-item label="确认新密码" prop="twoNewPwd">
+        <el-input
+          type="password"
+          v-model="addForm.twoNewPwd"
+          placeholder="请输入长度不超过20个字符"
+          maxlength="20"
+          show-password
+          clearable
+        >
+        </el-input>
+      </el-form-item>
+      
+      <el-form-item style="text-align: center;">
+        <el-button type="primary"  @click="addSubmit"
+          >确定</el-button
+        >
+        <el-button
+          type="primary"
+          plain
+          @click="historyBack"
+          >返回</el-button
+        >
+      </el-form-item>
+    </el-form>
+  </el-card>
 </template>
 <style scoped lang="scss">
-
+.box-card {
+    .el-form-item{
+      margin-bottom: 40px;
+    }
+}
 </style>

+ 28 - 0
src/views/dashboard_manage/components/chart.vue

@@ -0,0 +1,28 @@
+<script setup>
+import { watch,ref,nextTick } from 'vue'
+import Highcharts from 'highcharts';
+
+const props = defineProps({
+  id: {
+    type: String
+  },
+  options: {
+    type: Object
+  }
+})
+
+const chart = ref(null)
+function initChart() {
+  console.log('initchart')
+  nextTick(() => {
+    chart.value = Highcharts.chart(props.id,props.options) 
+  })
+}
+initChart()
+
+</script>
+<template>
+  <div :id="id" class="chart-wrapper"></div>
+</template>
+<style scoped lang="scss">
+</style>

+ 229 - 0
src/views/dashboard_manage/hooks/use-chart.js

@@ -0,0 +1,229 @@
+import { filterMoney } from '@/common/js/util';
+
+const baseOpt = {
+  title: {
+    text: ''
+  },
+  accessibility: {
+    enabled: false
+  },
+  chart: {
+    style: {
+      fontSize: 16
+    }
+  },
+  credits: {
+    enabled: false
+  },
+  legend: {
+    align: 'right',
+    verticalAlign: 'top'
+  },
+}
+
+export function useChart(type,data,key=null) {
+  const chartType = {
+    'bar': renderBar,
+    'line': renderLine,
+    'pie': renderPie
+  }
+
+  let options = chartType[type](data,key)
+
+  return options;
+}
+
+//柱形图
+const titleMap = {
+  'NewCompanyTotal': '新签客户',
+  'RenewalCompanyTotal': '续约客户',
+  'NotRenewalCompanyTotal': '未续约客户',
+  'CompanyTotal': '客户数',
+}
+function renderBar(data) {
+  const opt = {
+    colors: ['#5470c6', '#9a60b4', '#ea7ccc'],
+    yAxis: {
+      labels: {
+        style: {
+          color: '#999'
+        }
+      },
+      title: {
+        enabled: false
+      }
+    },
+    xAxis: {
+      categories: data.Date,
+      labels: {
+        style: {
+          color: '#999'
+        }
+      },
+      tickWidth: 1,
+      tickLength: 5,
+    },
+    series: [],
+    ...baseOpt
+  }
+  const filterKey = Object.keys(data).filter(key => !['Date','Title'].includes(key));
+
+  filterKey.forEach(key => {
+    let obj = {
+      data: data[key],
+      type: 'column',
+      name: titleMap[key],
+    };
+    opt.series.push(obj);
+  });
+
+
+  return opt
+}
+
+//折线图
+function renderLine(data) {
+  const opt = {
+    colors: ['#CD5C5C', '#25DEDB'],
+    plotOptions: {
+      series: {
+        marker: {
+          fillColor: '#FFFFFF',
+          lineWidth: 2,
+          lineColor: null // inherit from series
+        }
+      }
+    },
+    xAxis: {
+      categories: data.Date,
+      labels: {
+        style: {
+          color: '#999'
+        }
+      },
+      tickWidth: 1,
+      tickLength: 5,
+    },
+    yAxis: [
+      {
+        gridLineWidth: 0,
+        labels: {
+          style: {
+            color: '#999'
+          }
+        },
+        title: {
+          text: '合同数',
+          align:'high',
+          style: {
+            color: '#999'
+          },
+          y: -10,
+          offset: 0,
+          rotation: 0,
+        }
+      },
+      {
+        gridLineWidth: 0,
+        opposite: true,
+        labels: {
+          style: {
+            color: '#999'
+          }
+        },
+        title: {
+          text: '合同金额/元',
+          align:'high',
+          style: {
+            color: '#999'
+          },
+          y: -10,
+          offset: 0,
+          rotation: 0,
+        }
+      },
+    ],
+    series: [
+      {
+        data: [],
+        type: 'line',
+        name: '合同数',
+        yAxis: 0,
+      },
+      {
+        type: 'line',
+        name: '合同金额',
+        yAxis: 1,
+        data: [],
+      },
+    ],
+    ...baseOpt
+  }
+  
+  opt.series[0].data = data.ContractTotal;
+  opt.series[1].data = data.MoneyTotal;
+
+  return opt
+}
+
+//饼图
+const keyMap = {
+  'MoneyTotal': {
+    title: '有效合同金额',
+    color: '#FE4545'
+  },
+  'ContractTotal': {
+    title: '有效合同数',
+    color: '#FF7113'
+  },
+  'FormalCompanyCount': {
+    title: '正式客户数',
+    color: '#486CFF'
+  }
+}
+function renderPie(data,key) {
+  const opt = {
+    ...baseOpt,
+    chart: {
+      width: 200,
+      height: 200
+    },
+    yAxis: {
+      title: ''
+    },
+    title: {
+      floating:true,
+      y:80,
+      text: filterMoney(data[key]),
+      style: {
+        fontSize: 20
+      }
+    },
+    subtitle: {
+      floating:true,
+      y:100,
+      text: keyMap[key].title,
+      style: {
+        fontSize: 12
+      }
+    },
+    series: [{
+      type: 'pie',
+      name: '',
+      innerSize: '82%',
+      label: {
+        enabled: false
+      },
+      data: [{
+        name: keyMap[key].title,
+        color: keyMap[key].color,
+        y: data[key],
+        dataLabels: {
+          enabled: false
+        }
+      }]
+    }]
+  }
+
+  return opt
+}

+ 307 - 4
src/views/dashboard_manage/workBench.vue

@@ -1,9 +1,312 @@
 <script setup>
+import { ref,reactive,toRefs,computed } from 'vue'
+import { useRouter } from 'vue-router'
+import { dataMainInterface } from "@/api/api.js";
+import { useAppStore } from '@/store/modules/app'
+import { InfoFilled } from '@element-plus/icons-vue'
+import { useChart } from './hooks/use-chart'
+import Chart from './components/chart.vue'
 
+const $router = useRouter()
+
+const appStore = useAppStore();
+
+const dataAuth = computed(() => appStore.dataAuth)
+
+const showData = ref(false)
+
+const staticLabels = [
+  {
+    label: "正式客户数",
+  },
+  {
+    label: "试用客户数",
+  },
+  {
+    label: "新签客户数",
+  },
+  {
+    label: "续约客户数",
+  },
+  {
+    label: "未续约客户数",
+  },
+]
+
+const dataState = reactive({
+  dataInfo: {},
+  total_formal: 0, //正式客户数
+  total_trial: 0, //试用客户数
+  total_newsign: 0, //新签客户数
+  total_renew: 0, //续约客户数
+  total_notrenew: 0, //未续约客户数
+})
+ // 获取数据
+function getWorkdata() {
+  dataMainInterface.workdata().then((res) => {
+    if (res.Ret === 200) {
+      dataState.dataInfo = res.Data;
+
+      dataState.total_formal = res.Data.FormalCompanyCount;
+      dataState.total_trial = res.Data.TrialCompanyTotal;
+      dataState.total_newsign = res.Data.NewCompanyTotal;
+      dataState.total_renew = res.Data.RenewalCompanyTotal;
+      dataState.total_notrenew = res.Data.NotRenewalCompanyTotal;
+      showData.value = true;
+
+      getChartsOpts()
+    }
+  });
+}
+getWorkdata()
+
+// 获取图表option
+const optionState = reactive({
+  incremeOption: null,
+  expireOption: null,
+  incomeOption: null,
+  moneyOption: null,
+  contractOption: null,
+  formalCompanyOption: null,
+})
+function getChartsOpts() {
+  optionState.incremeOption = useChart('bar',dataState.dataInfo.IncrementalCompanyChartList);
+
+  optionState.expireOption = useChart('bar',dataState.dataInfo.WillExpireChartList); 
+
+  optionState.incomeOption = useChart('line',dataState.dataInfo.IncomeChartList);
+
+  optionState.moneyOption = useChart('pie',dataState.dataInfo.ContractData,'MoneyTotal');
+
+  optionState.contractOption = useChart('pie',dataState.dataInfo.ContractData,'ContractTotal');
+
+  optionState.formalCompanyOption = useChart('pie',dataState.dataInfo.ContractData,'FormalCompanyCount');
+}
+
+//顶部数据块点击跳转
+function handleTopGo(type) {
+  if (type === "正式客户数") {
+    $router.push({
+      path: "/customList",
+      query: {
+        act_status: "正式",
+      },
+    });
+  } else if (type === "试用客户数") {
+    $router.push({
+      path: "/customList",
+      query: {
+        act_status: "试用",
+      },
+    });
+  } else if (type === "新签客户数") {
+    $router.push({
+      path: "/stocklist",
+      query: {
+        type: "新签客户",
+      },
+    });
+  } else if (type === "续约客户数") {
+    $router.push({
+      path: "/stocklist",
+      query: {
+        type: "续约客户",
+      },
+    });
+  } else if (type === "未续约客户数") {
+    $router.push({
+      path: "/stocklist",
+      query: {
+        type: "未续约客户",
+      },
+    });
+  }
+}
+
+/* 页面跳转 */
+const pathMap = {
+  'incre': '/incrementalist',
+  'expire': '/expiringlist',
+  'income': '/incomelist',
+  'contract': '/contractlist',
+}
+function linkHandle(type) {
+  $router.push({
+    path: pathMap[type]
+  });
+}
+
+const { dataInfo,total_formal,total_trial,total_newsign,total_renew,total_notrenew } = toRefs(dataState)
 </script>
 <template>
-  <div>工作台</div>
-</template>
-<style scoped>
+  <div class="workbench-container" :loading="!showData">
+    <div class="workbench_top">
+      <h3>系统实时数据(每30分钟更新)</h3>
+      <el-row type="flex" justify="space-between" class="label-row">
+        <el-col v-for="item in staticLabels" :key="item.label" :span="4">
+          <el-card class="base-card" @click.native="handleTopGo(item.label)" shadow="hover">
+            <template #header>
+              <div class="clearfix">
+                <span>
+                  {{ item.label }}
+                  <el-tooltip
+                    v-if="item.label === '新签客户数' || item.label === '续约客户数' || item.label === '未续约客户数'"
+                    class="item"
+                    effect="dark"
+                    :content="
+                      item.label === '新签客户数'
+                        ? '当前状态为正式,且处于新签合同的存续期内的客户'
+                        : item.label === '续约客户数'
+                        ? '当前状态为正式,且处于续约合同的存续期内的客户'
+                        : '历史上有过正式转试用记录,且查询时间点为非正式、非永续状态的客户'
+                    "
+                    placement="top"
+                  >
+                    <i class="el-icon-info"></i>
+                  </el-tooltip>
+                </span>
+              </div>
+            </template>
+            <div class="card-cont">
+              {{ item.label === '正式客户数'
+                    ? total_formal
+                    : item.label === '试用客户数'
+                    ? total_trial
+                    : item.label === '新签客户数'
+                    ? total_newsign
+                    : item.label === '续约客户数'
+                    ? total_renew
+                    : item.label === '未续约客户数'
+                    ? total_notrenew
+                    : '' }}
+              <!-- <countTo
+                :duration="2000"
+                :startVal="0"
+                :endVal="
+                  item.label === '正式客户数'
+                    ? total_formal
+                    : item.label === '试用客户数'
+                    ? total_trial
+                    : item.label === '新签客户数'
+                    ? total_newsign
+                    : item.label === '续约客户数'
+                    ? total_renew
+                    : item.label === '未续约客户数'
+                    ? total_notrenew
+                    : ''
+                "
+              /> -->
+            </div>
+          </el-card>
+        </el-col>
+      </el-row>
+    </div>
+    <el-row type="flex" justify="space-between" class="label-row" style="margin-top: 40px"  v-if="showData">
+      <el-col :span="11">
+        <el-card shadow="never" class="chart-item">
+          <span class="editsty lookdtl" @click="linkHandle('incre')" v-if="dataAuth">查看数据详情>></span>
+          <h2 class="pie-title">
+            {{ dataInfo.IncrementalCompanyChartList.Title }}
+            <el-tooltip class="item" effect="dark" placement="top-start">
+              <template #content>
+                <div>
+                  新签客户:新签合同的起始日期包含在所选时间段内的客户 <br />续约客户:续约合同的起始日期包含在所选时间段内且不包含在新签合同存续期内的客户
+                </div>
+              </template>
+              <el-icon><InfoFilled /></el-icon>
+            </el-tooltip>
+          </h2>
+          <Chart id="barChart1" :options="optionState.incremeOption"/>
+        </el-card>
+      </el-col>
+      <el-col :span="11" :pull="1">
+        <el-card shadow="never" class="chart-item">
+          <span class="editsty lookdtl" @click="linkHandle('expire')" v-if="dataAuth">查看数据详情>></span>
+          <h2 class="pie-title">
+            {{ dataInfo.WillExpireChartList.Title }}
+            <el-tooltip class="item" effect="dark" content="合同截止日期在所选时间段内的客户" placement="top-start">
+              <el-icon><InfoFilled /></el-icon>
+            </el-tooltip>
+          </h2>
+          <Chart id="barChart2" :options="optionState.expireOption"/>
+        </el-card>
+      </el-col>
+      <el-col :span="11">
+        <el-card shadow="never" class="chart-item">
+          <span class="editsty lookdtl" @click="linkHandle('income')" v-if="dataAuth">查看数据详情>></span>
+          <h2 class="pie-title">
+            {{ dataInfo.IncomeChartList.Title }}
+            <el-tooltip class="item" effect="dark" placement="top-start">
+              <template #content>
+                <div>合同数:合同起始日期在所选月份区间内的合同数 <br />合同金额:合同金额的总和</div>
+              </template>
+              <el-icon><InfoFilled /></el-icon>
+            </el-tooltip>
+          </h2>
+          <Chart id="lineChart" :options="optionState.incomeOption"/>
+        </el-card>
+      </el-col>
+      <el-col :span="11" :pull="1">
+        <el-card shadow="never" class="chart-item">
+          <span class="editsty lookdtl" @click="linkHandle('contract')" v-if="dataAuth">查看数据详情>></span>
+          <h2 class="pie-title">
+            {{ dataInfo.ContractData.Title }}
+            <el-tooltip class="item" effect="dark" placement="top-start">
+              <template #content>
+                <div>有效合同数:当前未到期的合同总数量(合同截止日期≥所选日期) <br />合同总金额:当前未到期的合同金额总和 <br />正式客户数:当前处于有效合同执行期的客户数</div>
+              </template>
+              <el-icon><InfoFilled /></el-icon>
+            </el-tooltip>
+          </h2>
+          <div class="pie-cont">
+            <Chart id="pieChart1" :options="optionState.contractOption"/>
 
-</style>
+            <Chart id="pieChart2" :options="optionState.moneyOption"/>
+
+            <Chart id="pieChart3" :options="optionState.formalCompanyOption"/>
+          </div>
+        </el-card>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+<style scoped lang="scss">
+.workbench-container {
+  .label-row {
+    margin-top: 20px;
+    flex-wrap: wrap;
+    .base-card {
+      color: #999;
+      font-size: 16px;
+      cursor: pointer;
+      .card-cont {
+        font-size: 30px;
+        color: #333;
+      }
+    }
+    .chart-item {
+      min-height: 500px;
+      border: 1px solid #e6e6e6;
+      box-shadow: -2px 3px 6px rgba(172, 172, 172, 0.13);
+      margin-bottom: 40px;
+      .lookdtl {
+        display: block;
+        text-align: right;
+        margin-bottom: 15px;
+        &:hover {
+          text-decoration: underline;
+        }
+      }
+      .pie-cont {
+        display: flex;
+        justify-content: space-around;
+        margin-top: 60px;
+      }
+      .pie-title {
+        font-size: 19px;
+        color: #555;
+      }
+    }
+  }
+}
+</style>

Some files were not shown because too many files changed in this diff