微语 微语:代码适合中午敲,早晚出BUG

Avue+vue2实现大型文件切片上传 Vue

1.Avue配置项avue-crud中需要添加文件上传前函数

<avue-crud
:upload-before="uploadBefore"
>
</avue-crud>

2.工具函数代码

// 引入axios进行http请求
import axios from "axios";
// 异步函数Fileslicing,用于处理文件的分片上传
async function Fileslicing(vueInstance, file, done, loading, column) {
  let _this = vueInstance; // 将vue实例保存在_this中,用于后续引用
  const CHUNK_SIZE = 1 * 1024 * 1024; // 定义每个分片的大小为1MB
  // const CHUNK_SIZE = 512 * 1024; // 或定义分片大小为500KB
  const totalChunks = Math.ceil(file.size / CHUNK_SIZE); // 计算总的分片数量
  let uploadedChunks = 0; // 已上传分片的计数器
  const overallStartTime = performance.now(); // 记录开始上传的时间

  // 循环处理每个分片
  for (let index = 0; index < totalChunks; index++) {
    const start = index * CHUNK_SIZE; // 计算当前分片的起始位置
    const end = Math.min(start + CHUNK_SIZE, file.size); // 计算结束位置,确保不超出文件大小
    const blob = file.slice(start, end); // 从文件中切割出当前分片
    // 创建一个新的文件对象用于上传,包含当前分片内容
    const chunkFile = new File([blob], `${file.name}-${index}`, {
      type: file.type,
    });
    const formData = new FormData(); // 使用FormData上传分片
    formData.append("file", chunkFile); // 添加当前分片
    formData.append("chunkNumber", index); // 当前分片的序号
    formData.append("totalChunks", totalChunks); // 总分片数
    formData.append("fileName", file.name); // 原文件名

    try {
      // 发起分片上传请求
      const response = await axios.post(
        "/api/blade-resource/oss/endpoint/chunk",
        formData,
        {
          headers: {
            "Content-Type": "multipart/form-data",
          },
        }
      );
      uploadedChunks++; // 上传成功后,已上传分片数+1
      // 计算并显示上传进度
      const progressPercentage = ((uploadedChunks / totalChunks) * 100).toFixed(2);
      // 显示上传进度的通知
      vueInstance.$notify.info({
        title: "进度",
        message: `当前上传进度: ${progressPercentage}%`,
      });
      if(progressPercentage == 100){
          // 如果上传进度达到100%,显示文件上传完成的通知
          vueInstance.$notify({
            type: 'success',
            title: "通知",
            message: `文件已上传完成!正在处理中请稍等......`,
          });
      }

      // 如果当前分片是最后一个分片
      if (index === totalChunks - 1) {
        // 发起合并文件的请求
        const fileData = {
          fileName: file.name,
          totalChunks: totalChunks,
        };
        const Stresponse = await axios.post(
          "/api/blade-resource/oss/endpoint/mergeFile",
          fileData,
          {
            headers: {
              "Content-Type": "application/json",
            },
          }
        );

        if (Stresponse.data.code === 200) {
          // 如果文件合并成功
          const overallEndTime = performance.now(); // 记录操作结束时间
          // 计算总耗时
          const totalFunctionTime = (overallEndTime - overallStartTime) / 1000;
          column.tip = ``; // 可根据需要进行操作
          let url = (Stresponse.data.data.domain + '/' + Stresponse.data.data.name);
          _this.form.videoUrl = url; // 更新vue实例中的视频URL
          loading(); // 可能是结束加载状态的函数
          // 显示上传成功的消息
          vueInstance.$message.success(
            `上传成功:文件名:${file.name},文件大小${(file.size / (1024 * 1024)).toFixed(2)} MB, 上传进度: ${progressPercentage}%,总耗时:${totalFunctionTime.toFixed(2)}秒`
          );
        } else {
          // 如果文件合并失败
          loading();
          vueInstance.$message.error("文件合并失败");
        }
      }
    } catch (error) {
      // 如果上传过程中出现错误
      vueInstance.$message.error("上传出错");
      loading(); // 结束加载状态
      return; // 停止执行
    }
  }
}

// 导出Fileslicing函数,使其在Vue实例中可用
export default {
    install(Vue) {
      Vue.prototype.$fileslicing = Fileslicing;
    },
};

3.注册全局,在main.js中引入

import FileslicingPlugin from '../src/util/Fileslicing';
Vue.use(FileslicingPlugin);

4.页面使用

    Avue文件上传前函数
     uploadBefore(file, done, loading, column) {
      this.$fileslicing(this,file,done,loading,column);//文件切换全局函数(初始)
      },

Vue配置API代理 Vue

vite.config.js配置如下

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
export default defineConfig({
  plugins: [ 
    vue(), // 添加 Vue 插件配置
  ],
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src'),
    },
  },
    server: {
      port: 2888,
      proxy: {
        '/api': {
          target: 'http://192.168.3.14:13000',
          changeOrigin: true,
          rewrite: (path) => path.replace(/^\/api/, ''),
        },
      },
    },

})

axios封装

import axios from 'axios';

// 创建axios实例
const instance = axios.create({
  timeout: 10000, // 设置超时时间为10秒
  headers: {
    'Content-Type': 'application/json', // 设置Content-Type
  },
  // 删除baseURL配置
});

