Vue3商品SKU多规格编辑组件

简介: Vue3商品SKU多规格编辑组件

商品SKU多规格组件主要用于电商平台的商品详情页,帮助买家在选择商品时能够根据不同的规格(如颜色、尺码、容量等)进行筛选和购买。这种组件的设计旨在提升用户体验,简化购买流程,并确保买家能够准确地选择到自己需要的商品规格。以下是对商品SKU多规格组件的详细解析:

1. 定义与功能

  • SKU(Stock Keeping Unit)定义:SKU即库存量单位,是指宝贝的销售属性集合,供买家在下单时点选,如“规格”、“颜色分类”、“尺码”等。部分SKU的属性值可以由卖家自定义编辑,部分则不可编辑。
  • 功能:商品SKU多规格组件允许买家在商品详情页直接查看并选择商品的多种规格,同时展示每种规格对应的库存数量、价格等信息。这有助于买家快速了解商品的各种选项,并做出购买决策。

2. 组件特点

数据驱动:现代的商品SKU多规格组件通常采用数据驱动的方式,即组件从服务器获取商品规格数据,并根据这些数据动态渲染出可供买家选择的规格选项。这种方式提高了组件的灵活性和可扩展性。

用户友好:组件的设计注重用户体验,通过清晰的界面布局和交互设计,使买家能够轻松理解和选择商品的规格。

实时更新:组件能够实时从服务器获取商品的库存信息和价格信息,确保买家在选择规格时能够看到最新的数据。

3. 实现方式

前端开发:商品SKU多规格组件通常使用前端技术进行开发。这里使用了Vue3现代前端框架中,可以通过组件化的方式实现商品SKU多规格组件的复用和灵活配置。

后端支持:组件需要后端服务的支持,以获取商品的规格数据、库存信息、价格信息等。后端服务可以通过API接口向前端组件提供这些数据。

数据交互:前端组件与后端服务之间通过HTTP请求和响应进行数据交互。前端组件发送请求获取数据,后端服务处理请求并返回数据给前端组件进行展示。

4. 应用场景

电商平台:商品SKU多规格组件广泛应用于各类电商平台,如淘宝、京东、天猫等。这些平台上的商品种类繁多,规格多样,使用SKU多规格组件可以大大提升买家的购物体验。

零售店铺:对于拥有线上销售渠道的零售店铺来说,商品SKU多规格组件也是一个不可或缺的工具。它可以帮助店铺更好地展示商品信息,提高销售额。

5. 组件实现

<template>
  <div class="diygw-col-24">
    <div v-if="specs.length > 0">
      <VueDraggable v-bind="{ animation: 200, disabled: false, ghostClass: 'ghost' }" v-model="specs" class="list-group" ghost-class="ghost">
        <div v-for="(element, itemIndex) in specs" class="box sku-spec flex flex-direction-column margin-bottom">
          <div class="flex margin-bottom-xs align-center justify-between">
            <el-input style="width: 200px !important; flex: unset" placeholder="请输入规格名称" v-model="element.title"> </el-input>
            <div class="toolbar">
              <i class="diyicon handle diyicon-yidongxuanze" />
              <el-tooltip content="上移" placement="bottom">
                <i class="diy-icon-top" @click.stop="move(itemIndex, 'up')" />
              </el-tooltip>
              <el-tooltip content="下移" placement="bottom">
                <i class="diy-icon-down" @click.stop="move(itemIndex, 'down')" />
              </el-tooltip>
              <el-tooltip content="新增规格组" placement="bottom">
                <i class="diy-icon-add" @click.stop="addSpec()" />
              </el-tooltip>
              <el-tooltip content="删除规格组" placement="bottom">
                <i class="diy-icon-close" @click.stop="removeSpec(itemIndex)" />
              </el-tooltip>
            </div>
          </div>
          <div class="flex flex-wrap align-center">
            <VueDraggable
              v-bind="{ animation: 200, disabled: false, ghostClass: 'ghost' }"
              v-model="element.datas"
              class="flex flex-wrap align-center sku-spec-item"
              ghost-class="ghost"
            >
              <div v-for="(subitem, subindex) in element.datas" class="margin-right-xs spec-item margin-bottom-xs" style="width: 150px">
                <el-input v-model="subitem.title"> </el-input>
                <div class="spec-toolbar">
                  <el-tooltip content="删除规格值" placement="bottom">
                    <i class="diy-icon-close" @click.stop="removeSpecItem(itemIndex, subindex)" />
                  </el-tooltip>
                </div>
              </div>
            </VueDraggable>
            <div class="margin-right-xs margin-bottom-xs text-xs" @click="addSpecIndex(itemIndex)">新增规格值</div>
          </div>
        </div>
      </VueDraggable>
    </div>
    <el-button class="diygw-col-24 margin-bottom-xs" @click="addSpec">新增规格</el-button>
    <DiySkutable v-model="skus" :specs="specs" :columns="columns" ref="skuref"></DiySkutable>
  </div>
