Selaa lähdekoodia

feature/欢迎页修改&&计划页面二维码导入和展示功能

dy 1 vuosi sitten
vanhempi
commit
d357017c52

+ 1 - 1
.env.development

@@ -12,7 +12,7 @@ VITE_APP_UPLOAD_URL = 'http://192.168.101.4:9000'
 # 开发接口地址
 # VITE_APP_API_URL = 'http://192.168.101.4:8078'
 #VITE_APP_API_URL = 'http://192.168.101.188:8078'
- VITE_APP_API_URL = 'http://192.168.101.188:8078'  #lup
+ VITE_APP_API_URL = 'http://192.168.0.107:8078'  #lup
 #VITE_APP_API_URL = 'http://192.168.101.64:8078'  #hetao
 
 # 是否启用 Mock 服务

+ 1 - 1
.env.production

@@ -2,7 +2,7 @@
 NODE_ENV='production'
 
 # 上传文件接口地址
-VITE_APP_UPLOAD_URL = 'http://192.168.101.4:9000'
+VITE_APP_UPLOAD_URL = 'http://192.168.101.189:9000'
 # 代理前缀
 VITE_APP_BASE_API = '/mes-server'
 

+ 3 - 0
.vscode/settings.json

@@ -77,4 +77,7 @@
   "i18n-ally.localesPaths": [
     "src/lang"
   ],
+  "[vue]": {
+    "editor.defaultFormatter": "esbenp.prettier-vscode"
+  },
 }

+ 30 - 11
src/api/order/index.ts

@@ -28,15 +28,36 @@ export function getExpandAlias(tableType: String): AxiosPromise<any> {
     method: "get",
   });
 }