// 添加请求拦截器
instance.interceptors.request.use(
  config => {
    // 在请求发送之前做些什么
    // 如果需要在请求发送前进行额外的处理,可以在这里添加
    return config;
  },
  error => {
    // 对请求错误做些什么
    return Promise.reject(error);
  }
);

// 添加响应拦截器
instance.interceptors.response.use(
  response => {
    // 对响应数据做处理
    return response.data;
  },
  error => {
    // 对响应错误做处理
    return Promise.reject(error);
  }
);

export default instance;

API引用

import request from '@/axios';
export const $_deliveryInfo = (params) => {
    return request({
      url: '/api/logpm-distribution/a/abc', // 注意这里的 URL带上API 更改
      method: 'get',
      params
    })
}

启动项目如果只有本地地址需要再package.json中配置

  "scripts": {
    "dev": "vite --host",
  },

Vue3中判断对象中是否存在指定参数 Vue

export function checkParams(data, targetParam) {
  const keys = Object.keys(data);
  if (keys.includes(targetParam) && keys.every(key => key === targetParam || data[key] === "")) {
    return true; // 指定参数存在且没有其他参数或其他参数值为空时返回 true
  } else {
    return false; // 其他情况返回 false
  }
};

参数说明:


data:传入一个对象
targetParam:传入要校验的参数

使用示例:


let data1 = {
  operatorTime: '123123'
};
let targetParam1 = 'operatorTime';
console.log(checkParams(data1, targetParam1)); // 输出 true

let data2 = {
  serviceNumber: '',
  operatorTime: ''
};
let targetParam2 = 'operatorTime';
console.log(checkParams(data2, targetParam2)); // 输出 false

let data3 = {
  serviceNumber: '',
  operatorTime: '456456'
};
let targetParam3 = 'operatorTime';
console.log(checkParams(data3, targetParam3)); // 输出 true

饿了么表格组件 Vue