</template>
 
<script setup name="DiySku">
import { ElMessage } from 'element-plus';
import { useVModels } from '@vueuse/core';
import { VueDraggable } from 'vue-draggable-plus';
import DiySkutable from './skutable.vue';
const props = defineProps({
  skus: {
    type: Array,
    default: () => {
      return [];
    },
  },
  specs: {
    //规格配置
    type: Array,
    default: () => {
      return [];
    },
  },
  columns: {
    //自定义sku属性
    type: Array,
    default: () => {
      return [];
    },
  },
  filterable: {
    //是否开启sku搜索
    type: Boolean,
    default: true,
  },
});
 
const emit = defineEmits(['update:specs', 'update:skus']);
const { skus, specs } = useVModels(props, emit);
const removeSpec = (index) => {
  specs.value.splice(index, 1);
};
 
const removeSpecItem = (index, subindex) => {
  specs.value[index].datas.splice(subindex, 1);
};
 
const addSpecIndex = (index) => {
  let id = specs.value[index].datas.length + specs.value[index].id;
  specs.value[index].datas.push({
    title: '',
    id: id + '' + new Date().getTime(),
  });
};
 
const addSpec = () => {
  let id = (specs.value.length + 1) * 1000;
  specs.value.push({
    id: id,
    title: '',
    datas: [
      {
        title: '',
        id: id + '' + new Date().getTime(),
      },
    ],
  });
};
 
