gaoluyang
2026-02-03 830adfef9c17ff8915761ff218f9d8e8a04bc3ea
src/views/basicData/product/index.vue
@@ -3,35 +3,62 @@
    <div class="left">
      <div>
        <el-input
            v-model="search"
            style="width: 240px"
            placeholder="输入关键字进行搜索"
            @change="searchFilter"
            @clear="searchFilter"
            clearable
            prefix-icon="Search"
          v-model="search"
          style="width: 210px"
          placeholder="输入关键字进行搜索"
          @change="searchFilter"
          @clear="searchFilter"
          clearable
          prefix-icon="Search"
        />
        <el-button type="primary" @click="openProDia('add')" style="margin-left: 10px">新增产品</el-button>
        <el-button
          type="primary"
          @click="openProDia('addOne')"
          style="margin-left: 10px"
          >新增产品大类</el-button
        >
      </div>
      <div>
        <el-tree ref="tree" v-loading="treeLoad" :data="list"
                 :default-expanded-keys="expandedKeys" :draggable="true" :filter-node-method="filterNode"
                 :props="{ children: 'children', label: 'label' }" highlight-current node-key="label"
                 style="height: calc(100vh - 190px);overflow-y: scroll;scrollbar-width: none;">
      <div ref="containerRef">
        <el-tree
          ref="tree"
          v-loading="treeLoad"
          :data="list"
          @node-click="handleNodeClick"
          :expand-on-click-node="false"
          :default-expanded-keys="expandedKeys"
          :filter-node-method="filterNode"
          :props="{ children: 'children', label: 'label' }"
          highlight-current
          node-key="id"
          class="product-tree-scroll"
          style="height: calc(100vh - 190px); overflow-y: auto"
        >
          <template #default="{ node, data }">
            <div class="custom-tree-node">
              <span>{{ node.label }}</span>
              <span class="tree-node-content">
                <el-icon class="orange-icon">
                  <component :is="data.children && data.children.length > 0
                  ? node.expanded ? 'FolderOpened' : 'Folder' : 'Tickets'" />
                </el-icon>
                <span class="tree-node-label">{{ data.label }}</span>
              </span>
              <div>
                <el-button type="primary" link @click="openProDia('edit', data)">
                <el-button
                  type="primary"
                  link
                  @click="openProDia('edit', data)"
                >
                  编辑
                </el-button>
                <!-- 修改此处 -->
                <el-button type="primary" link @click="openProDia('add', data)" :disabled="node.level >= 3">
                  添加产品
                </el-button>
                <el-button
                    v-if="!node.childNodes.length"
                    style="margin-left: 4px"
                    type="danger"
                    link
                    @click="remove(node, data)"
                  v-if="!node.childNodes.length"
                  style="margin-left: 4px"
                  type="danger"
                  link
                  @click="remove(node, data)"
                >
                  删除
                </el-button>
@@ -42,19 +69,61 @@
      </div>
    </div>
    <div class="right">
      <div style="margin-bottom: 10px">
        <el-button type="primary" @click="modelDia = true">新增规格型号</el-button>
        <el-button type="danger" @click="handleDelete" style="margin-left: 10px" plain>删除</el-button>
      <div style="margin-bottom: 10px" v-if="isShowButton">
        <el-button type="primary" @click="openModelDia('add')">
          新增规格型号
        </el-button>
        <ImportExcel @uploadSuccess="getModelList" />
        <el-button
          type="danger"
          @click="handleDelete"
          style="margin-left: 10px"
          plain
        >
          删除
        </el-button>
      </div>
      <PIMTable :column="tableColumn" :tableData="tableData" :page="page" :isSelection="true" :handleSelectionChange="handleSelectionChange"
                :tableLoading="tableLoading" @pagination="pagination" :total="total"></PIMTable>
      <PIMTable
        rowKey="id"
        :column="tableColumn"
        :tableData="tableData"
        :page="page"
        :isSelection="true"
        @selection-change="handleSelectionChange"
        :tableLoading="tableLoading"
        @pagination="pagination"
      >
        <template #productImage="{ row }">
          <img
            v-if="row.url"
            class="upload-img"
            :src="javaApiUrl + row.url"
            @click="previewImage(row.url)"
            style="cursor: pointer"
          />
          <span v-else style="color: #909399">暂无图片</span>
        </template>
      </PIMTable>
    </div>
    <el-dialog :visible.sync="productDia" title="产品" width="400px">
      <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
    <el-dialog v-model="productDia" title="产品" width="400px" @keydown.enter.prevent>
      <el-form
        :model="form"
        label-width="140px"
        label-position="top"
        :rules="rules"
        ref="formRef"
      >
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="产品名称:" prop="productName">
              <el-input v-model="form.productName" placeholder="请输入产品名称" clearable/>
              <el-input
                v-model="form.productName"
                placeholder="请输入产品名称"
                maxlength="20"
                show-word-limit
                clearable
                @keydown.enter.prevent
              />
            </el-form-item>
          </el-col>
        </el-row>