<template>
  <!-- 表格组件 -->
  <div class="el-content-el" ref="TabHeight">
    <!-- 搜索功能  -->
    <div class="SoInput" v-show="searchSo" ref="SoHeight">
      <el-form label-position="top" label-width="100px" :model="SoInfoData">
        <el-form-item label="盘点任务编码:">
          <el-input v-model="SoInfoData.a" placeholder="请输入盘点任务编码" />
        </el-form-item>
      </el-form>
      <div class="SoBtn">
        <el-button type="primary" @click="SoInput"
          ><el-icon><Search /></el-icon>搜索</el-button
        >
        <el-button class="SoEmpty" type="primary" @click="SoEmpty">
          <el-icon><Delete /></el-icon> 清空
        </el-button>
      </div>
    </div>
    <!-- 顶部开始 -->
    <div class="el-Cart-button">
      <!-- 表格顶部左侧按钮 -->
      <div class="el-Cart-button-left">
        <el-button type="primary" @click="AddInfo"
          ><el-icon><Plus /></el-icon>新 增</el-button
        >
        <el-button @click="handleDelete"
          ><el-icon><Delete /></el-icon>删 除</el-button
        >
        <el-button @click="handleDelete"
          ><el-icon><Edit /></el-icon>查看维修记录</el-button
        >
        <!-- <el-button link type="primary" icon="el-icon-view" @click="toggleSelection()"
          >多选测试</el-button
        > -->
      </div>
      <!-- 表格顶部右侧按钮 -->
      <div class="el-Cart-button-right">
        <!-- 刷新按钮 -->
        <button @click="refresh">
          <el-icon color="#ccc"><Refresh /></el-icon>
        </button>
        <!-- 功能按钮 -->
        <button @click="menu = true">
          <el-icon color="#ccc"><Operation /></el-icon>
        </button>
        <!-- 搜索按钮 -->
        <button @click="search">
          <el-icon color="#ccc"><Search /></el-icon>
        </button>
      </div>
    </div>
    <!-- 顶部结束 -->
    <div class="el-Cart">
      <!-- 表格列开始 -->
      <el-table
        v-loading="loading"
        element-loading-text="数据正在更新中..."
        :data="data"
        row-key="name"
        border
        :height="TabHeight + 'px'"
        style="width: 100%"
        ref="tableRef"
        @select-all="selectAll"
        @select="selectChange"
      >
        <el-table-column type="selection" width="55" fixed />
        <el-table-column type="index" fixed width="50" height="100" label="#" align="center" />
        <template v-for="(item, index) in menuData" :key="item.label">
          <!-- 
            Type ===1 普通文本
            Type ===2 图片显示
            Type ===3 操作功能
            label:    标题
            width:    宽度
            prop:     数据字段
           -->
          <template v-if="item.type === 1">
            <el-table-column
              v-if="item.head"
              :prop="item.prop"
              :label="item.label"
              :width="item.width"
              :fixed="item.fixed"
              align="center"
            >
              <template #default="props">
                <span>{{ props.row[item.prop] ? props.row[item.prop] : '/' }}</span>
              </template>
            </el-table-column>
          </template>

          <template v-if="item.type === 2">
            <el-table-column
              v-if="item.head"
              :label="item.label"
              :width="item.width"
              :fixed="item.fixed"
              align="center"
            >
              <template #default="props">
                <img :src="props.row[item.prop]" class="el-Img" />
              </template>
            </el-table-column>
          </template>

          <template v-if="item.type === 3">
            <el-table-column
              v-if="item.head"
              fixed="right"
              label="操作"
              width="280"
              align="center "
            >
              <template #default="scope">
                <div :class="ElBtnClass">
                  <el-button
                    :link="ElButtonS[0].link"
                    :class="ElButtonS[0].class"
                    :auto-insert-space="ElButtonS[0].space"
                    :size="ElButtonS[0].size"
                    :icon="ElButtonS[0].icon"
                    @click="DeleteEvent(scope.row)"
                    >删除</el-button
                  >
                  <el-button
                    :link="ElButtonS[1].link"
                    :class="ElButtonS[1].class"
                    :auto-insert-space="ElButtonS[1].space"
                    :size="ElButtonS[1].size"
                    :icon="ElButtonS[1].icon"
                    @click="EditEvent(scope.row)"
                    >编辑</el-button
                  >
                  <el-button
                    :link="ElButtonS[2].link"
                    :class="ElButtonS[2].class"
                    :auto-insert-space="ElButtonS[2].space"
                    :size="ElButtonS[2].size"
                    :icon="ElButtonS[2].icon"
                    @click="ViewEvent(scope.row)"
                    >查看</el-button
                  >
                </div>
              </template>
            </el-table-column>
          </template>
        </template>
      </el-table>
      <!-- 表格翻页功能 -->
      <div class="demo-pagination-block">
        <el-pagination
          v-model:current-page="currentPage"
          v-model:page-size="pageSize"
          :page-sizes="pageList"
          :disabled="disabled"
          :background="background"
          layout="total, sizes, prev, pager, next, jumper"
          :total="total"
          @size-change="PageSizeChange"
          @current-change="SizeChange"
        />
      </div>
    </div>
    <!-- 弹窗组件 -->
    <div class="addlalog">
      <el-dialog v-model="AddLalog" title="新增产品">
        <el-form label-position="top" label-width="100px" :model="AddForm">
          <el-form-item label="产品名称">
            <el-input v-model="AddForm.name" placeholder="请输入产品名称" />
          </el-form-item>
          <el-form-item label="购买金额">
            <el-input v-model="AddForm.purchaseAmount" placeholder="请输入购买金额" />
          </el-form-item>
          <el-form-item label="当前使用仓">
            <el-input v-model="AddForm.currentUsageWarehouse" placeholder="请输入当前使用仓" />
          </el-form-item>

          <el-form-item label="设备序列号S/N">
            <el-input v-model="AddForm.deviceSerialNumber" placeholder="请输入设备序列号" />
          </el-form-item>

          <el-form-item label="是否收取押金">
            <el-select v-model="AddForm.isDeposit" class="m-2" placeholder="选择是否收取押金">
              <el-option
                v-for="item in options"
                :key="item.value"
                :label="item.label"
                :value="item.value"
              />
            </el-select>
          </el-form-item>

          <el-form-item label="采购公司">
            <el-input v-model="AddForm.purchasingCompany" placeholder="请输入采购公司" />
          </el-form-item>
          <el-form-item label="保修截至时间">
            <el-date-picker
              v-model="AddForm.warrantyPeriodEndTime"
              type="date"
              placeholder="选择保修截至日期"
              size="default"
              format="YYYY/MM/DD"
              value-format="YYYY-MM-DD"
            />
          </el-form-item>
          <el-form-item label="图片">
            <!-- photo -->
            <el-upload
              ref="uploadRef"
              class="upload-demo"
              :action="doubledCount"
              :on-success="ImgSuccess"
              :before-upload="beforeAvatarUpload"
              :headers="headers"
              v-if="!img"
            >
              <template #trigger>
                <div class="img-icon">
                  <el-button type="primary">点击上传图片</el-button>
                  <el-icon><UploadFilled /></el-icon>
                </div>
              </template>
            </el-upload>

            <div class="el-load-img" v-else>
              <div class="el-img">
                <div class="el-img-left">
                  <el-icon><Link /></el-icon>
                  <span>{{ ImgText }}</span>
                </div>
              </div>
              <div class="el-z">
                <el-image
                  :src="url"
                  :zoom-rate="1.2"
                  :max-scale="7"
                  :min-scale="0.2"
                  :preview-src-list="UrlImg"
                  :initial-index="4"
                  :z-index="10"
                  fit="cover"
                />
              </div>
              <el-icon><SuccessFilled /></el-icon>
            </div>
          </el-form-item>

          <el-form-item label="型号">
            <el-input v-model="AddForm.model" placeholder="请输入型号" />
          </el-form-item>

          <el-form-item label="状态">
            <el-select v-model="AddForm.state" class="m-2" placeholder="选择状态">
              <el-option
                v-for="item in options2"
                :key="item.value"
                :label="item.label"
                :value="item.value"
              />
            </el-select>
          </el-form-item>

          <el-form-item label="使用人">
            <el-input v-model="AddForm.user" placeholder="请输入使用人" />
          </el-form-item>

          <el-form-item label="使用人岗位">
            <el-input v-model="AddForm.userPosition" placeholder="请输入使用人岗位" />
          </el-form-item>

          <el-form-item label="押金金额(RMB)">
            <el-input v-model="AddForm.deposit" placeholder="请输入押金金额(元)" />
          </el-form-item>

          <el-form-item label="购买时间">
            <el-date-picker
              v-model="AddForm.buyingTime"
              type="date"
              placeholder="选择购买日期"
              size="default"
              format="YYYY/MM/DD"
              value-format="YYYY-MM-DD"
            />
          </el-form-item>

          <el-form-item label="备注">
            <el-input v-model="AddForm.notes" placeholder="请输入备注" />
          </el-form-item>
        </el-form>
        <!-- 弹窗底部提交按钮 -->
        <div class="el-dialog-button">
          <el-button type="danger" @click="submit">
            <el-icon><Select /></el-icon>
            确认提交
          </el-button>
        </div>
      </el-dialog>
    </div>
    <!-- 弹窗结束 -->

    <!-- 右侧功能列表弹窗 -->
    <div class="el-menu-load">
      <el-drawer v-model="menu" direction="rtl">
        <template #header>
          <h4>菜单功能列表</h4>
        </template>
        <!-- 默认插入到侧边栏内容 -->
        <!-- 表格功能列表  -->
        <template #default>
          <div v-if="!Btn">
            <el-tabs type="border-card">
              <el-tab-pane label="表格功能">
                <el-table :data="menuData" border :height="menuHeight + 'px'">
                  <el-table-column
                    v-for="column in MenuTop"
                    :key="column.prop"
                    :prop="column.prop"
                    :label="column.label"
                    :width="column.width"
                  >
                    <!--
                  / 
                  MenuTop:       0:菜单选项
                  true-label:    1.选中时候的值
                  false-label:   2.没有选中时候的值
                  @change:       3.当绑定时候的值发生变化的时候触发
                  label:         4.选中状态的值

                  /
                -->
                    <!-- 标题 -->
                    <template #default="scope">
                      <template v-if="column.prop == 'label'">
                        <el-text class="mx-1">{{ scope.row[column.prop] }}</el-text>
                      </template>
                      <!-- 隐藏复选框 -->
                      <template v-else-if="column.label == '隐藏'">
                        <el-checkbox-group v-model="checkList">
                          <el-checkbox
                            :key="scope.row['label']"
                            :label="scope.row['label']"
                            @change="CheckBox(scope.row, 1)"
                          >
                            <!-- 用于隐藏文字 -->
                            <template #default="scope">
                              <!-- {{ scope }} -->
                            </template>
                          </el-checkbox>
                        </el-checkbox-group>
                      </template>

                      <template v-else-if="column.label == '冻结'">
                        <el-checkbox-group v-model="flexList">
                          <el-checkbox
                            :key="scope.row['label']"
                            :label="scope.row['label']"
                            @change="CheckBox(scope.row, 2)"
                          >
                            <!-- 用于隐藏文字 -->
                            <template #default="scope">
                              <!-- {{ scope }} -->
                            </template>
                          </el-checkbox>
                        </el-checkbox-group>
                      </template>
                    </template>
                  </el-table-column>
                </el-table>
              </el-tab-pane>
              <el-tab-pane label="按钮功能">
                <!-- <div class="el-title-btn">
                  <span> 表格操作按钮风格选择 </span>
                </div> -->
                <el-radio-group v-model="BtnRadio" @change="btnClass">
                  <el-radio label="1" size="default" border>链接样式风格</el-radio>
                  <el-radio label="2" size="default" border>按钮样式风格</el-radio>
                </el-radio-group>
              </el-tab-pane>
              <el-tab-pane label="表格导出">Role</el-tab-pane>
              <el-tab-pane label="待添加">Task</el-tab-pane>
            </el-tabs>
          </div>
          <!-- 按钮功能结束-->
        </template>
      </el-drawer>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, reactive, onMounted, computed } from 'vue';