const move = (itemIndex, dir) => {
  let comps = specs.value;
  let item = comps[itemIndex];
  if (dir == 'up') {
    if (itemIndex == 0) {
      ElMessage.error('已经是第一个了!');
      return;
    }
    const swap = comps[itemIndex - 1];
    const tmp = swap;
    comps[itemIndex - 1] = item;
    comps[itemIndex] = tmp;
  } else {
    if (itemIndex == comps.length - 1) {
      ElMessage.error('已经是最一个了!');
      return;
    } else {
      const swap = comps[itemIndex + 1];
      const tmp = swap;
      comps[itemIndex + 1] = item;
      comps[itemIndex] = tmp;
    }
  }
};
</script>
<style lang="scss">
.sku-spec {
  padding: 6px;
  border-radius: 5px;
  border: 1px solid #ebeef5;
 
  .sku-spec-item {
    padding-left: 30px;
    padding-right: 5px;
    .spec-item {
      position: relative;
 
      .spec-toolbar {
        top: -2px;
        right: -3px;
        position: absolute;
        display: none;
      }
 
      &:hover {
        .spec-toolbar {
          display: block;
        }
      }
    }
  }
}
</style>
<template>
  <div class="sku-table" v-if="specs.length > 0">
    <table class="diy-table diy-skutable">
      <tr>
        <th v-for="(item, colIndex) in specs" :key="colIndex">
          {{ item.title }}
        </th>
        <th v-for="(item, colIndex) in columns" :style="{ width: item.type == 'number' ? '100px' : 'auto' }" :key="colIndex + specs.length + 1">
          {{ item.title }}
        </th>
      </tr>
      <tr>
        <td v-for="(item, colIndex) in specs" :key="colIndex">
          <el-select v-if="filterable" @change="resetTable" v-model="headerFilterParams[item.title]" placeholder="过滤查询" clearable>
            <el-option label="全部" value=""></el-option>
            <el-option v-for="(child, index2) in item.datas" :key="index2" :label="child.title" :value="child.title"></el-option>
          </el-select>
          <span v-else class="spec-title">
            {{ item.title }}
          </span>
        </td>
        <td v-for="(item, colIndex) in columns" :key="colIndex + specs.length + 1">
          <span v-if="item.readOnly">{{ item.title }}</span>
          <diy-uploadinput
            class="diygw-col-24"
            v-else-if="item.type == 'img'"
            @blur="
              () => {
                onBatchEdit(item.id);
              }
            "
            placeholder="批量修改"
            v-model="columnsValue[item.id]"
          />
          <el-input-number
            :min="0"
            :precision="item.precision ? item.precision : 0"
            :step="item.precision ? 0.1 : 1"
            v-else-if="item.type == 'number'"
            v-model="columnsValue[item.id]"
            controls-position="right"
            :controls="false"
            class="sku-input-number"
            @blur="
              () => {
                onBatchEdit(item.id);
              }
            "
            @keyup.native.enter="
              () => {
                onBatchEdit(item.id);
              }
            "
            placeholder="批量修改"
          />
          <el-input
            v-else
            class="diygw-col-24"
            v-model="columnsValue[item.id]"
            @blur="
              () => {
                onBatchEdit(item.id);
              }
            "
            @keyup.native.enter="
              () => {
                onBatchEdit(item.id);
              }
            "
            placeholder="批量修改"
          />
        </td>
      </tr>
      <tr v-for="(row, rowIndex) in renderTableRows" :key="row.id">
        <td
          v-for="(child, colIndex) in row.columns"
          :class="[
            child.shouldSetRowSpan ? '' : 'hide',
            rowIndex === rowLastCanSpan[colIndex] ? 'col-last-rowspan' : '',
            colIndex < specs.length - 1 ? 'row-span-style' : '',
          ]"
          :rowspan="child.shouldSetRowSpan ? assignRule[colIndex] : ''"
          :key="colIndex"
        >
          <span>{{ child.showValue }}</span>
        </td>
        <td v-for="(child, colIndex) in columns" :key="colIndex + columns.length + 1">
          <span v-if="child.readOnly">{{ row[child.id] }}</span>
          <diy-uploadinput
            v-else-if="child.type == 'img'"
            @change="
              (value) => {
                checkValue(value, child, row);
              }
            "
            :placeholder="child.title"
            v-model="row[child.id]"
          ></diy-uploadinput>
          <el-input-number
            :min="0"
            :precision="child.precision ? child.precision : 0"
            :step="child.precision ? 0.1 : 1"
            v-else-if="child.type == 'number'"
            v-model="row[child.id]"
            controls-position="right"
            :controls="false"
            class="sku-input-number"
            @change="
              (value) => {
                checkValue(value, child, row);
              }
            "
            :placeholder="child.title"
          />
          <el-input
            v-else
            v-model="row[child.id]"
            @change="
              (value) => {
                checkValue(value, child, row);
              }
            "
            :placeholder="child.title"
          />
        </td>
      </tr>
    </table>
  </div>
</template>
 
<script setup name="DiySkutable">
import { uuid } from '@/utils';
import { ElMessageBox } from 'element-plus';
import { onMounted, ref, watch, computed } from 'vue';
import DiyUploadinput from '@/components/upload/uploadinput.vue';
import { useVModel } from '@vueuse/core';
 
const props = defineProps({
  modelValue: {
    type: Array,
    default: () => {
      return [];
    },
  },
  specs: {
    //规格配置
    type: Array,
    default: () => {
      return [];
    },
  },
  columns: {
    //自定义sku属性
    type: Array,
    default: () => {
      return [];
    },
  },
  filterable: {
    //是否开启sku搜索
    type: Boolean,
    default: true,
  },
});
 