-
+/**
+ * 生成二维码数据
+ *
+ * @param userId
+ */
+export function getScanCode(tableType: String): AxiosPromise<any> {
+  return request({
+    url: "/api/v1/plan/order/scanCode/" + tableType,
+    method: "get",
+  });
+}
+/**
+ * 扫码导入提交
+ *
+ *
+ */
+export function scanImport(data: any): AxiosPromise<any> {
+  return request({
+    url: "/api/v1/plan/order/scanImport",
+    method: "post",
+    data,
+  });
+}
 /**
  * 添加订单
  *
  * @param data
  */
 export function addOrder(data: any) {
-  if(data.orderType==='2'){
-
+  if (data.orderType === "2") {
   }
   return request({
     url: "/api/v1/plan/order/add",
@@ -184,7 +205,7 @@ export function queryWorkOrderList(params: any) {
   return request({
     url: "/api/v1/plan/workOrder/list",
     method: "post",
-    data: params
+    data: params,
   });
 }
 
@@ -192,22 +213,20 @@ export function lockWorkOrder(params: any) {
   return request({
     url: "/api/v1/plan/workOrder/lockWorkOrder",
     method: "post",
-    data: params
+    data: params,
   });
 }
-export function prepareCheckInfo(
-    workOrderCode: string
-): AxiosPromise<any> {
+export function prepareCheckInfo(workOrderCode: string): AxiosPromise<any> {
   return request({
-    url: "/api/v1/plan/prepare/checkInfo/"+workOrderCode,
+    url: "/api/v1/plan/prepare/checkInfo/" + workOrderCode,
     method: "get",
   });
 }
 export function refreshPrepareCheckInfo(
-    workOrderCode: string
+  workOrderCode: string
 ): AxiosPromise<any> {
   return request({
-    url: "/api/v1/plan/prepare/refresh/"+workOrderCode,
+    url: "/api/v1/plan/prepare/refresh/" + workOrderCode,
     method: "get",
   });
 }

+ 5 - 1
src/layout/components/Sidebar/components/SidebarMixTopMenu.vue

@@ -40,7 +40,7 @@ import { RouteRecordRaw } from "vue-router";
 const appStore = useAppStore();
 const permissionStore = usePermissionStore();
 const router = useRouter();
-
+const route = useRoute();
 const activePath = computed(() => appStore.activeTopMenuPath);
 
 // 顶部菜单集合
@@ -79,5 +79,9 @@ onMounted(() => {
   mixTopMenus.value = permissionStore.routes.filter(
     (item) => !item.meta || !item.meta.hidden
   );
+  if (route.name == "Welcome") {
+    appStore.activeTopMenu("");
+    permissionStore.mixLeftMenus = [];
+  }
 });
 </script>

+ 265 - 61
src/views/plan/order/index.vue

@@ -1,11 +1,11 @@
 <template>
   <div class="mainContentBox">
     <avue-crud
-        ref="crudRef"
+      ref="crudRef"
       :option="option"
       v-model:page="page"
       v-model:search="search"
-        v-model="form"
+      v-model="form"
       :table-loading="loading"
       :permission="permission"
       @search-change="handleQuery"
@@ -17,6 +17,9 @@
       @row-del="rowDel"
       :data="pageData"
     >
+      <template #menu-left="{ size }">
+        <el-button type="primary" @click="scan">扫码导入</el-button>
+      </template>
       <template #menu-right="{}">
         <el-dropdown split-button v-hasPerm="['plan:order:import']"
           >导入
@@ -39,17 +42,21 @@
           <template #icon> <i-ep-download /> </template>导出
         </el-button>
       </template>
-      <template #menu="{size,row,index}">
+      <template #menu="{ size, row, index }">
         <el-button
-            v-hasPerm="[buttonPermission.PLAN.BTNS.order_edit]"
-            v-if="row.orderState === '0' ||  row.orderState === '1' ||  row.orderState === '2'"
-            type="primary"
-            link
-            size="small"
-            @click="handleEdit(row,0)"
-        ><i-ep-edit />编辑
+          v-hasPerm="[buttonPermission.PLAN.BTNS.order_edit]"
+          v-if="
+            row.orderState === '0' ||
+            row.orderState === '1' ||
+            row.orderState === '2'
+          "
+          type="primary"
+          link
+          size="small"
+          @click="handleEdit(row, 0)"
+          ><i-ep-edit />编辑
         </el-button>
-<!--        <el-button
+        <!--        <el-button
             v-if="row.nameplated === 1"
             type="info"
             link
@@ -58,23 +65,30 @@
         ><i-ep-edit />铭牌
         </el-button>-->
         <el-button
-            v-hasPerm="[buttonPermission.PLAN.BTNS.order_del]"
-            v-if="row.orderState === '0' ||  row.orderState === '1' ||  row.orderState === '2'"
-            type="danger"
-            link
-            size="small"
-            @click="rowDel(row,0)"
-        ><i-ep-edit />删除
+          v-hasPerm="[buttonPermission.PLAN.BTNS.order_del]"
+          v-if="
+            row.orderState === '0' ||
+            row.orderState === '1' ||
+            row.orderState === '2'
+          "
+          type="danger"
+          link
+          size="small"
+          @click="rowDel(row, 0)"
+          ><i-ep-edit />删除
+        </el-button>
+        <el-button type="primary" size="small" @click="handleScanCode(row.id)"
+          >二维码生成
         </el-button>
       </template>
     </avue-crud>
     <el-dialog
-        v-model="dialog1.visible"
-        :title="dialog1.title"
-        width="950px"
-        @close="dialog1.visible = false"
+      v-model="dialog1.visible"
+      :title="dialog1.title"
+      width="950px"
+      @close="dialog1.visible = false"
     >
-      <choice-item-page @materialInfo="materialInfo"/>
+      <choice-item-page @material-info="materialInfo" />
     </el-dialog>
     <el-dialog
       v-model="dialog.visible"
@@ -120,6 +134,71 @@
         </div>
       </template>
     </el-dialog>
+    <el-dialog
+      v-model="dialog2.visible"
+      :title="dialog2.title"
+      width="950px"
+      @close="
+        dialog2.visible = false;
+        scanArray = [];
+        scanCpArray = [];
+      "
+    >
+      <el-input
+        @keyup.enter="scanFnc"
+        v-model="scanCode"
+        style="padding: 20px"
+        placeholder="请扫码"
+      />
+
+      <div class="scanInfo">
+        <el-scrollbar class="scrollbar">
+          <div class="item" v-for="(item, index) in scanArray" :key="index">
+            <div class="text">扫码Cp: {{ item.cp }}</div>
+            <div @click="deleteItem(index)" class="text">
+              <el-icon><Delete /></el-icon>
+            </div>
+          </div>
+          <div v-if="scanArray.length < 1">暂无数据</div>
+        </el-scrollbar>
+        <div class="bottombtn">
+          <el-button
+            type="primary"
+            :disabled="scanArray.length < 1"
+            @click="scanSubmit"
+            >生 成</el-button
+          >
+        </div>
+
+        <!-- <vue-qrcode value="1241421gaga" size="45" error-level="H" /> -->
+      </div>
+    </el-dialog>
+    <el-dialog
+      v-model="dialog3.visible"
+      :title="dialog3.title"
+      width="950px"
+      @close="
+        dialog3.visible = false;
+        scanCodeArray = [];
+      "
+    >
+      <div class="scanInfo">
+        <div style="padding: 0 20px; text-align: center">
+          二维码数量:{{ scanCodeArray.length }}
+        </div>
+        <el-scrollbar class="scrollbar">
+          <div
+            class="scancodeitem"
+            v-for="(item, index) in scanCodeArray"
+            :key="index"
+          >
+            <vue-qrcode :value="item" size="45" error-level="H" />
+          </div>
+
+          <div v-if="scanCodeArray.length < 1">暂无数据</div>
+        </el-scrollbar>
+      </div>
+    </el-dialog>
     <CommonTable
       ref="ctableRef"
       tableTitle="报故单"
@@ -143,8 +222,11 @@ import {
   importOrder,
   updateOrder,
   getExpandAlias,
+  scanImport,
+  getScanCode,
 } from "@/api/order";
-import {ref} from "vue";
+import { ref } from "vue";
+import _ from "lodash-es";
 import ChoiceItemPage from "@/views/base/materials/components/choice-item-page.vue";
 
 // 弹窗对象
@@ -168,15 +250,23 @@ const importData = reactive({
   fileList: [],
 });
 const materialInfo = (value) => {
-  form.value.materialCode = value.materialCode
-  form.value.materialName = value.materialName
-  form.value.materialModel = value.spec
-  dialog1.visible = false
-}
+  form.value.materialCode = value.materialCode;
+  form.value.materialName = value.materialName;
+  form.value.materialModel = value.spec;
+  dialog1.visible = false;
+};
 const dialog1 = reactive({
   title: "物料选择",
   visible: false,
 });
+const dialog2 = reactive({
+  title: "扫码导入",
+  visible: false,
+});
+const dialog3 = reactive({
+  title: "二维码详情",
+  visible: false,
+});
 const permission = reactive({
   delBtn: checkPerm(buttonPermission.PLAN.BTNS.order_del),
   addBtn: checkPerm(buttonPermission.PLAN.BTNS.order_add),
@@ -241,7 +331,7 @@ option.value = {
       type: "select", //类型为下拉选择框
       width: 100,
       overHidden: true,
-      editDisabled:true,
+      editDisabled: true,
       dicUrl: dictDataUtil.request_url + dictDataUtil.TYPE_CODE.plan_order_type,
       props: {
         label: "dictLabel",
@@ -257,15 +347,15 @@ option.value = {
         },
       ],
       change: ({ value, column }) => {
-        if(value==='2'){
-          option.value.column[4].display=true;
-          option.value.column[6].disabled=true;
-          option.value.column[10].disabled=true;
+        if (value === "2") {
+          option.value.column[4].display = true;
+          option.value.column[6].disabled = true;
+          option.value.column[10].disabled = true;
         }
-        if(value==='1'){
-          option.value.column[4].display=false;
-          option.value.column[6].disabled=false;
-          option.value.column[10].disabled=false;
+        if (value === "1") {
+          option.value.column[4].display = false;
+          option.value.column[6].disabled = false;
+          option.value.column[10].disabled = false;
         }
       },
     },
@@ -273,21 +363,19 @@ option.value = {
       label: "报故单",
       prop: "trackingNumber",
       width: 100,
-      display:false,
-      click:({ value, column }) => {
+      display: false,
+      click: ({ value, column }) => {
         if (ctableRef.value) {
           ctableRef.value.startSelect();
         }
       },
-
     },
     {
       label: "报故单id",
       prop: "faultId",
       width: 100,
-      display:false,
-      hide:true,
-
+      display: false,
+      hide: true,
     },
     {
       label: "产品编码",
@@ -303,8 +391,8 @@ option.value = {
         },
       ],
       click: ({ value, column }) => {
-        if(column.boxType){
-          dialog1.visible = true
+        if (column.boxType) {
+          dialog1.visible = true;
         }
       },
     },
@@ -347,7 +435,6 @@ option.value = {
       filterable: true, //添加filterable属性即可启用搜索功能
     },
 
-
     {
       label: "订单数量",
       prop: "orderNum",
@@ -409,7 +496,7 @@ option.value = {
         },
       ],
     },
-   /* {
+    /* {
       label: "所属公司",
       prop: "companyId",
       width: 100,
@@ -478,7 +565,84 @@ option.value = {
     },
   ],
 };
+function useScanCode() {
+  const scanCodeArray = ref([]);
+  const handleScanCode = async (id: string) => {
+    const { data, code } = await getScanCode(id);
+    let data1 = [];
+    data1.push(data[0]);
+    data1.push(data[0]);
+    scanCodeArray.value = data1;
+    dialog3.visible = true;
+  };
+  return { scanCodeArray, handleScanCode };
+}
+const { scanCodeArray, handleScanCode } = useScanCode();
+//扫码板块
 
+function useScan() {
+  const scanCode = ref("");
+  const scanArray = ref([]);
+  const scanCpArray = ref([]);
+  const scanStatus = ref(false);
+  const deleteItem = (index: any) => {
+    scanArray.value.splice(index, 1);
+  };
+  const scan = () => {
+    dialog2.visible = true;
+  };
+  const scanFnc = () => {
+    if (scanCode.value) {
+      // scanCode.value =
+      //   '{"id":"453b5261e07745f2b7f4a042171f0644","tp":"10","cp":"1", "data":"PD94bWwgdmVyc2lvbj0iMS4wliBlbmNvZGluZz0iROlyMzEylj8+PFI+PERBIEIEPSJhc2QyMzRhZGYzNDJkZGZhc3NzliBOcDOiNSlgY3A9liEiPjxsaXN0IE5hPSJCT00iPjxCT00gdHk9lkkiPjxBPuS6p+WTgeWei+WPtzQT48Qj7lm77li7c8L0|+PEM+5qOA6agM5L6d5o2uPC9DPjxUSj"}';
+      let obj = {};
+      obj = JSON.parse(scanCode.value);
+      if (!_.includes(scanCpArray.value, obj.cp)) {
+        scanArray.value.push(obj);
+        scanCpArray.value.push(obj.cp);
+        ElMessage.success("扫码成功");
+      } else {
+        ElMessage.error("请勿重复扫码");
+      }
+      //判定是否重复扫码
+    } else {
+      ElMessage.error("请正确扫码");
+    }
+    scanCode.value = "";
+  };
+  const scanSubmit = async () => {
+    const { data, code } = await scanImport({
+      planOrderImportList: scanArray.value,
+    });
+    if (code == "200") {
+      ElMessage.success("导入成功!");
+      dialog2.visible = false;
+      scanArray.value = [];
+      scanCpArray.value = [];
+      handleQuery(null, null);
+    }
+  };
+  return {
+    scanCode,
+    scanArray,
+    scanCpArray,
+    scanStatus,
+    scan,
+    scanFnc,
+    deleteItem,
+    scanSubmit,
+  };
+}
+const {
+  scanCode,
+  scanArray,
+  scanCpArray,
+  scanStatus,
+  scanSubmit,
+  scan,
+  scanFnc,
+  deleteItem,
+} = useScan();
 const queryExpandAlias = () => {
   getExpandAlias(dictDataUtil.EXPAND_FIELD_TABLE.plan_order_info).then(
     (data: any) => {
@@ -541,9 +705,9 @@ const rowUpdate = (form: any, index: any, done: any, loading: any) => {
     handleQuery(null, null);
   });
 };
-const handleNameplated = (row) =>{
-  ElMessage.warning("功能开发中")
-}
+const handleNameplated = (row) => {
+  ElMessage.warning("功能开发中");
+};
 const rowDel = (form: any, index) => {
   ElMessageBox.confirm("当前操作会删除数据,你确认要继续吗?")
     .then(() => {
@@ -582,9 +746,9 @@ const downloadTemplate = () => {
     downFile(response);
   });
 };
-const handleEdit = (row: any, index: any) =>{
+const handleEdit = (row: any, index: any) => {
   crudRef.value && crudRef.value.rowEdit(row, index);
-}
+};
 /** 弹窗提交 */
 const handleSubmit = () => {
   importOrder(importData).then((data: any) => {
@@ -638,12 +802,52 @@ onMounted?.(() => {
 });
 
 /** 选择报故单 */
-const onSelectedFinish=(selectedValue)=>{
-  form.value.materialCode=selectedValue.materialCode;
-  form.value.materialName=selectedValue.materialName;
-  form.value.materialModel=selectedValue.spec;
-  form.value.faultId=selectedValue.id;
-  form.value.trackingNumber=selectedValue.trackingNumber;
-  form.value.orderNum=selectedValue.unqualifiedNum;
-}
+const onSelectedFinish = (selectedValue) => {
+  form.value.materialCode = selectedValue.materialCode;
+  form.value.materialName = selectedValue.materialName;
+  form.value.materialModel = selectedValue.spec;
+  form.value.faultId = selectedValue.id;
+  form.value.trackingNumber = selectedValue.trackingNumber;
+  form.value.orderNum = selectedValue.unqualifiedNum;
+};
 </script>
+
+<style lang="scss" scoped>
+.scanInfo {
+  width: 100%;
+
+  .scrollbar {
+    padding: 20px;
+    height: 350px;
+    .item {
+      margin: 20px 0;
+      width: 100%;
+      background-color: #80808030;
+      border-radius: 16px;
+      height: 60px;
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      padding: 0 20px;
+      .el-icon {
+        width: 50px;
+        height: 50px;
+        color: red;
+        cursor: pointer;
+      }
+    }
+    .scancodeitem {
+      width: 100%;
+      margin: 10px 0;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+    }
+  }
+  .bottombtn {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+  }
+}
+</style>

+ 202 - 0
src/views/welcome/components/BarChart.vue

@@ -0,0 +1,202 @@
+<!--  线 + 柱混合图 -->
+<template>
+  <el-card>
+    <template #header>
+      <div class="title">
+        产量柱状图
+        <el-tooltip effect="dark" content="点击试试下载" placement="bottom">
+          <i-ep-download class="download" @click="downloadEchart" />
+        </el-tooltip>
+      </div>
+    </template>
+
+    <div :id="id" :class="className" :style="{ height, width }"></div>
+  </el-card>
+</template>
+
+<script setup lang="ts">
+import * as echarts from "echarts";
+
+const props = defineProps({
+  id: {
+    type: String,
+    default: "barChart",
+  },
+  className: {
+    type: String,
+    default: "",
+  },
+  width: {
+    type: String,
+    default: "200px",
+    required: true,
+  },
+  height: {
+    type: String,
+    default: "200px",
+    required: true,
+  },
+});
+
+const options = {
+  grid: {
+    left: "2%",
+    right: "2%",
+    bottom: "10%",
+    containLabel: true,
+  },
+  tooltip: {
+    trigger: "axis",
+    axisPointer: {
+      type: "cross",
+      crossStyle: {
+        color: "#999",
+      },
+    },
+  },
+  legend: {
+    x: "center",
+    y: "bottom",
+    data: ["收入", "毛利润", "收入增长率", "利润增长率"],
+    textStyle: {
+      color: "#999",
+    },
+  },
+  xAxis: [
+    {
+      type: "category",
+      data: ["1月", "2月", "3月", "4月", "5月","6月", "7月","8月", "9月","10月", "11月","12月"],
+      axisPointer: {
+        type: "shadow",
+      },
+    },
+  ],
+  yAxis: [
+    {
+      type: "value",
+      min: 0,
+      max: 10000,
+      interval: 2000,
+      axisLabel: {
+        formatter: "{value} ",
+      },
+    },
+    {
+      type: "value",
+      min: 0,
+      max: 100,
+      interval: 20,
+      axisLabel: {
+        formatter: "{value}%",
+      },
+    },
+  ],
+  series: [
+    {
+      name: "收入",
+      type: "bar",
+      data: [5000, 7100, 7200, 7300, 6000,7800, 3500, 7000, 7000, 7500,7500, 7100],
+      barWidth: 20,
+      itemStyle: {
+        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+          { offset: 0, color: "#83bff6" },
+          { offset: 0.5, color: "#188df0" },
+          { offset: 1, color: "#188df0" },
+        ]),
+      },
+    },
+    {
+      name: "毛利润",
+      type: "bar",
+      data: [5100, 7200, 7300, 7800, 6100,7100, 3200, 7100, 7200, 7100,7200, 7200],
+      barWidth: 20,
+      itemStyle: {
+        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+          { offset: 0, color: "#25d73c" },
+          { offset: 0.5, color: "#1bc23d" },
+          { offset: 1, color: "#179e61" },
+        ]),
+      },
+    },
+    {
+      name: "收入增长率",
+      type: "line",
+      yAxisIndex: 1,
+      data: [60, 65, 70, 75, 80,60, 65, 70, 75, 80,23,23],
+      itemStyle: {
+        color: "#67C23A",
+      },
+    },
+    {
+      name: "利润增长率",
+      type: "line",
+      yAxisIndex: 1,
+      data: [70, 75, 80, 85, 90,65, 70, 75, 80,60, 65, 70],
+      itemStyle: {
+        color: "#409EFF",
+      },
+    },
+  ],
+};
+
+const downloadEchart = () => {
+  // 获取画布图表地址信息
+  const img = new Image();
+  img.src = chart.value.getDataURL({
+    type: "png",
+    pixelRatio: 1,
+    backgroundColor: "#fff",
+  });
+  // 当图片加载完成后,生成 URL 并下载
+  img.onload = () => {
+    const canvas = document.createElement("canvas");
+    canvas.width = img.width;
+    canvas.height = img.height;
+    const ctx = canvas.getContext("2d");
+    if (ctx) {
+      ctx.drawImage(img, 0, 0, img.width, img.height);
+      const link = document.createElement("a");
+      link.download = `业绩柱状图.png`;
+      link.href = canvas.toDataURL("image/png", 0.9);
+      document.body.appendChild(link);
+      link.click();
+      link.remove();
+    }
+  };
+};
+
+const chart = ref<any>("");
+onMounted(() => {
+  // 图表初始化
+  chart.value = markRaw(
+    echarts.init(document.getElementById(props.id) as HTMLDivElement)
+  );
+
+  chart.value.setOption(options);
+
+  // 大小自适应
+  window.addEventListener("resize", () => {
+    chart.value.resize();
+  });
+});
+
+onActivated(() => {
+  if (chart.value) {
+    chart.value.resize();
+  }
+});
+</script>
+<style lang="scss" scoped>
+.title {
+  display: flex;
+  justify-content: space-between;
+
+  .download {
+    cursor: pointer;
+
+    &:hover {
+      color: #409eff;
+    }
+  }
+}
+</style>

+ 115 - 0
src/views/welcome/components/FunnelChart.vue

@@ -0,0 +1,115 @@
+<!-- 漏斗图 -->
+<template>
+  <div :id="id" :class="className" :style="{ height, width }"></div>
+</template>
+
+<script setup lang="ts">
+import * as echarts from "echarts";
+
+const props = defineProps({
+  id: {
+    type: String,
+    default: "funnelChart",
+  },
+  className: {
+    type: String,
+    default: "",
+  },
+  width: {
+    type: String,
+    default: "200px",
+    required: true,
+  },
+  height: {
+    type: String,
+    default: "200px",
+    required: true,
+  },
+});
+
+const options = {
+  title: {
+    show: true,
+    text: "订单线索转化漏斗图",
+    x: "center",
+    padding: 15,
+    textStyle: {
+      fontSize: 18,
+      fontStyle: "normal",
+      fontWeight: "bold",
+      color: "#337ecc",
+    },
+  },
+  grid: {
+    left: "2%",
+    right: "2%",
+    bottom: "10%",
+    containLabel: true,
+  },
+  legend: {
+    x: "center",
+    y: "bottom",
+    data: ["Show", "Click", "Visit", "Inquiry", "Order"],
+  },
+
+  series: [
+    {
+      name: "Funnel",
+      type: "funnel",
+      left: "20%",
+      top: 60,
+      bottom: 60,
+      width: "60%",
+      sort: "descending",
+      gap: 2,
+      label: {
+        show: true,
+        position: "inside",
+      },
+      labelLine: {
+        length: 10,
+        lineStyle: {
+          width: 1,
+          type: "solid",
+        },
+      },
+      itemStyle: {
+        borderColor: "#fff",
+        borderWidth: 1,
+      },
+      emphasis: {
+        label: {
+          fontSize: 20,
+        },
+      },
+      data: [
+        { value: 60, name: "Visit" },
+        { value: 40, name: "Inquiry" },
+        { value: 20, name: "Order" },
+        { value: 80, name: "Click" },
+        { value: 100, name: "Show" },
+      ],
+    },
+  ],
+};
+
+const chart = ref<any>("");
+
+onMounted(() => {
+  chart.value = markRaw(
+    echarts.init(document.getElementById(props.id) as HTMLDivElement)
+  );
+
+  chart.value.setOption(options);
+
+  window.addEventListener("resize", () => {
+    chart.value.resize();
+  });
+});
+
+onActivated(() => {
+  if (chart.value) {
+    chart.value.resize();
+  }
+});
+</script>

+ 89 - 0
src/views/welcome/components/PieChart.vue

@@ -0,0 +1,89 @@
+<!-- 饼图 -->
+<template>
+  <el-card>
+    <template #header> 产品分类饼图 </template>
+    <div :id="id" :class="className" :style="{ height, width }"></div>
+  </el-card>
+</template>
+
+<script setup lang="ts">
+import * as echarts from "echarts";
+
+const props = defineProps({
+  id: {
+    type: String,
+    default: "pieChart",
+  },
+  className: {
+    type: String,
+    default: "",
+  },
+  width: {
+    type: String,
+    default: "200px",
+    required: true,
+  },
+  height: {
+    type: String,
+    default: "200px",
+    required: true,
+  },
+});
+const options = {
+  grid: {
+    left: "2%",
+    right: "2%",
+    bottom: "10%",
+    containLabel: true,
+  },
+  legend: {
+    top: "bottom",
+    textStyle: {
+      color: "#999",
+    },
+  },
+  series: [
+    {
+      name: "Nightingale Chart",
+      type: "pie",
+      radius: [50, 130],
+      center: ["50%", "50%"],
+      roseType: "area",
+      itemStyle: {
+        borderRadius: 1,
+        color: function (params: any) {
+          //自定义颜色
+          const colorList = ["#409EFF", "#67C23A", "#E6A23C", "#F56C6C"];
+          return colorList[params.dataIndex];
+        },
+      },
+      data: [
+        { value: 26, name: "家用电器" },
+        { value: 27, name: "户外运动" },
+        { value: 24, name: "汽车用品" },
+        { value: 23, name: "手机数码" },
+      ],
+    },
+  ],
+};
+
+const chart = ref<any>("");
+
+onMounted(() => {
+  chart.value = markRaw(
+    echarts.init(document.getElementById(props.id) as HTMLDivElement)
+  );
+
+  chart.value.setOption(options);
+
+  window.addEventListener("resize", () => {
+    chart.value.resize();
+  });
+});
+
+onActivated(() => {
+  if (chart.value) {
+    chart.value.resize();
+  }
+});
+</script>

+ 109 - 0
src/views/welcome/components/RadarChart.vue

@@ -0,0 +1,109 @@
+<!-- 雷达图 -->
+<template>
+  <el-card>
+    <template #header> 订单状态雷达图 </template>
+    <div :id="id" :class="className" :style="{ height, width }"></div>
+  </el-card>
+</template>
+
+<script setup lang="ts">
+import * as echarts from "echarts";
+
+const props = defineProps({
+  id: {
+    type: String,
+    default: "radarChart",
+  },
+  className: {
+    type: String,
+    default: "",
+  },
+  width: {
+    type: String,
+    default: "200px",
+    required: true,
+  },
+  height: {
+    type: String,
+    default: "200px",
+    required: true,
+  },
+});
+
+const options = {
+  grid: {
+    left: "2%",
+    right: "2%",
+    bottom: "10%",
+    containLabel: true,
+  },
+  legend: {
+    x: "center",
+    y: "bottom",
+    data: ["预定数量", "下单数量", "发货数量"],
+    textStyle: {
+      color: "#999",
+    },
+  },
+  radar: {
+    // shape: 'circle',
+    radius: "60%",
+    indicator: [
+      { name: "家用电器" },
+      { name: "服装箱包" },
+      { name: "运动户外" },
+      { name: "手机数码" },
+      { name: "汽车用品" },
+      { name: "家具厨具" },
+    ],
+  },
+  series: [
+    {
+      name: "Budget vs spending",
+      type: "radar",
+      itemStyle: {
+        borderRadius: 6,
+        color: function (params: any) {
+          //自定义颜色
+          const colorList = ["#409EFF", "#67C23A", "#E6A23C", "#F56C6C"];
+          return colorList[params.dataIndex];
+        },
+      },
+      data: [
+        {
+          value: [400, 400, 400, 400, 400, 400],
+          name: "预定数量",
+        },
+        {
+          value: [300, 300, 300, 300, 300, 300],
+          name: "下单数量",
+        },
+        {
+          value: [200, 200, 200, 200, 200, 200],
+          name: "发货数量",
+        },
+      ],
+    },
+  ],
+};
+
+const chart = ref<any>("");
+
+onMounted(() => {
+  chart.value = markRaw(
+    echarts.init(document.getElementById(props.id) as HTMLDivElement)
+  );
+
+  chart.value.setOption(options);
+
+  window.addEventListener("resize", () => {
+    chart.value.resize();
+  });
+});
+
+onActivated(() => {
+  if (chart.value) {
+    chart.value.resize();
+  }
+});
+</script>

+ 283 - 3
src/views/welcome/index.vue

@@ -1,11 +1,291 @@
 <template>
-  <div>src/views/welcome /index.vue</div>
+  <div class="dashboard-container">
+    <!-- github角标 -->
+    <!--    <github-corner class="github-corner" />-->
+
+    <el-card shadow="never">
+      <el-row justify="space-between">
+        <el-col :span="18" :xs="24">
+          <div class="flex h-full items-center">
+            <img
+              class="w-20 h-20 mr-5 rounded-full"
+              :src="userStore.user.avatar + '?imageView2/1/w/80/h/80'"
+            />
+            <div>
+              <p>{{ greetings }}</p>
+              <p class="text-sm text-gray">
+                今日天气晴朗,气温在15℃至25℃之间,东南风。
+              </p>
+            </div>
+          </div>
+        </el-col>
+
+        <el-col :span="6" :xs="24">
+          <div class="flex h-full items-center justify-around">
+            <el-statistic :value="99">
+              <template #title>
+                <div class="flex items-center">
+                  <svg-icon icon-class="message" size="20px" />
+                  <span class="text-[16px] ml-1">消息</span>
+                </div>
+              </template>
+            </el-statistic>
+
+            <el-statistic :value="50">
+              <template #title>
+                <div class="flex items-center">
+                  <svg-icon icon-class="todolist" size="20px" />
+                  <span class="text-[16px] ml-1">待办</span>
+                </div>
+              </template>
+              <template #suffix>/100</template>
+            </el-statistic>
+
+            <el-statistic :value="10">
+              <template #title>
+                <div class="flex items-center">
+                  <svg-icon icon-class="project" size="20px" />
+                  <span class="text-[16px] ml-1">项目</span>
+                </div>
+              </template>
+            </el-statistic>
+          </div>
+        </el-col>
+      </el-row>
+    </el-card>
+
+    <!-- 数据卡片 -->
+    <el-row :gutter="10" class="mt-3">
+      <el-col :xs="24" :sm="12" :lg="6">
+        <el-card shadow="never">
+          <template #header>
+            <div class="flex items-center justify-between">
+              <span class="text-[var(--el-text-color-secondary)]">访客数</span>
+              <el-tag type="success">日</el-tag>
+            </div>
+          </template>
+
+          <div class="flex items-center justify-between mt-5">
+            <div class="text-lg text-right">
+              {{ Math.round(visitCountOutput) }}
+            </div>
+            <svg-icon icon-class="visit" size="2em" />
+          </div>
+
+          <div
+            class="flex items-center justify-between mt-5 text-sm text-[var(--el-text-color-secondary)]"
+          >
+            <span> 总访客数 </span>
+            <span> {{ Math.round(visitCountOutput * 15) }} </span>
+          </div>
+        </el-card>
+      </el-col>
+
+      <!--消息数-->
+      <el-col :xs="24" :sm="12" :lg="6">
+        <el-card shadow="never">
+          <template #header>
+            <div class="flex items-center justify-between">
+              <span class="text-[var(--el-text-color-secondary)]">IP数</span>
+              <el-tag type="success">日</el-tag>
+            </div>
+          </template>
+
+          <div class="flex items-center justify-between mt-5">
+            <div class="text-lg text-right">
+              {{ Math.round(dauCountOutput) }}
+            </div>
+            <svg-icon icon-class="ip" size="2em" />
+          </div>
+
+          <div
+            class="flex items-center justify-between mt-5 text-sm text-[var(--el-text-color-secondary)]"
+          >
+            <span> 总IP数 </span>
+            <span> {{ Math.round(dauCountOutput) }} </span>
+          </div>
+        </el-card>
+      </el-col>
+
+      <!--销售额-->
+      <el-col :xs="24" :sm="12" :lg="6">
+        <el-card shadow="never">
+          <template #header>
+            <div class="flex items-center justify-between">
+              <span class="text-[var(--el-text-color-secondary)]">产品数</span>
+              <el-tag>月</el-tag>
+            </div>
+          </template>
+
+          <div class="flex items-center justify-between mt-5">
+            <div class="text-lg text-right">
+              {{ Math.round(amountOutput) }}
+            </div>
+            <svg-icon icon-class="money" size="2em" />
+          </div>
+
+          <div
+            class="flex items-center justify-between mt-5 text-sm text-[var(--el-text-color-secondary)]"
+          >
+            <span> 总产品数 </span>
+            <span> {{ Math.round(amountOutput * 15) }} </span>
+          </div>
+        </el-card>
+      </el-col>
+
+      <!--订单量-->
+      <el-col :xs="24" :sm="12" :lg="6">
+        <el-card shadow="never">
+          <template #header>
+            <div class="flex items-center justify-between">
+              <span class="text-[var(--el-text-color-secondary)]">订单量</span>
+              <el-tag type="danger">季</el-tag>
+            </div>
+          </template>
+
+          <div class="flex items-center justify-between mt-5">
+            <div class="text-lg text-right">
+              {{ Math.round(orderCountOutput) }}
+            </div>
+            <svg-icon icon-class="order" size="2em" />
+          </div>
+
+          <div
+            class="flex items-center justify-between mt-5 text-sm text-[var(--el-text-color-secondary)]"
+          >
+            <span> 总订单量 </span>
+            <span> {{ Math.round(orderCountOutput * 15) }} </span>
+          </div>
+        </el-card>
+      </el-col>
+    </el-row>
+
+    <!-- Echarts 图表 -->
+    <el-row :gutter="10" class="mt-3">
+      <el-col :sm="24" :lg="8" class="mb-2">
+        <BarChart
+          id="barChart"
+          height="400px"
+          width="100%"
+          class="bg-[var(--el-bg-color-overlay)]"
+        />
+      </el-col>
+
+      <el-col :xs="24" :sm="12" :lg="8" class="mb-2">
+        <PieChart
+          id="pieChart"
+          height="400px"
+          width="100%"
+          class="bg-[var(--el-bg-color-overlay)]"
+        />
+      </el-col>
+
+      <el-col :xs="24" :sm="12" :lg="8" class="mb-2">
+        <RadarChart
+          id="radarChart"
+          height="400px"
+          width="100%"
+          class="bg-[var(--el-bg-color-overlay)]"
+        />
+      </el-col>
+    </el-row>
+  </div>
 </template>
 
 <script setup lang="ts">
 defineOptions({
-  name: "Welcome",
+  name: "Dashboard",
+  inheritAttrs: false,
+});
+
+import { useUserStore } from "@/store/modules/user";
+import { useTransition, TransitionPresets } from "@vueuse/core";
+
+const userStore = useUserStore();
+const date: Date = new Date();
+
+const greetings = computed?.(() => {
+  const hours = date.getHours();
+  if (hours >= 6 && hours < 8) {
+    return "晨起披衣出草堂,轩窗已自喜微凉🌅!";
+  } else if (hours >= 8 && hours < 12) {
+    return "上午好," + userStore.user.userName + "!";
+  } else if (hours >= 12 && hours < 18) {
+    return "下午好," + userStore.user.userName + "!";
+  } else if (hours >= 18 && hours < 24) {
+    return "晚上好," + userStore.user.userName + "!";
+  } else if (hours >= 0 && hours < 6) {
+    return "偷偷向银河要了一把碎星,只等你闭上眼睛撒入你的梦中,晚安🌛!";
+  }
+});
+
+const duration = 5000;
+
+// 销售额
+const amount = ref(0);
+const amountOutput = useTransition(amount, {
+  duration: duration,
+  transition: TransitionPresets.easeOutExpo,
+});
+amount.value = 2000;
+
+// 访客数
+const visitCount = ref(0);
+const visitCountOutput = useTransition(visitCount, {
+  duration: duration,
+  transition: TransitionPresets.easeOutExpo,
+});
+visitCount.value = 2000;
+
+// IP数
+const dauCount = ref(0);
+const dauCountOutput = useTransition(dauCount, {
+  duration: duration,
+  transition: TransitionPresets.easeOutExpo,
+});
+dauCount.value = 2000;
+
+// 订单量
+const orderCount = ref(0);
+const orderCountOutput = useTransition(orderCount, {
+  duration: duration,
+  transition: TransitionPresets.easeOutExpo,
 });
+orderCount.value = 2000;
 </script>
 
-<style scoped lang="scss"></style>
+<style lang="scss" scoped>
+.dashboard-container {
+  position: relative;
+  padding: 24px;
+
+  .user-avatar {
+    width: 40px;
+    height: 40px;
+    border-radius: 50%;
+  }
+
+  .github-corner {
+    position: absolute;
+    top: 0;
+    right: 0;
+    z-index: 1;
+    border: 0;
+  }
+
+  .data-box {
+    display: flex;
+    justify-content: space-between;
+    padding: 20px;
+    font-weight: bold;
+    color: var(--el-text-color-regular);
+    background: var(--el-bg-color-overlay);
+    border-color: var(--el-border-color);
+    box-shadow: var(--el-box-shadow-dark);
+  }
+
+  .svg-icon {
+    fill: currentcolor !important;
+  }
+}
+</style>