import { getList, getDetail, $_AddInfo, update, remove, $_TableList } from '@/api/basic/basicPda'; //API
import { ElMessage, type UploadProps } from 'element-plus';
import { getToken } from '@/utils/auth';
// 菜单功能标题
const MenuTop = ref([
  {
    prop: 'label',
    label: '列名',
    width: '',
  },
  {
    prop: '',
    label: '隐藏',
  },
  {
    prop: '',
    label: '冻结',
  },
  {
    prop: '',
    label: '排序',
  },
]);
// 按钮配置
const ElButtonS = ref([
  // 1.link    切换按钮样式(文本模式-false ,按钮模式-tre)
  // 2.class   按钮样式名(用于切换按钮样式)
  // 3.space   是否开启按钮文子直接间距(true开启 ,false关闭)
  // 4.size    按钮尺寸默认小尺寸('large'| 'default'| 'small')
  // 5.icon    按钮图标
  // 6.table   按钮名称
  {
    link: false,
    class: 'el-btn-danger',
    space: true,
    size: 'small',
    icon: 'el-icon-delete',
    table: '删除',
  },
  {
    link: false,
    class: 'el-btn-success',
    space: true,
    size: 'small',
    icon: 'Edit',
    table: '编辑',
  },
  {
    link: false,
    class: 'el-btn-view',
    space: true,
    size: 'small',
    icon: 'el-icon-view',
    table: '查看',
  },
]);
const currentPage = ref(1); // 默认页码
const pageSize = ref(30); // 默认每一页几条
const total = ref(0); //页码总页数
const pageList = ref([5, 10, 50, 100, 200]); // 选择每页显示多少条
const background = ref(true); // 是否开启背景颜色
const disabled = ref(false); // 是否禁止使用页码功能
const data = ref([]); // 表格数据
const TabHeight = ref(); //动态获取表格高度
const AddLalog = ref(false); //提交弹窗
const img = ref(false); //图片是否上传成功
const ImgText = ref(''); //图片上传成功文件名
const AddForm = ref({}); //表单所有信息
const uploadRef = ref(); //图片上传
const tableRef = ref(null); // 用于引用 table 实例
const menu = ref(true); //功能菜单列表
const menuHeight = ref(); //右侧菜单栏高度,用于固定表头
const checkList = ref<(string | number)[]>([]); //隐藏
const flexList = ref<(string | number)[]>([]); //冻结
const loading = ref(false); //刷新功能
const SoHeight = ref(); //搜索栏高度动态
const BtnRadio = ref('1'); //按钮样式风格切换
const ElBtnClass = ref('el-Btn-link'); //按钮样式风格类名
const searchSo = ref(false); //控制搜索框是否显示隐藏
const SoInfoData = ref({ a: 123 }); //搜索框表单数据
//下拉选择框
const options = [
  {
    value: '0',
    label: '是',
  },
  {
    value: '1',
    label: '否',
  },
];
//下拉选择框
const options2 = [
  {
    value: '1',
    label: '正常',
  },
  {
    value: '2',
    label: '维修',
  },
  {
    value: '3',
    label: '不能使用(不能开机)',
  },
];
// 菜单功能头部