@@ -66,173 +135,577 @@
        </div>
      </template>
    </el-dialog>
    <el-dialog
      v-model="modelDia"
      title="规格型号"
      width="600px"
      @close="closeModelDia"
      @keydown.enter.prevent
    >
      <el-form
        :model="modelForm"
        label-width="140px"
        label-position="top"
        :rules="modelRules"
        ref="modelFormRef"
      >
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="规格型号:" prop="model">
              <el-input
                v-model="modelForm.model"
                placeholder="请输入规格型号"
                clearable
                @keydown.enter.prevent
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="单位:" prop="unit">
              <el-input
                v-model="modelForm.unit"
                placeholder="请输入单位"
                clearable
                @keydown.enter.prevent
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="高度:" prop="height">
              <el-input
                v-model="modelForm.height"
                placeholder="请输入高度"
                clearable
                @keydown.enter.prevent
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="每件数量/支:" prop="boxNum">
              <el-input-number
                :step="1"
                :min="0"
                style="width: 100%"
                v-model="modelForm.boxNum"
                @change="calculateTotalPrice"
                placeholder="请输入每件数量"
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="单价(元)/件:" prop="taxInclusiveUnitPrice">
              <el-input-number
                :step="0.01"
                :min="0"
                style="width: 100%"
                v-model="modelForm.taxInclusiveUnitPrice"
                @change="calculateTotalPrice"
                placeholder="请输入单价"
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="单价(美元)/件:" prop="dollarPrice">
              <el-input-number
                :step="0.01"
                :min="0"
                style="width: 100%"
                v-model="modelForm.dollarPrice"
                placeholder="请输入美元单价"
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="产品图片:" prop="url">
              <el-upload
                :action="uploadUrl"
                :before-upload="handleBeforeUpload"
                :on-success="(res, file) => handleUploadSuccess(res, file)"
                :on-error="handleUploadError"
                name="file"
                :show-file-list="false"
                :headers="headers"
                accept="image/*"
                :data="{ type: 13 }"
              >
                <img
                  v-if="modelForm.url"
                  class="upload-img-dialog"
                  :src="javaApiUrl + modelForm.url"
                />
                <el-icon v-else class="avatar-uploader-icon-dialog"><Plus /></el-icon>
              </el-upload>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitModelForm">确认</el-button>
          <el-button @click="closeModelDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import {ref} from "vue";