const emit = defineEmits(['update:modelValue']);
const modeldata = useVModel(props, 'modelValue', emit);
 
const headerFilterParams = ref({});
const columnsValue = ref({});
const originTableRows = ref([]);
const renderTableRows = ref([]);
 
onMounted(() => {
  render();
});
 
function render() {
  originTableRows.value = createTable();
  renderTableRows.value = originTableRows.value;
  getData();
}
 
function _resetRowSpan(table) {
  table.forEach((row, rowIndex) => {
    row.columns.forEach((column, columnIndex) => {
      column.shouldSetRowSpan = shouldSetRowSpan(rowIndex, columnIndex);
    });
  });
}
function createTable() {
  let tableData = [];
  let details = props.modelValue;
  let theaderFilterParams = {};
  props.specs.forEach((item) => {
    theaderFilterParams[item.title] = '';
  });
  headerFilterParams.value = theaderFilterParams;
  for (let i = 0; i < skuTotal.value; i++) {
    let columns = props.specs.map((t, j) => {
      let { title, id } = getShowValue(i, j);
      return {
        shouldSetRowSpan: shouldSetRowSpan(i, j),
        showValue: title,
        valueId: id,
        columnName: t.title,
        columnId: t.id,
        precision: t.precision,
      };
    });
    //获取当前组合
    let pattern = columns
      .map((t) => t.valueId)
      .sort()
      .toString();
    //从详情中找回同一个组合的sku数据
    let rowDetails = details.find((t) => t.specIds === pattern);
    //当数据长度不为0,并新增了大的规格
    if (details.length > 0 && details.length >= i && !rowDetails && details[0].specIds.split(',').length != columns.length) {
      rowDetails = details[i];
    }
    tableData.push({
      id: uuid(),
      ...createSkuPropertyFields(columns, i, rowDetails),
      columns,
    });
  }
  return tableData;
}
function createSkuPropertyFields(columns, rowIndex, row) {
  return props.columns.reduce((res, item) => {
    if (row && row[item.id]) {
      res[item.id] = row[item.id] || '';
    } else {
      if (item.defaultValue) {
        // 设置默认值,可以为string或function,fuction时会传入行的索引和列的信息
        if (typeof item.defaultValue === 'string') {
          res[item.id] = item.defaultValue;
        } else if (typeof item.defaultValue === 'function') {
          res[item.id] = item.defaultValue({ columns, rowIndex });
        }
      } else if (columnsValue.value[item.id]) {
        res[item.id] = columnsValue.value[item.id];
      } else if (res.type == 'number') {
        res[item.id] = 0;
      } else {
        res[item.id] = '';
      }
    }
    return res;
  }, {});
}
function shouldSetRowSpan(rowIndex, colIndex) {
  return rowIndex % assignRule.value[colIndex] === 0;
}
function getShowValue(rowIndex, colIndex) {
  let datas = props.specs[colIndex].datas;
  let index;
  if (colIndex === props.specs.length - 1) {
    index = rowIndex % datas.length;
  } else {
    let step = assignRule.value[colIndex];
    index = Math.floor(rowIndex / step);
    if (index >= datas.length) {
      index = index % datas.length;
    }
  }
  return datas[index];
}
 
function onBatchEdit(id) {
  ElMessageBox.confirm(`确认批量修改吗?`, '提示', {
    customClass: 'diygw-messagebox',
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning',
  })
    .then(() => {
      let value = columnsValue.value[id];
      renderTableRows.value.forEach((item) => {
        item[id] = value;
      });
      getData();
    })
    .catch(() => {});
}
function checkValue(value, columnInfo, row) {
  // let { id, pattern } = columnInfo;
  // if (pattern) {
  //  (row || columnsValue.value)[id] = value.replace(pattern, '');
  // }
  getData();
}
function getData() {
  let data = originTableRows.value.map((t) => {
    let columnObj = props.columns.reduce((res, item) => {
      res[item.id] = item.format ? item.format(t[item.id]) : t[item.id];
      return res;
    }, {});
    return {
      specIds: t.columns.map((t) => t.valueId).join(','),
      ...columnObj,
    };
  });
  modeldata.value = data;
}
 