// 菜单功能列表
const menuData = ref([
  {
    prop: 'name',
    label: '产品名称',
    type: 1,
    values: '',
    width: '150',
    checkarr: [],
    fixed: false,
    sortable: true,
    head: true,
  },
  {
    prop: 'currentUsageWarehouse',
    label: '当前使用仓',
    type: 1,
    values: '',
    width: '150',
    checkarr: [],
    fixed: false,
    sortable: true,
    head: true,
  },
  {
    prop: 'model',
    label: '型号',
    type: 1,
    values: '',
    width: '150',
    checkarr: [],
    fixed: false,
    sortable: true,
    head: true,
  },
  {
    prop: 'purchaseAmount',
    label: '购买金额',
    type: 1,
    values: '',
    width: '180',
    checkarr: [],
    fixed: false,
    sortable: true,
    head: true,
  },
  {
    prop: 'state',
    label: '状态',
    type: 1,
    values: '',
    width: '180',
    checkarr: [],
    fixed: false,
    sortable: true,
    head: true,
  },
  {
    prop: 'user',
    label: '使用人',
    type: 1,
    values: '',
    width: '180',
    checkarr: [],
    fixed: false,
    sortable: true,
    head: true,
  },
  {
    prop: 'deviceSerialNumber',
    label: '设备序列号S/N',
    type: 1,
    values: '',
    width: '180',
    checkarr: [],
    fixed: false,
    sortable: true,
    head: true,
  },
  {
    prop: 'userPosition',
    label: '使用人岗位',
    type: 1,
    values: '',
    width: '180',
    checkarr: [],
    fixed: false,
    sortable: true,
    head: true,
  },
  {
    prop: 'isDeposit',
    label: '是否收取押金',
    type: 1,
    values: '',
    width: '180',
    checkarr: [],
    fixed: false,
    sortable: true,
    head: true,
  },
  {
    prop: 'deposit',
    label: '押金金额(元)',
    type: 1,
    values: '',
    width: '180',
    checkarr: [],
    fixed: false,
    sortable: true,
    head: true,
  },
  {
    prop: 'purchasingCompany',
    label: '采购公司',
    type: 1,
    values: '',
    width: '180',
    checkarr: [],
    fixed: false,
    sortable: true,
    head: true,
  },
  {
    prop: 'photo',
    label: '商品图片',
    type: 2,
    values: '',
    width: '180',
    checkarr: [],
    fixed: false,
    sortable: true,
    head: true,
  },
  {
    prop: 'buyingTime',
    label: '购买时间',
    type: 1,
    values: '',
    width: '180',
    checkarr: [],
    fixed: false,
    sortable: true,
    head: true,
  },
  {
    prop: 'notes',
    label: '备注',
    type: 1,
    values: '',
    width: '180',
    checkarr: [],
    fixed: false,
    sortable: true,
    head: true,
  },
  {
    prop: 'warrantyPeriodEndTime',
    label: '保修截止时间',
    type: 1,
    values: '',
    width: '180',
    checkarr: [],
    fixed: false,
    sortable: true,
    head: true,
  },
  {
    prop: '',
    label: '操作',
    type: 3,
    values: '',
    width: '280',
    checkarr: [],
    fixed: false,
    sortable: true,
    head: true,
  },
]);
// 图片上传背景图,固定白色背景
const url =
  'https://img1.baidu.com/it/u=1996827238,2183445322&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=889';
// 图片预览显示
const UrlImg = ref([]);
// 图片上传接口
const doubledCount = computed(() => {
  return '/api/blade-resource/oss/endpoint/put-file';
});
// 图片上传必须携带TOKEN
const headers = computed(() => {
  return { 'Blade-Auth': 'Bearer ' + getToken() };
});
// 获取表格数据
function onLoad() {
  // 获取表格数据API($_TableList)
  $_TableList({ current: currentPage.value, size: pageSize.value }).then(res => {
    //表格信息
    data.value = res.data.data.records; //获取到的数据存到暂存区 DOM更新之后在放入正式变量(先计算出表格高度)
    const setIn = setTimeout(() => {
      toggleSelection(); //表格自动选中函数
      clearTimeout(setIn); //清除定时器
    }, 0);
  });
}
onLoad();
// 动态计算表格高度
const TabHeig = () => {
  TabHeight.value = TabHeight.value.offsetHeight - 92; //表格高度
  menuHeight.value = window.innerHeight - 136; //侧边栏表格宽度
};