import {listCustomer} from "@/api/basicData/customerFile.js";
import {ElMessageBox} from "element-plus";
import {productTreeList} from "@/api/basicData/product.js";
const { proxy } = getCurrentInstance()
import { ref, reactive, toRefs, getCurrentInstance, nextTick } from "vue";
import { ElMessageBox } from "element-plus";
import { Plus } from "@element-plus/icons-vue";
import { getToken } from "@/utils/auth";
import {
  addOrEditProduct,
  addOrEditProductModel,
  delProduct,
  delProductModel,
  modelListPage,
  productTreeList,
} from "@/api/basicData/product.js";
import ImportExcel from "./ImportExcel/index.vue";
import PIMTable from "@/components/PIMTable/PIMTable.vue";
const search = ref('')
const treeLoad = ref(false)
const list = ref([])
const expandedKeys = ref([])
const { proxy } = getCurrentInstance();
const tree = ref(null);
const containerRef = ref(null);
const productDia = ref(false);
const modelDia = ref(false);
const modelOperationType = ref("");
const search = ref("");
const currentId = ref("");
const currentParentId = ref("");
const operationType = ref("");
const treeLoad = ref(false);
const list = ref([]);
const expandedKeys = ref([]);
const tableColumn = ref([
  {
    label: '规格型号',
    prop: 'model',
    label: "产品图片",
    prop: "url",
    dataType: "slot",
    slot: "productImage",
    align: "center",
    width: 100,
  },
  {
    label: '单位',
    prop: 'model',
    label: "规格型号",
    prop: "model",
  },
  {
    label: "单位",
    prop: "unit",
  },
  {
    label: "高度",
    prop: "height",
    width: 120,
  },
  {
    label: "每件数量/支",
    prop: "boxNum",
    width: 120,
  },
  {
    label: "单价(元)/件",
    prop: "taxInclusiveUnitPrice",
    width: 120,
  },
  {
    label: "单价(美元)/件",
    prop: "dollarPrice",
    width: 130,
  },
  {
    dataType: "action",
    label: "操作",
    align: 'center',
    align: "center",
    operation: [
      {
        name: "编辑",
        type: "text",
        clickFun: (row) => {
          openForm('edit', row);
          openModelDia("edit", row);
        },
      },
    ],
  },
])
const tableData = ref([])
const tableLoading = ref(false)
const total = ref(0)
const selectedRows = ref([])
]);
const tableData = ref([]);
const tableLoading = ref(false);
const isShowButton = ref(false);
const selectedRows = ref([]);
// 上传配置
const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + "/file/upload"); // 上传的图片服务器地址
const headers = ref({ Authorization: "Bearer " + getToken() });
const javaApiUrl = proxy.javaApi || import.meta.env.VITE_APP_BASE_API;
const page = reactive({
  current: 1,
  size: 10,
})
const productDia = ref(false)
const modelDia = ref(false)
  total: 0,
});
const data = reactive({
  form: {
    productName: '',
    productName: "",
  },
  rules: {
    productName: [{ required: true, message: "请输入", trigger: "blur" }],
  }
})
const { form, rules } = toRefs(data)
    productName: [
      { required: true, message: "请输入", trigger: "blur" },
      { max: 20, message: "产品名称不能超过20个字符", trigger: "blur" },
    ],
  },
  modelForm: {
    model: "",
    unit: "",
    url: "",
    height: "",
    boxNum: null,
    taxInclusiveUnitPrice: null,
    dollarPrice: null,
  },
  modelRules: {
    model: [{ required: true, message: "请输入规格型号", trigger: "blur" }],
    unit: [{ required: true, message: "请输入单位", trigger: "blur" }],
    url: [{ required: true, message: "请上传产品图片", trigger: "change" }],
    height: [{ required: true, message: "请输入高度", trigger: "blur" }],
    boxNum: [{ required: true, message: "请输入每件数量/支", trigger: "change" }],
    taxInclusiveUnitPrice: [{ required: true, message: "请输入单价(元)/件", trigger: "change" }],
    dollarPrice: [{ required: true, message: "请输入单价(美元)/件", trigger: "change" }],
  },
});
const { form, rules, modelForm, modelRules } = toRefs(data);
// 查询产品树
const getProductTreeList = () => {
  treeLoad.value = true;
  productTreeList().then(res => {
    list.value = res
    list.value.forEach((a) => {
      expandedKeys.value.push(a.label);
  productTreeList()
    .then((res) => {
      list.value = res;
      list.value.forEach((a) => {
        expandedKeys.value.push(a.label);
      });
      treeLoad.value = false;
    })
    .catch((err) => {
      treeLoad.value = false;
    });
    treeLoad.value = false;
  }).catch(err => {
    treeLoad.value = false;
  })
}
};
// 过滤产品树
const searchFilter = () => {
  proxy.$refs.tree.filter(search.value);
}
};
// 打开产品弹框
const openProDia = (type, data) => {
  productDia.value = true
  console.log('openEditDia', data)
}
  operationType.value = type;
  productDia.value = true;
  form.value.productName = "";
  if (type === "edit") {
    form.value.productName = data.productName;
  }
};
// 打开规格型号弹框
const openModelDia = (type, data) => {
  modelOperationType.value = type;
  modelDia.value = true;
  // 重置表单
  modelForm.value = {
    model: "",
    unit: "",
    url: "",
    height: "",
    boxNum: null,
    taxInclusiveUnitPrice: null,
    dollarPrice: null,
    id: "",
  };
  if (type === "edit" && data) {
    // 如果 url 是 Windows 路径,需要转换
    let url = data.url || "";
    if (url && url.indexOf("\\") > -1) {
      url = processFileUrl(url);
    }
    modelForm.value = {
      ...data,
      url: url,
      boxNum: data.boxNum || null,
      taxInclusiveUnitPrice: data.taxInclusiveUnitPrice || null,
      dollarPrice: data.dollarPrice || null,
    };
  }
};
// 提交产品名称修改
const submitForm = () => {
}
  proxy.$refs.formRef.validate((valid) => {
    if (valid) {
      if (operationType.value === "add") {
        form.value.parentId = currentId.value;
        form.value.id = "";
      } else if (operationType.value === "addOne") {
        form.value.id = "";
        form.value.parentId = "";
      } else {
        form.value.id = currentId.value;
        form.value.parentId = "";
      }
      addOrEditProduct(form.value).then((res) => {
        proxy.$modal.msgSuccess("提交成功");
        closeProDia();
        getProductTreeList();
      });
    }
  });
};
// 关闭产品弹框
const closeProDia = () => {
  proxy.$refs.formRef.resetFields();
  productDia.value = false;
}
const remove = (value) => {
};
}
// 删除产品
const remove = (node, data) => {
  let ids = [];
  ids.push(data.id);
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除提示", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(() => {
      tableLoading.value = true;
      delProduct(ids)
        .then((res) => {
          proxy.$modal.msgSuccess("删除成功");
          getProductTreeList();
        })
        .finally(() => {
          tableLoading.value = false;
        });
    })
    .catch(() => {
      proxy.$modal.msg("已取消");
    });
};
// 选择产品
const handleNodeClick = (val, node, el) => {
  // 判断是否为叶子节点
  isShowButton.value = !(val.children && val.children.length > 0);
  // 只有叶子节点才执行以下逻辑
  currentId.value = val.id;
  currentParentId.value = val.parentId;
  getModelList();
};
// 提交规格型号修改
const submitModelForm = () => {
  proxy.$refs.modelFormRef.validate((valid) => {
    if (valid) {
      modelForm.value.productId = currentId.value;
      addOrEditProductModel(modelForm.value).then((res) => {
        proxy.$modal.msgSuccess("提交成功");
        closeModelDia();
        getModelList();
      });
    }
  });
};
// 关闭型号弹框
const closeModelDia = () => {
  proxy.$refs.modelFormRef.resetFields();
  modelDia.value = false;
};
// 表格选择数据
const handleSelectionChange = (selection) => {
  selectedRows.value = selection
}
// 删除
const handleDelete = () => {
  let ids = []
  if (selectedRows.value.length > 0) {
    ids = selectedRows.value.map(item => item.id);
  } else {
    proxy.$modal.msgWarning('请选择数据')
    return
  }
  ElMessageBox.confirm(
      '选中的内容将被删除,是否确认删除?',
      '删除提示', {
        confirmButtonText: '确认',
        cancelButtonText: '取消',
        type: 'warning',
      }
  ).then(() => {
    tableLoading.value = true
    delCustomer(ids).then(res => {
      proxy.$modal.msgSuccess("删除成功")
      getList()
    }).finally(() => {
      tableLoading.value = false
    })
  }).catch(() => {
    proxy.$modal.msg("已取消")
  })
}
  selectedRows.value = selection;
};
// 查询规格型号
const pagination = ({ current, limit }) => {
  page.current = current;
  page.size = limit;
  getList()
}
const getList = () => {
  tableLoading.value = true
  listCustomer({...searchForm.value, ...page}).then(res => {
    tableLoading.value = false
    tableData.value = res.rows
    total.value = res.total
const pagination = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getModelList();
};
const getModelList = () => {
  tableLoading.value = true;
  modelListPage({
    id: currentId.value,
    current: page.current,
    size: page.size,
  }).then((res) => {
    console.log("res", res);
    // 处理返回的数据,转换 Windows 路径为可访问的 URL
    tableData.value = (res.records || []).map((item) => {
      if (item.url && item.url.indexOf("\\") > -1) {
        item.url = processFileUrl(item.url);
      }
      return item;
    });
    page.total = res.total;
    tableLoading.value = false;
  });
};
// 上传前校验
const handleBeforeUpload = (file) => {
  const isImage = file.type.startsWith("image/");
  const isLt5M = file.size / 1024 / 1024 < 5;
  if (!isImage) {
    proxy.$modal.msgError("上传文件只能是图片格式!");
    return false;
  }
  if (!isLt5M) {
    proxy.$modal.msgError("上传图片大小不能超过 5MB!");
    return false;
  }
  return true;
};
// 处理文件路径:将 Windows 路径转换为可访问的 URL
const processFileUrl = (filePath) => {
  if (!filePath) return "";
  // 如果路径是 Windows 路径格式(包含反斜杠),需要转换
  if (filePath && filePath.indexOf("\\") > -1) {
    // 查找 temp 或 uploads 关键字的位置
    const tempIndex = filePath.toLowerCase().indexOf("temp");
    const uploadsIndex = filePath.toLowerCase().indexOf("uploads");
    if (tempIndex > -1) {
      // 从 temp 开始提取相对路径,并将反斜杠替换为正斜杠
      const relativePath = filePath.substring(tempIndex).replace(/\\/g, "/");
      filePath = "/" + relativePath;
    } else if (uploadsIndex > -1) {
      // 从 uploads 开始提取相对路径
      const relativePath = filePath.substring(uploadsIndex).replace(/\\/g, "/");
      filePath = "/" + relativePath;
    } else {
      // 如果没有找到关键字,提取文件名
      const parts = filePath.split("\\");
      const fileName = parts[parts.length - 1];
      filePath = "/temp/uploads/" + fileName;
    }
  }
  // 确保路径以 / 开头
  if (filePath && !filePath.startsWith("/")) {
    filePath = "/" + filePath;
  }
  return filePath;
};
// 上传成功
const handleUploadSuccess = (res, file) => {
  if (res.code === 200) {
    // 从 res.data 中获取 tempPath,并转换为可访问的 URL
    const tempPath = res.data?.tempPath || res.tempPath;
    if (tempPath) {
      const relativePath = processFileUrl(tempPath);
      modelForm.value.url = relativePath;
      proxy.$modal.msgSuccess("上传成功");
      // 触发表单验证
      nextTick(() => {
        proxy.$refs.modelFormRef?.validateField("url");
      });
    } else {
      proxy.$modal.msgError("上传成功但未返回文件路径");
    }
  } else {
    proxy.$modal.msgError(res.msg || "上传失败");
  }
};
// 上传失败
const handleUploadError = () => {
  proxy.$modal.msgError("上传失败,请重试");
};
// 计算总价
const calculateTotalPrice = () => {
  // 如果需要计算总价,可以在这里添加逻辑
  // if (modelForm.value.boxNum && modelForm.value.taxInclusiveUnitPrice) {
  //   modelForm.value.totalPrice = modelForm.value.boxNum * modelForm.value.taxInclusiveUnitPrice;
  // }
};
// 预览图片
const previewImage = (url) => {
  if (url) {
    window.open(javaApiUrl + url, "_blank");
  }
};
// 删除规格型号
const handleDelete = () => {
  let ids = [];
  if (selectedRows.value.length > 0) {
    ids = selectedRows.value.map((item) => item.id);
  } else {
    proxy.$modal.msgWarning("请选择数据");
    return;
  }
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除提示", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
}
    .then(() => {
      tableLoading.value = true;
      delProductModel(ids)
        .then((res) => {
          proxy.$modal.msgSuccess("删除成功");
          getModelList();
        })
        .finally(() => {
          tableLoading.value = false;
        });
    })
    .catch(() => {
      proxy.$modal.msg("已取消");
    });
};
// 调用tree过滤方法 中文英过滤
const filterNode = (value, data, node) => {
  if (!value) {    //如果数据为空,则返回true,显示所有的数据项
    return true
  if (!value) {
    //如果数据为空,则返回true,显示所有的数据项
    return true;
  }
  // 查询列表是否有匹配数据,将值小写,匹配英文数据
  let val = value.toLowerCase()
  return chooseNode(val, data, node) // 调用过滤二层方法
}
  let val = value.toLowerCase();
  return chooseNode(val, data, node); // 调用过滤二层方法
};
// 过滤父节点 / 子节点 (如果输入的参数是父节点且能匹配,则返回该节点以及其下的所有子节点;如果参数是子节点,则返回该节点的父节点。name是中文字符,enName是英文字符.
const chooseNode = (value, data, node) => {
  if (data.label.indexOf(value) !== -1) {
    return true
    return true;
  }
  const level = node.level
  const level = node.level;
  // 如果传入的节点本身就是一级节点就不用校验了
  if (level === 1) {
    return false
    return false;
  }
  // 先取当前节点的父节点
  let parentData = node.parent
  let parentData = node.parent;
  // 遍历当前节点的父节点
  let index = 0
  let index = 0;
  while (index < level - 1) {
    // 如果匹配到直接返回,此处name值是中文字符,enName是英文字符。判断匹配中英文过滤
    if (parentData.data.label.indexOf(value) !== -1) {
      return true
      return true;
    }
    // 否则的话再往上一层做匹配
    parentData = parentData.parent
    index++
    parentData = parentData.parent;
    index++;
  }
  // 没匹配到返回false
  return false
}
getProductTreeList()
  return false;
};
getProductTreeList();
</script>
<style scoped>
@@ -240,22 +713,114 @@
  display: flex;
}
.left {
  width: 380px;
  width: 450px;
  min-width: 450px;
  padding: 16px;
  background: #ffffff;
}
.right {
  width: calc(100% - 380px);
  flex: 1;
  min-width: 0;
  padding: 16px;
  margin-left: 20px;
  background: #ffffff;
}
.custom-tree-node {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 14px;
  padding-right: 8px;
}
</style>
.tree-node-content {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  height: 100%;
  overflow: hidden;
}
.tree-node-content .orange-icon {
  flex-shrink: 0;
}
.tree-node-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.orange-icon {
  color: orange;
  font-size: 18px;
  margin-right: 8px; /* 图标与文字之间加点间距 */
}
.product-tree-scroll {
  scrollbar-width: thin;
  scrollbar-color: #c0c4cc #f5f7fa;
}
.product-tree-scroll::-webkit-scrollbar {
  width: 8px;
}
.product-tree-scroll::-webkit-scrollbar-track {
  background: #f5f7fa;
  border-radius: 4px;
}
.product-tree-scroll::-webkit-scrollbar-thumb {
  background: #c0c4cc;
  border-radius: 4px;
}
.product-tree-scroll::-webkit-scrollbar-thumb:hover {
  background: #909399;
}
.upload-img {
  width: 50px;
  height: 50px;
  object-fit: cover;
  cursor: pointer;
  border-radius: 4px;
}
.avatar-uploader-icon {
  font-size: 28px;
  color: #8c939d;
  width: 50px;
  height: 50px;
  line-height: 50px;
  text-align: center;
  border: 1px dashed #d9d9d9;
  border-radius: 4px;
  cursor: pointer;
}
.avatar-uploader-icon:hover {
  border-color: #409eff;
}
.upload-img-dialog {
  width: 100px;
  height: 100px;
  object-fit: cover;
  cursor: pointer;
  border-radius: 4px;
  border: 1px solid #dcdfe6;
}
.avatar-uploader-icon-dialog {
  font-size: 28px;
  color: #8c939d;
  width: 100px;
  height: 100px;
  line-height: 100px;
  text-align: center;
  border: 1px dashed #d9d9d9;
  border-radius: 4px;
  cursor: pointer;
  display: block;
}
.avatar-uploader-icon-dialog:hover {
  border-color: #409eff;
}
</style>