const hasFilter = computed(() => {
  return Object.values(headerFilterParams.value).filter((t) => !!t).length > 0;
});
 
const renderSpecs = computed(() => {
  return props.specs
    .map((t) => t.datas.length)
    .map((t, index) => {
      return hasFilter.value ? (headerFilterParams.value[props.specs[index].title] ? 1 : t) : t;
    });
});
 
const skuTotal = computed(() => {
  return renderSpecs.value.reduce((result, item) => result * item, renderSpecs.value.length ? 1 : 0);
});
 
const assignRule = computed(() => {
  return renderSpecs.value.reduce((result, item, index) => {
    let preValue = result[index - 1];
    if (preValue) {
      result.push(preValue / item);
    } else {
      result.push(skuTotal.value / item);
    }
    return result;
  }, []);
});
 
const rowLastCanSpan = computed(() => {
  let indexArr = Array.from(new Array(skuTotal.value).keys()); //生成行的索引数组
  //每列可以合并的最后一行的行索引数组,为了设置样式
  return assignRule.value.map((t, index, array) => {
    return index === array.length - 1 ? null : indexArr.filter((row) => row % t === 0).pop();
  });
});
 
watch(
  () => props.specs,
  () => {
    render();
  },
  {
    deep: true,
    immediate: true,
  }
);
function resetTable() {
  if (hasFilter.value) {
    let trenderTableRows = originTableRows.value.filter((t) => {
      return t.columns.reduce((res, item) => {
        let filterValue = headerFilterParams.value[item.columnName];
        return filterValue ? res && item.showValue === filterValue : res;
      }, true);
    });
    _resetRowSpan(trenderTableRows);
    renderTableRows.value = trenderTableRows;
  } else {
    _resetRowSpan(originTableRows.value);
    renderTableRows.value = originTableRows.value;
  }
}
// watch(
//  headerFilterParams,
//  () => {
//    resetTable();
//  },
//  {
//    deep: true,
//    immediate: true,
//  }
// );
</script>

组件调用