// 动态获取DOM高度给表单防止页面出现滚动条
onMounted(async () => {
  //默认值88
  TabHeig(); //调用表格计算高度
  // 等待DOM高度被复制之后在请求数据,防止被数据撑开高度
});

// 新增数据功能
const AddInfo = () => {
  AddLalog.value = true; //展开表单弹窗
};

// 表格自动选中
const toggleSelection = () => {
  if (data.value) {
    data.value.forEach((row, i) => {
      tableRef.value!.toggleRowSelection(data.value[i], true);
    });
  }
  //对表单进行循环把所有表格进行全部选中,或者通过IF对条件满足进行选中
};
// 图片上传
const imageUrl = ref('');
// 图片上传成功回调函数
const ImgSuccess: UploadProps['onSuccess'] = (response, uploadFile) => {
  if (response.success === true) {
    img.value = true;
    // 图片放入预览
    UrlImg.value.push(response.data.link);
    // 图片存入表单准备提交
    AddForm.value.photo = response.data.link;
    // 显示图片名字
    ImgText.value = response.data.originalName;
  }
};
// 上传图片规则
const beforeAvatarUpload: UploadProps['beforeUpload'] = rawFile => {
  if (rawFile.type !== 'image/png') {
    ElMessage.error('图片格式只能为image/png!');
    return false;
  } else if (rawFile.size / 1024 / 1024 > 2) {
    ElMessage.error('图片大小不能大于2MB!');
    return false;
  }
  return true;
};
// 字典转换(英文转中文)
let dictionary = (columnNames, text) => {
  // columnNames:传入的数组,要修改的字段名
  // text:传入要修改的对象
  const mapping = text;
  // 只获取指定参数对应的列数据,并将英文字段名替换为中文字段名
  const filteredData = userList.value.map(item => {
    const translatedItem = {};
    for (let column of columnNames) {
      const chineseColumn = mapping[column] || column;
      translatedItem[chineseColumn] = item[column];
    }
    return translatedItem;
  });
  // 把修修改好表头数据赋值给要导出的工作表
  expExcel.value = filteredData;
};
// 表单信息提交
const submit = () => {
  console.log(AddForm.value);
  $_AddInfo(AddForm.value).then(res => {
    console.log(res);
  });
};
// 每页多少条触发
const PageSizeChange = val => {
  console.log('每一页', val, '条');
};
// 页码翻页触发
const SizeChange = val => {
  console.log('当前是第', val, '页');
};
// 表格全选功能触发
const selectAll = val => {
  console.log(val);
};
// 表格单个选中触发事件
const selectChange = (selection, row) => {
  // 1.selection 选中的数组
  // 2.row 选中的当前行
  console.log(selection);
};
// 删除触发事件
const DeleteEvent = val => {
  // 接收点击删除当前行数据
  console.log('触发了删除事件', val);
};
// 编辑触发事件
const EditEvent = val => {
  // 接收点击编辑当前行数据
  console.log('触发了编辑事件', val);
};
// 查看事件
const ViewEvent = val => {
  //接收点击查看当前行数据;
  console.log('触发了查看事件', val);
};
// 菜单功能选择触发
const CheckBox = (scope, type: number) => {
  console.log(scope, 'scope');
  if (type === 1) {
    scope.head = !scope.head;
  }
  if (type === 2) {
    scope.fixed = !scope.fixed;
  }
};
// 刷新触发事件
const refresh = () => {
  loading.value = !loading.value;
};
// 搜索展开功能
const search = () => {
  searchSo.value = !searchSo.value;
  if (searchSo.value) {
    // 展开
    setTimeout(() => {
      console.log(SoHeight.value.offsetHeight);
      console.log(TabHeight.value.offsetHeight);
      TabHeight.value = TabHeight.value.offsetHeight - SoHeight.value.offsetHeight - 91; //表格高度
    }, 0);
  } else {
    // 关闭

    TabHeight.value = TabHeight.value.offsetHeight - 91; //表格高度
  }
};
// 顶部搜索功能
const SoInput = () => {};
// 顶部清空搜索
const SoEmpty = () => {};
// 按钮样式风格切换
const btnClass = type => {
  // 切换链接样式风格
  if (type == 1) {
    ElBtnClass.value = 'el-Btn-link';
  }
  // 切换按钮样式风格
  if (type == 2) {
    ElBtnClass.value = 'el-Btn-btn';
  }
};
</script>