<template>
  <div class="container">
    <div class="el-card is-always-shadow diygw-col-24">
      <div class="el-card__body">
        <div class="mb8">
          <el-button type="primary" plain @click="onOpenAddModule"> <SvgIcon name="ele-Plus" />新增 </el-button>
          <el-button type="danger" plain :disabled="state.multiple" @click="onTabelRowDel"> <SvgIcon name="ele-Delete" />删除 </el-button>
        </div>
        <el-table
          @selection-change="handleSelectionChange"
          v-loading="state.loading"
          :data="state.tableData"
          stripe
          border
          current-row-key="id"
          empty-text="没有数据"
          style="width: 100%"
        >
          <el-table-column type="selection" width="55" align="center"></el-table-column>
          <el-table-column label="姓名" prop="name" align="center"> </el-table-column>
          <el-table-column label="性别" prop="sex" align="center"> </el-table-column>
          <el-table-column label="邮箱" prop="email" align="center"> </el-table-column>
          <el-table-column label="操作" align="center" fixed="right" width="180">
            <template #default="scope">
              <el-button type="primary" plain size="small" @click="onOpenEditModule(scope.row)"> <SvgIcon name="ele-Edit" />修改 </el-button>
              <el-button v-if="scope.row.id != 0" type="danger" plain size="small" @click="onTabelRowDel(scope.row)">
                <SvgIcon name="ele-Delete" />删除
              </el-button>
            </template>
          </el-table-column>
        </el-table>
        <!-- 分页设置-->
        <div v-show="state.total > 0">
          <el-divider></el-divider>
          <el-pagination
            background
            :total="state.total"
            :current-page="state.queryParams.pageNum"
            :page-size="state.queryParams.pageSize"
            layout="total, sizes, prev, pager, next, jumper"
            @size-change="handleSizeChange"
            @current-change="handleCurrentChange"
          />
        </div>
      </div>
      <!-- 添加或修改参数配置对话框 -->
      <el-dialog :fullscreen="isFullscreen" width="800px" v-model="isShowDialog" destroy-on-close title="CRUD表格" draggable center>
        <template #header="{ close, titleId, titleClass }">
          <h4 :id="titleId" :class="titleClass">CRUD表格</h4>
          <el-tooltip v-if="!isFullscreen" content="最大化" placement="bottom">
            <el-button class="diygw-max-icon el-dialog__headerbtn">
              <el-icon @click="isFullscreen = !isFullscreen">
                <FullScreen />
              </el-icon>
            </el-button>
          </el-tooltip>
          <el-tooltip v-if="isFullscreen" content="返回" placement="bottom">
            <el-button class="diygw-max-icon el-dialog__headerbtn">
              <el-icon @click="isFullscreen = !isFullscreen">
                <Remove />
              </el-icon>
            </el-button>
          </el-tooltip>
        </template>
        <el-form class="flex flex-direction-row flex-wrap" :model="state.editForm" :rules="state.editFormRules" ref="editFormRef" label-width="80px">
          <div class="flex diygw-col-24">
            <el-form-item prop="sku" label="多规格">
              <diy-sku :columns="editFormData.skuColumns" v-model:skus="editForm.sku.skus" v-model:specs="editForm.sku.specs"></diy-sku>
            </el-form-item>
          </div>
        </el-form>
        <template #footer>
          <div class="dialog-footer flex justify-center">
            <el-button @click="closeDialog"> 取 消 </el-button>
            <el-button type="primary" @click="onSubmit" :loading="state.saveloading"> 保 存 </el-button>
          </div>
        </template>
      </el-dialog>
    </div>
    <div class="clearfix"></div>
  </div>
</template>
 
<script setup name="index">
import { ElMessageBox, ElMessage } from 'element-plus';
import { ref, toRefs, reactive, onMounted, getCurrentInstance, onUnmounted, unref } from 'vue';
import { deepClone, changeRowToForm } from '@/utils/other';
import { addData, updateData, delData, listData } from '@/api';
 
import { useRouter, useRoute } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useUserInfo } from '@/stores/userInfo';
 
import DiySku from '@/components/sku/index.vue';
 
const stores = useUserInfo();
const { userInfos } = storeToRefs(stores);
const { proxy } = getCurrentInstance();
const router = useRouter();
const route = useRoute();
const globalOption = ref(route.query);
const getParamData = (e, row) => {
  row = row || {};
  let dataset = e.currentTarget ? e.currentTarget.dataset : e;
  if (row) {
    dataset = Object.assign(dataset, row);
  }
  return dataset;
};
 
const navigateTo = (e, row) => {
  let dataset = getParamData(e, row);
  let { type } = dataset;
  if ((type == 'page' || type == 'inner' || type == 'href') && dataset.url) {
    router.push({
      path: (dataset.url.startsWith('/') ? '' : '/') + dataset.url,
      query: dataset,
    });
  } else {
    ElMessage.error('待实现点击事件');
  }
};
 