<style scoped lang="scss">
// 最外层 IDV
.el-content-el {
  padding: 0 8px;
  padding-top: 8px;
  background-color: #fff;
  height: 100%;
  box-sizing: border-box;
  overflow-y: hidden; //防止页面重新计算高度的时候出现滚动条闪烁
  // 表格顶部按钮
  :deep(.el-Cart-button) {
    margin-bottom: 8px;
    display: flex;
    justify-content: space-between;
  }
  // 表格顶部左侧、右侧按钮样式
  .el-Cart-button-left,
  .el-Cart-button-right {
    display: flex;
    margin: 0 0px;
    justify-content: space-between;
  }
  .el-Cart-button-left {
    :deep(button) {
      padding: 0 14px;
      min-width: 70px;
      display: flex;
      span {
        width: 100%;
        display: inline-flex;
        align-items: center;
        justify-content: space-between;
        i {
          margin-right: 6px;
        }
      }
    }
  }
  .el-Cart-button-right {
    display: flex;
    margin: 0 0px;
    justify-content: space-between;
    align-items: center;
    button {
      background-color: transparent;
      border-radius: 50%;
      width: 30px;
      height: 30px;
      display: flex;
      align-items: center;
      justify-content: center;
      border: 1px solid #ccc;
      margin-right: 8px;

      &:hover {
        background-color: #172e601f;
        i {
          color: #0e2549;
        }
      }
      // 去掉最后一个按钮右侧边距
      &:last-child {
        margin-right: 0;
      }
    }
  }
  .el-Cart {
    // 表格标题颜色
    :deep(.el-table__header) {
      th {
        background-color: #fafafa;
        color: #000;
      }
    }
    // 组件样式开始
    // 分页样式
    .demo-pagination-block {
      display: flex;
      justify-content: flex-end;
      margin-top: 6px;
    }

    // 表格行高度
    :deep(.el-table__row) {
      height: 60px;
    }
    // 表格图片
    .el-Img {
      width: 100%;
      height: 40px;

      img {
        width: 100%;
        height: 100%;
      }
    }
  }
  // 新增数据弹窗组件样式
  :deep(.addlalog) {
    // 弹窗
    .el-dialog__header {
      border-bottom: 1px solid #172e607d;
      margin-right: 0;
      margin-bottom: 0;
      padding-bottom: 20px;
      position: relative;
      i {
        color: #172e60;
        border-radius: 50%;
        border: 1px solid;
        transition: transform 1s;
      }
    }
    .el-dialog__header button:hover {
      i {
        transform: rotate(360deg);
      }
    }
    // 弹窗标题左侧小竖线
    .el-dialog__header::after {
      content: '';
      display: block;
      width: 3px;
      height: 16px;
      background-color: #172e60;
      position: absolute;
      left: 8px;
      top: 24px;
    }
    .el-dialog__body {
      .el-form {
        width: 100%;
        display: flex;
        flex-wrap: wrap;
        justify-content: space-between;
      }
      .el-form-item {
        width: 276px;
        .el-form-item__label {
          font-family: '黑体';
        }
      }
      /* 去掉第三个、第六个、第九个div的右外边距 */
      .el-form-item:nth-child(3n) {
        margin-right: 0;
      }
    }

    .el-form-item__content {
      height: 30px;
      // 日期选择框
      .el-input {
        width: 100%;
        height: 30px !important;
      }
      //  下拉选择框
      .el-select {
        width: 100%;
      }
    }
  }

  // 底部提交
  :deep(.el-dialog-button) {
    display: flex;
    justify-content: flex-end;
  }
  .el-dialog-button button:hover {
    color: #fff;
    background-color: #10d070;
    transition: all 0.3s ease-in-out; /* 添加动画效果 */
    background-color: #10d070;
    border: 1px solid #10d070;
  }
  // 图片上传
  .img-icon {
    width: 100%;
    position: relative;
    .el-button {
      width: 100%;
      background-color: #fff;
      color: #a8abb9;
      justify-content: flex-start;
      border: 1px solid #dcdfe6;
    }
    i {
      position: absolute;
      left: 110px;
      top: 8px;
      font-size: 18px;
    }
  }
  :deep(.el-load-img) {
    width: 100%;
    height: 32px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    .el-image {
      width: 100%;
      height: auto;
      img {
        width: 100%;
      }
    }
    .el-image-viewer__canvas {
      img {
        width: 50%;
        height: 50%;
        z-index: 10;
      }
    }
    .el-z {
      .el-image {
        position: absolute;
        height: 28px;
        top: 2px;
        left: 1px;
      }
    }
    .el-icon {
      color: #10d070;
    }
  }
  :deep(.upload-demo) {
    width: 100%;
    .el-upload {
      width: 100%;
    }
    ul {
      margin-top: 0;
    }
  }
  // 图片上传成功
  .el-img {
    border: 1px solid #ccc;
    width: 100%;
    border-radius: 4px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    height: 30px;
    position: absolute;
    top: 0;
    left: 0;
    .el-img-left {
      display: flex;
      align-items: center;
      span {
        z-index: 9;
      }
      i {
        margin-right: 8px;
        color: #10d070;
        z-index: 9;
      }
    }
    i {
      color: #10d070;
      margin-left: 6px;
    }
  }
  // 侧边弹窗功能样式
  :deep(.el-menu-load) {
    .el-drawer__header {
      padding: 0;
      padding-left: 20px;
      position: relative;
      border-bottom: 1px solid #172e5f;
      margin-bottom: 0;
      i {
        color: #172e60;
      }
    }
    .el-drawer__header::after {
      content: '';
      display: block;
      width: 4px;
      height: 18px;
      background-color: #172e60;
      position: absolute;
      top: 23px;
      left: 9px;
    }
    .el-table__inner-wrapper {
      .cell {
        text-align: center;
        width: 100%;
        display: flex;
        align-items: center;
        justify-content: center;
      }
      .el-table__row {
        height: 40px;
      }
    }
    // 上移,下移按钮
    .el-text {
      margin: 0 5px;
    }
  }
  .el-table__row {
    height: 40px;

    // 链接样式风格(默认)
    .el-Btn-link {
      .el-btn-danger,
      .el-btn-success,
      .el-btn-view {
        color: #172e60;
        background-color: transparent;
        border: 1px solid transparent;
      }
    }
    // 按钮样式风格
    .el-Btn-btn {
      // 删除按钮颜色
      .el-btn-danger {
        background-color: #f56c6c;
        border: 1px solid #f56c6c;
        color: #fff;
        padding: 5px 11px;
      }
      // 编辑按钮颜色
      .el-btn-success {
        background-color: #409eff;
        border: 1px solid #409eff;
        color: #fff;
        padding: 5px 11px;
      }
      // 查看按钮颜色
      .el-btn-view {
        background-color: #172e60;
        border: 1px solid #172e60;
        color: #fff;
        padding: 5px 11px;
      }
    }
  }
  :deep(.SoInput) {
    width: 100%;
    transition: all 0.5s;
    display: flex;
    justify-content: space-between;

    .el-form {
      display: flex;
      flex-wrap: wrap;
      .el-form-item {
        width: auto;
        display: flex;
        align-items: center;
        justify-items: center;
        margin-bottom: 8px;
        margin-right: 18px;
        justify-content: space-between;
      }
      .el-form-item__label {
        margin-bottom: 0;
      }
      .el-form-item__content {
        width: 259px;
      }
    }
    // 右侧搜索清空按钮样式
    .SoBtn {
      display: flex;
      .SoEmpty {
        background-color: transparent;
        color: #000;
        border: 1px solid #ccc;
      }
    }
  }
}
</style>