const state = reactive({
  userInfo: userInfos.value,
  // 遮罩层
  loading: true,
  // 选中数组
  ids: [],
  // 非单个禁用
  single: true,
  // 非多个禁用
  multiple: true,
  // 弹出层标题
  title: '',
  // 总条数
  total: 0,
  tableData: [],
  // 是否显示弹出层
  isFullscreen: false,
  isShowDialog: false,
  saveloading: false,
 
  editFormData: {
    skuColumns: [
      { title: '图片地址', id: 'thumb', type: 'img' },
      { title: '价格', id: 'price', type: 'number' },
      { title: '划线价格', id: 'linePrice', type: 'number' },
      { title: '数量', id: 'amount', type: 'number' },
      { title: '备注', id: 'sku', type: 'text' },
    ],
  },
 
  queryParams: {
    pageNum: 1,
    pageSize: 10,
  },
 
  queryParamsRules: {},
 
  editForm: {
    id: undefined,
    sku: {
      skus: [],
      specs: [],
    },
  },
 
  editFormRules: {},
});
const {
  userInfo,
  editFormData,
  queryParams,
  multiple,
  tableData,
  loading,
  title,
  single,
  total,
  isShowDialog,
  editForm,
  ids,
  saveloading,
  isFullscreen,
} = toRefs(state);
const editFormRef = ref(null);
 
const editFormInit = deepClone(state.editForm);
 
// 打开弹窗
const openDialog = (row) => {
  if (row.id && row.id != undefined && row.id != 0) {
    state.editForm = changeRowToForm(row, state.editForm);
  } else {
    initForm();
  }
  state.isShowDialog = true;
  state.saveloading = false;
};
 
// 关闭弹窗
const closeDialog = (row) => {
  proxy.mittBus.emit('onEditAdmintableModule', row);
  state.isShowDialog = false;
};
 
// 保存
const onSubmit = () => {
  const formWrap = unref(editFormRef);
  if (!formWrap) return;
  formWrap.validate((valid) => {
    if (valid) {
      state.saveloading = true;
      if (state.editForm.id != undefined && state.editForm.id != 0) {
        updateData('/sys/user', state.editForm)
          .then(() => {
            ElMessage.success('修改成功');
            state.saveloading = false;
            closeDialog(state.editForm); // 关闭弹窗
          })
          .finally(() => {
            state.saveloading = false;
          });
      } else {
        addData('/sys/user', state.editForm)
          .then(() => {
            ElMessage.success('新增成功');
            state.saveloading = false;
            closeDialog(state.editForm); // 关闭弹窗
          })
          .finally(() => {
            state.saveloading = false;
          });
      }
    } else {
      ElMessage.error('验证未通过!');
    }
  });
};
// 表单初始化,方法:`resetFields()` 无法使用
const initForm = () => {
  state.editForm = deepClone(editFormInit);
};
const queryParamsInit = deepClone(state.queryParams);
/** 查询CRUD表格列表 */
const handleQuery = () => {
  state.loading = true;
  listData('/sys/user', state.queryParams).then((response) => {
    state.tableData = response.rows;
    state.total = response.total;
    state.loading = false;
  });
};
/** 重置按钮操作 */
const resetQuery = () => {
  state.queryParams = deepClone(queryParamsInit);
  handleQuery();
};
 
// 打开新增CRUD表格弹窗
const onOpenAddModule = (row) => {
  row = [];
  state.title = '添加CRUD表格';
  initForm();
  openDialog(row);
};
// 打开编辑CRUD表格弹窗
const onOpenEditModule = (row) => {
  state.title = '修改CRUD表格';
  openDialog(row);
};
/** 删除按钮操作 */
const onTabelRowDel = (row) => {
  const id = row.id || state.ids;
 
  ElMessageBox({
    message: '是否确认删除选中的CRUD表格?',
    title: '警告',
    showCancelButton: true,
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning',
  }).then(function () {
    return delData('/sys/user', id).then(() => {
      handleQuery();
      ElMessage.success('删除成功');
    });
  });
};
// 多选框选中数据
const handleSelectionChange = (selection) => {
  state.ids = selection.map((item) => item.id);
  state.single = selection.length != 1;
  state.multiple = !selection.length;
};
 
//分页页面大小发生变化
const handleSizeChange = (val) => {
  state.queryParams.pageSize = val;
  handleQuery();
};
//当前页码发生变化
const handleCurrentChange = (val) => {
  state.queryParams.pageNum = val;
  handleQuery();
};
const init = async () => {};
// 页面加载时
onMounted(async () => {
  await init();
  handleQuery();
  proxy.mittBus.on('onEditAdmintableModule', () => {
    handleQuery();
  });
});
 
// 页面卸载时
onUnmounted(() => {
  proxy.mittBus.off('onEditAdmintableModule');
});
</script>
 
<style lang="scss" scoped>
.container {
  font-size: 12px;
}
</style>


已集成进开源项目组件:diygw-ui-admin: 基于vite、vue3.x 、router、pinia、Typescript、Element plus等,适配手机、平板、pc 的后台开源免费模板库

目录
相关文章
|
12天前
|
存储 JavaScript 前端开发
vue3的脚手架模板你真的了解吗?里面有很多值得我们学习的地方!
【10月更文挑战第21天】 vue3的脚手架模板你真的了解吗?里面有很多值得我们学习的地方!
vue3的脚手架模板你真的了解吗?里面有很多值得我们学习的地方!
|
9天前
|
JavaScript 前端开发 开发者
Vue 3中的Proxy
【10月更文挑战第23天】Vue 3中的`Proxy`为响应式系统带来了更强大、更灵活的功能,解决了Vue 2中响应式系统的一些局限性,同时在性能方面也有一定的提升,为开发者提供了更好的开发体验和性能保障。
24 7
|
10天前
|
前端开发 数据库
芋道框架审批流如何实现(Cloud+Vue3)
芋道框架审批流如何实现(Cloud+Vue3)
30 3
|
9天前
|
JavaScript 数据管理 Java
在 Vue 3 中使用 Proxy 实现数据双向绑定的性能如何?
【10月更文挑战第23天】Vue 3中使用Proxy实现数据双向绑定在多个方面都带来了性能的提升,从更高效的响应式追踪、更好的初始化性能、对数组操作的优化到更优的内存管理等,使得Vue 3在处理复杂的应用场景和大量数据时能够更加高效和稳定地运行。
28 1
|
9天前
|
JavaScript 开发者
在 Vue 3 中使用 Proxy 实现数据的双向绑定
【10月更文挑战第23天】Vue 3利用 `Proxy` 实现了数据的双向绑定,无论是使用内置的指令如 `v-model`,还是通过自定义事件或自定义指令,都能够方便地实现数据与视图之间的双向交互,满足不同场景下的开发需求。
28 1
|
12天前
|
缓存 JavaScript UED
Vue 中实现组件的懒加载
【10月更文挑战第23天】组件的懒加载是 Vue 应用中提高性能的重要手段之一。通过合理运用动态导入、路由配置等方式,可以实现组件的按需加载,减少资源浪费,提高应用的响应速度和用户体验。在实际应用中,需要根据具体情况选择合适的懒加载方式,并结合性能优化的其他措施,以打造更高效、更优质的 Vue 应用。
|
11天前
|
前端开发 JavaScript
简记 Vue3(一)—— setup、ref、reactive、toRefs、toRef
简记 Vue3(一)—— setup、ref、reactive、toRefs、toRef
|
12天前
Vue3 项目的 setup 函数
【10月更文挑战第23天】setup` 函数是 Vue3 中非常重要的一个概念,掌握它的使用方法对于开发高效、灵活的 Vue3 组件至关重要。通过不断的实践和探索,你将能够更好地利用 `setup` 函数来构建优秀的 Vue3 项目。
|
15天前
|
JavaScript 前端开发 API
vue3知识点:Vue3.0中的响应式原理和 vue2.x的响应式
vue3知识点:Vue3.0中的响应式原理和 vue2.x的响应式
23 0
|
13天前
|
数据采集 监控 JavaScript
在 Vue 项目中使用预渲染技术
【10月更文挑战第23天】在 Vue 项目中使用预渲染技术是提升 SEO 效果的有效途径之一。通过选择合适的预渲染工具,正确配置和运行预渲染操作,结合其他 SEO 策略,可以实现更好的搜索引擎优化效果。同时,需要不断地监控和优化预渲染效果,以适应不断变化的搜索引擎环境和用户需求。
下一篇
无影云桌面