数组如何递归降维 JavaScript

Array.prototype.myReduce = function () {
let newArr = [];
for (let item of this) {
   if (Array.isArray(item)) {
       newArr = newArr.concat(item.myReduce());
        } else {
            newArr.push(item);
            }
    }
return newArr;
};
// 多维数组
let arr = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]];
 console.log(arr.myReduce());

小程序入门教程 微信小程序

1. 微信小程序介绍

只能通过微信APP, 扫码/点击链接, 进入的特殊"网页"的集合.
无需下载安装包(扫码直用)

2. 移动端应用分类

H5APP: 使用传统WEB技术(H5+CSS3+JS), 在浏览器中运行的网页
小程序: 使用自家开发技术栈研发的小应用, 只能在特定APP中扫码/点击打开(微信小程序, 支付宝小程序)
原生APP: 需要下载安装包, 安装才能使用, 区分IOS/ANDROID ! 安装包不互通

3. 小程序的开发流程

3.1 注册小程序开发者账号
https://mp.weixin.qq.com/
3.2 下载小程序专用开发IDE-微信开发者工具
https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html
3.3 通过微信开发者工具, 新建项目
每次创建项目需要填写自己的小程序AppID

4. 小程序的核心目录

(重要)pages -- 小程序所有核心页面
utils -- 工具文件夹
.eslintrc.js -- eslint语法检测配置文件
app.js -- 入口js文件
app.json -- 全局配置
app.wxss -- 全局样式
project.config.json -- 工程配置1
project.private.config.json -- 工程配置2
sitemap.json -- 其他描述

5. 小程序的页面结构

小程序并非HTML, 所以有一套自己的开发模式和文件类型
.js -- 页面的逻辑
.json -- 页面的配置
.wxml -- 页面的结构
.wxss -- 页面的样式

6. 小程序独有常用标签

navigator: 超链接, 允许跳转到小程序的任意页面
view: 容器!! ===div
text: 文本 ===span
swiper: 轮播组件
swiper-item: 轮播的儿子
例:

<swiper>
   <swiper-item>元素1</swiper-item>
   <swiper-item>元素2</swiper-item>
..
</swiper>
  • button: 按钮
  • input: 输入框
  • image: 图片
  • icon: 系统送的图标
  • scroll-view: 可以滚动的view容器
  • map: 地图

7. 小程序的事件

小程序只有两种事件: 冒泡(bind), 非冒泡(catch)

事件类型:

  • tap: 触摸
  • longpress: 长按(0.35s)
  • touchstart: 触摸开始
  • touchend: 触摸结束
  • touchmove: 触摸后开始移动
  • touchcancel: 触摸后被系统弹窗/电话等打断

    绑定方式: bindtap冒泡触摸 catchtap非冒泡触摸

8. 小程序的数据驱动

8.1 把要变化的数据放入data中

data: {
name: 'zhangsan'
}

8.2 使用mustauche获取动态数据, 小程序不区分内容还是属性, 都是用{{}}获取

<view name="{{ test }}">测试{{ aa }}</view>

8.3 修改数据

this.setData({
//key要修改的key名: value新的值
name: 'lisi'
})

9. 小程序其他

底部 tabBar
自定义头部导航 navigationStyle
获取胶囊坐标 wx.getMenuButtonBoundingClientRect()


修改C盘下的User中文名文件夹 Windows教程

1.首先按Win+R快捷键,调出运行窗口,输入regedit回车打开注册表

2.依次展开注册表:\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList

展开ProfileList后双击打开最后一项,在右边的列表中找到ProfileImagePath选项

双击或者右击修改ProfileImagePath,将里面的中文名修改成英文,然后重启电脑

3.重启电脑后,我们打开C盘找到用户当中与我们修改之前的中文名一致的文件夹修改成我们修改后的英文名,然后重启电脑

注:亲测有效!!!


React打包白屏如何解决 React

在打包React项目的时候出现白屏了,需要在package.json中添加一行配置

"homepage":"."

homepage作用

打包时候, 自动在资源目录, 也就是引入的js和css目录前面添加设置的字符串,设置为 "."会自动在前面添加一个., 这样绝对路径就变为相对路径了.