UI组件库elementplus

29 阅读14分钟

官网:www.he-fan.cn/zh-CN/

1. 安装

npm install element-plus --save安装elementplus

npm install -d unplugin-vue-components unplugin-auto-import安装两个插件自动按需引入

2. vite.config.js集成插件

import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    AutoImport({
        resolvers: [ElementPlusResolver()],
    }),
    Components({
        resolvers: [ElementPlusResolver()],
    }),
  ],
  resolve: {
    alias: {
      '@'fileURLToPath(new URL('./src', import.meta.url))
    }
  },

// 代理服务器
    // server: {
    //  proxy: {
    //      '/api': {
    //          target: 'http://43.136.34.132:8088', // 目标
    //          changeOrigin: true,
    //          // rewrite: path => path.replace(/^\/api/, ''),
    //      },
    //  },
    // },
})

3.main.js引入elementplus样式,否则可能出现使用组件没效果。


import { createApp } from "vue";
import router from "./router";
import store from "./store";
import 'element-plus/dist/index.css';//引入elementplus样式
import App from "./App.vue";
import './permission.js' //登录认证

import ElementPlus from 'element-plus';//完整引入elementplus,文件大小会很大。可以考虑按需引入

const app = createApp(App);
app.use(router);
app.use(store);
 
app.use(ElementPlus);//完整引入elementplus,文件大小会很大。可以考虑按需引入
app.mount("#app");

4.项目中使用组件示例

  • Layout网格布局组件;
  • form表单组件、button按钮组件、icon图标组件、ElMessage消息提示组件;
  • container布局容器、image图片组件、面包屑导航组件、dropdown下拉菜单、menu菜单组件;
  • table表格组件、dialog弹框组件、upload上传组件、popconfirm气泡确认框、pagination分页组件。

Layout网格布局组件:(默认每行分成24个栅栏)

1行1列

<el-row>
<el-col :span=”24”></el-col>
</el-row>

1行2列,row组件的gutter属性指定列之间的间距,默认0

<el-row :gutter=”20”>
  <el-col :span=”12”></el-col>
  <el-col :span=”12”></el-col>
</el-row>

1行2列,col组件的offset属性指定列偏移

<el-row>
  <el-col :span=”6”></el-col>
  <el-col :span=”12” :offset=”6”></el-col>
</el-row>

1行3列,默认flex布局,

使用justify属性定义对齐方式start/end/center/speace-between/speace-around/space-evenly

<el-row :justisy=”center”>
  <el-col :span=”6”></el-col>
  <el-col :span=”6”></el-col>
  <el-col :span=”6”></el-col>
</el-row>

form表单、button按钮组件、icon图标、ElMessage消息提示

  • <el-from>rules定义验证规则,:rules绑定验证规则,:model绑定数据
  • <el-from-item>绑定具体规则(prop属性设为需验证的特殊键值)
  • 输入框v-model双向数据绑定
  • <el-button type=’success’>success</el-button>
  • <el-icon>

npm install@element-plus/icons-vue下载图标包管理器

import { User, Lock } from '@element-plus/icons-vue'引入图标

components: {

        User,

        Lock,

 },注册后使用

  • import { ElMessage } from 'element-plus'

ElMessage({

       message: '成功了!',

       type: 'success',

})

Login.vue

<template>
    <div class="g-container">
        <div class="g-wrapper">
            <h2>xx系统</h2>
            <el-form
                class="g-login"
                :rules="rules"
                :model="user"
                ref="loginFormRef"
            >
                <el-form-item prop="name">
                    <el-input placeholder="请输入用户名" v-model="user.name">
                        <template #prefix>
                            <el-icon><User /></el-icon>
                        </template>
                    </el-input>
                </el-form-item>
                <el-form-item prop="password">
                    <el-input
                        placeholder="请输入密码"
                        v-model="user.password"
                        show-password
                    >
                        <template #prefix>
                            <el-icon><Lock /></el-icon>
                        </template>
                    </el-input>
                </el-form-item>
                <el-button type="primary" @click="bindLogin">登录</el-button>
            </el-form>
        </div>
    </div>
</template>

<script>
import { User, Lockfrom '@element-plus/icons-vue'
import { RequestLoginfrom '@/api/index.js'
import { ElMessagefrom 'element-plus'

export default {
    components: {
        User,
        Lock,
    },

    data() {
        return {
            user: {
                name'root',
                password'root',
            },

            // 定义校验规则,在data选项中,
            // 在form-item中使用prop绑定规则
            // 双向数据绑定 v-model
            // form单表中使用 :model="user"
            rules: {
                name: [
                    {
                        requiredtrue,
                        message'请输入用户名',
                        trigger'blur',
                    },
                ],
                password: [
                    { requiredtrue, message'请输入密码', trigger'blur' },
                ],
            },
        }
    },

    methods: {
        bindLogin() {
            const { name, password } = this.user
            const formRef = this.$refs.loginFormRef
            formRef.validate(async valid => {
                // 1. 表单校验
                if (valid) {
                    // 2. 调用登录接口,验证账户
                    const data = await RequestLogin(name, password)
                    const { resultCode, resultInfo } = data
                    if (resultCode === 1) {
                        // 3. 保存用户昵称和头像,用于主界面显示
                        const userInfo = {
                            nick: resultInfo.nick,
                            headerimg: resultInfo.headerimg,
                        }
                        this.$store.dispatch('member/saveUser', userInfo)
                        // 4. 保存token
                        // eslint-disable-next-line no-undef
                        localStorage.setItem('TOKEN', token)
                        // 5. 跳转到主界
                        this.$router.push({ path: '/home' })
                    } else {
                        ElMessage({
                            message'账号出错!',
                            type'error',
                        })
                    }
                }
            })
        },
    },
}
</script>

<style lang="scss" scoped>
.g-container {
    width: 100%;
    height: 100vh;
    background-color: #2b3c4d;
    position: relative;
    .g-wrapper {
        position: absolute;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
        width: 400px;
        h2 {
            text-align: center;
            color: white;
            margin-bottom: 20px;
        }
        .g-login {
            background-color: white;
            border-radius: 5px;
            padding: 40px 30px;
            .el-button {
                width: 100%;
            }
        }
    }
}
</style>

story→index.js(vuex持久化存储插件,解决刷新用户信息丢失问题)

/* eslint-disable prettier/prettier */
import { createStore } from "vuex";
import member from "./modules/member.js";
import createPersistedState from "vuex-persistedstate";

const store = createStore({
  modules: {
    member,
  },

  // 集成插件
  plugins: [
    createPersistedState({
      storage: sessionStorage,
      key"storekey",
    }),
  ],
});
export default store;

story→modules→member.js


/* eslint-disable prettier/prettier */

const member = {
  namespacedtrue,
  // 内存中
  state: {
    // user:{name:'',password:''},
    usernull,
  },

  mutations: {
    SAVE_USER(state, _user) {
      state.user = _user; // 保存内存
      // localStorage.setItem('USER', JSON.stringify(_user)) // 持久化存储
    },
  },

  actions: {
    saveUser({ commit }, _user) {
      commit("SAVE_USER", _user);
    },
  },

  getters: {
    user(state) => state.user,
  },
};
export default member;

permission.js统一登录认证封装

import router from './router'
/**

 * 全局前置导航守卫
 *   路由router对象的一个方法 beforeEach
 */
// eslint-disable-next-line no-unused-vars

router.beforeEach((to, from) => {
    // 1. 加入白名单: 有些路由是不需要登录身份认证 path: /login ,  /

    // if (to.path === '/login') {
    //     return true //放行
    // }

    const whiteList = ['/login', '/']

    if (whiteList.includes(to.path)) {
        return true //放行
    }

    // 2. 登录认证,检查token

    let token = localStorage.getItem('TOKEN')

    if (token) {
        return true // 放行
    } else {
        // 如果不存在, 重定向到登录界面
        router.replace({ path'/login' })
        return false
    }
})

container布局容器(上中下布局、左右布局等)、image图片、面包屑导航、dropdown下拉菜单

  • <el-container>外层容器,子元素中含<el-header><el-footer>时全部子元素垂直上下排列,否则水平排列

  • <el-header>顶栏容器

  • <el-main>主要区域容器

  • <el-aside>侧边栏容器

  • <el-footer>底栏容器

  • <el-image :src="url"></el-image>

  • <el-breadcrumb separator-class="el-icon-arrow-right">

  • <el-breadcrumb-item :to="{ path: '' ,name:’’}">  

  • 收缩菜单功能:(图标改变、宽度改变)

<component :is="componentName"></component>动态组件切换图标

<el-menu :collapse="!isCollapse" >  通过 :collapse控制收缩(默认false打开,true收起)

 

  • <el-dropdown trigger=”click”>  trigger触发方式,默认移上去触发,也可设置为点击触发

<el-dropdown-menu>

<el-dropdown-item>

  • 可选链运算 ? (先判断前面的对象是否为真,如果为真,执行后面的点语法)

<p>欢迎您:{{ userInfo?.nick }}</p>

Home.vue

<template>

    <el-container>

        <!-- 左侧区域 -->

        <el-aside :width="asideWidth">

            <div class="g-title">

                <el-image :src="url"></el-image>

                <h3 v-show="isCollapse">xx管理</h3>

            </div>

            <p>首页</p>

            <Menu :isCollapse="isCollapse"></Menu>

        </el-aside>

        <!-- 右侧区域 -->

        <el-container>

            <!-- 头部区域 -->

            <el-header>

                <el-icon size="25" @click="bindCollapse">

                    <!-- <Fold v-if="isCollapse" @click="bindCollapse" />

                  <Expand v-else @click="bindCollapse"/>  -->

                    <component :is="componentName"></component>

                </el-icon>

 

                <div>

                    <el-dropdown>

                        <div class="g-header-r">

                           <!-- 可选链运算 ?. 先判断前面的对象是否为真,如果为真执行点语法-->

                            <p>欢迎您:{{ userInfo?.nick }}</p>

                            <el-image :src="userInfo?.headerimg"></el-image>

                            <!-- <p>欢迎您:{{ userInfo?userInfo.nick:'' }}</p>

                            <el-image :src="userInfo?userInfo.headerimg:''"></el-image> -->

                        </div>

                        <template #dropdown>

                            <el-dropdown-menu>

                                <el-dropdown-item>个人中心</el-dropdown-item>

                                <el-dropdown-item>切换用户</el-dropdown-item>

                                <el-dropdown-item @click="bindExit"

                                    >退出登录</el-dropdown-item

                                >

                            </el-dropdown-menu>

                        </template>

                    </el-dropdown>

                </div>

            </el-header>

            <!-- 内容区域 -->

            <el-main>

                <!-- 面包屑导航 -->

               <BreadCrumb></BreadCrumb>

                <!-- 子路由输出 -->

                <router-view></router-view>

            </el-main>

        </el-container>

    </el-container>

</template>

<script>

import Menu from '@/components/Menu.vue'

import BreadCrumb from '@/components/BreadCrumb.vue'

import { Fold, Expandfrom '@element-plus/icons-vue'

export default {

    components: {

        Menu,

        Fold,

        Expand,

        BreadCrumb,

    },

    data() {

        return {

            url'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg',

            isCollapsetrue,

            asideWidth'200px',

            componentNameFold,

        }

    },

    computed: {

        userInfo() {

            return this.$store.getters['member/user']

        },

    },

    methods: {

        bindCollapse() {

            this.isCollapse = !this.isCollapse

            this.asideWidththis.isCollapse'200px' : '70px'

            this.componentNamethis.isCollapseFoldExpand

        },

        bindExit() {

            localStorage.remove('TOKEN')//退出登录清除token

            this.$router.replace({ path'/login' })

        },

    },

}

</script>

<!-- eslint-disable prettier/prettier -->

<style lang="scss" scoped>

.el-container {

    width: 100%;

    height: 100vh;

 

    .el-aside {

        background-color: #2d3436;

        color: white;

        transition: 0.5s;

        .g-title {

            display: flex;

            margin: 20px;

        }

        .el-image {

            width: 25px;

            height: 25px;

        }

        p {

            margin-left: 20px;

        }

        .el-menu {

            border-right: 0;

        }

    }

    .el-container {

        .el-header {

            display: flex;

            justify-content: space-between;

            padding: 10px 20px;

            background-color: #7f8fa6;

            height: 60px;

            .g-header-r {

                display: flex;

                p {

                    color: white;

                    // padding-top: 10px;

                }

                .el-image {

                    width: 30px;

                    height: 30px;

                }

            }

        }

        .el-breadcrumb {

            margin: 10px 0;

        }

    }

}

</style>

BreadCrumb.vue面包屑导航组件化

<!-- eslint-disable prettier/prettier -->
<template>
    <el-breadcrumb separator="/">
        <el-breadcrumb-item :to="{ path: '/index' }">首页</el-breadcrumb-item>
        <el-breadcrumb-item v-for="(item,index) in navigateList" :key="index" :to="{ path: item.path }">{{item.title}}</el-breadcrumb-item>
    </el-breadcrumb>
</template>

<!-- eslint-disable prettier/prettier -->
<script>
export default{
    data(){
        return{
            navigateList:[]
        }
    },

    watch:{
        // 路由变化,更新面包屑导航
        //侦听$route
        //获取matched数组中元素,更新面包屑导航
        $route(value){
            if(value.path === '/index'){
                this.navigateList = []
                return
            }
            // console.log(this.matched);
            // [{path:'/home1',title:'产品管理'},{path:'/product/list',title:'产品列表'},]
            const navigateList = value.matched.map(item =>{
                return{path:item.path,title:item.meta.title}
            })
            console.log(navigateList);
            this.navigateList = navigateList
        }
    }
}
</script>
<!-- eslint-disable prettier/prettier -->
<style lang="scss" scoped></style>

menu菜单组件:

<el-menu>菜单

<el-sub-menu index="1">二级菜单

<el-menu-item index="1-1">菜单项

Menu.vue

<template>

    <el-menu

        active-text-color="#ffd04b"

        background-color="#2d3436"

        text-color="#fff"

        :collapse="!isCollapse"

    >

        <el-sub-menu index="1">

            <template #title>

                <el-icon><Location /></el-icon> <span>产品管理</span>

            </template>

            <el-menu-item index="1-1">

                <el-icon><Handbag /></el-icon>

                <router-link to="/product/list">产品列表</router-link>

            </el-menu-item>

            <el-menu-item index="1-2">

                <el-icon><ReadingLamp /></el-icon>

                <router-link to="/product/category">产品分类</router-link>

            </el-menu-item>

            <el-menu-item index="1-3">

                <el-icon><ReadingLamp /></el-icon>

                <router-link to="/product/map">产品地图</router-link>

            </el-menu-item>

        </el-sub-menu>

 

        <el-sub-menu index="2">

            <template #title>

                <el-icon><OfficeBuilding /></el-icon><span>账户管理</span>

            </template>

            <el-menu-item index="2-1"

                ><el-icon><Mic /></el-icon

                ><router-link to="/account/list"

                    >账户列表</router-link

                ></el-menu-item

            >

            <el-menu-item index="2-2"

                ><el-icon><Camera /></el-icon

                ><router-link to="/account/add"

                    >账户添加</router-link

                ></el-menu-item

            >

        </el-sub-menu>

        <el-sub-menu index="3">

            <template #title>

                <el-icon><OfficeBuilding /></el-icon><span>OA管理</span>

            </template>

            <el-menu-item index="3-1"

                ><el-icon><Mic /></el-icon

                ><router-link to="/log/list"

                    >日志列表</router-link

                ></el-menu-item

            >

            <el-menu-item index="3-2"

                ><el-icon><Camera /></el-icon

                ><router-link to="/log/reply"

                    >日志回复</router-link

                ></el-menu-item

            >

            <el-menu-item index="3-3"

                ><el-icon><Camera /></el-icon

                ><router-link to="/log/add"

                    >日志添加</router-link

                ></el-menu-item

            >

        </el-sub-menu>

        <!-- <el-sub-menu index="1">

          <template #title>

              <el-icon><Location /></el-icon> <span>产品管理</span>

          </template>

          <el-menu-item

              :index="index"

              v-for="(menu, index) in menuList"

              :key="index"

              ><el-icon><Handbag /></el-icon

              ><router-link :to="menu.path">{{

                  menu.meta.title

              }}</router-link></el-menu-item

          >

      </el-sub-menu> -->

    </el-menu>

</template>

<script>

import {

    Location,

    Handbag,

    ReadingLamp,

    Camera,

    Mic,

    OfficeBuilding,

} from '@element-plus/icons-vue'

export default {

    props: {

        isCollapseBoolean,

    },

    components: { Location, Handbag, ReadingLamp, Camera, Mic, OfficeBuilding },

    computed: {

        menuList() {

            const list = this.$router.options.routes

            const menu = list[2].children

            const list1 = menu.filter(item => item.meta.hidden)

            return list1

        },

    },

}

</script>

<style lang="scss" scoped></style>

router→index.js

import { createRouter, createWebHistory } from 'vue-router'

import Home from '../views/Home.vue'

import Login from '../views/Login.vue'

import Index from '../views/Index.vue'

 

const router = createRouter({

    history: createWebHistory(import.meta.env.BASE_URL),

    routes: [

        {

            path: '/',

            redirect: '/login',

        },

        {

            path: '/login',

            component: Login,

        },

        {

            path: '/home',

            component: Home,

            redirect: '/index',

            children: [

                {

                    path: '/index',

                    component: Index,

                    meta: { hidden: false },

                },

            ],

        },

         // 产品管理模块

        {

            path: '/home1',

            meta: { title: '产品管理' },

            component: Home,

            children: [

                {

                    path: '/product/list',

                    component: () =>

                        import('../views/plateform/product/list.vue'),

                    meta: { hidden: true, title: '产品列表' },

                },

                {

                    path: '/product/category',

                    component: () =>

                        import('../views/plateform/product/category.vue'),

                    meta: { hidden: true, title: '产品分类' },

                },

                {

                    path: '/product/map',

                    component: () =>

                        import('../views/plateform/product/map.vue'),

                    meta: { hidden: true, title: '产品地图' },

                },

            ],

        },

        // 系统设置

        {

            path: '/home2',

            meta: { title: '账户管理' },

            component: Home,

            children: [

                {

                    path: '/account/list',

                    component: () =>

                        import('../views/plateform/account/list.vue'),

                    meta: { hidden: true, title: '账户列表' },

                },

                {

                    path: '/account/add',

                    component: () =>

                        import('../views/plateform/account/add.vue'),

                    meta: { hidden: true, title: '添加账户' },

                },

            ],

        },

        // oa管理

        {

            path: '/home3',

            meta: { title: 'OA管理' },

            component: Home,

            children: [

                {

                    path: '/log/list',

                    component: () => import('../views/plateform/log/list.vue'),

                    meta: { hidden: true, title: '日志管理' },

                },

                {

                    path: '/log/reply',

                    component: () => import('../views/plateform/log/reply.vue'),

                    meta: { hidden: true, title: '日志回复' },

                },

                {

                    path: '/log/add',

                    component: () => import('../views/plateform/log/add.vue'),

                    meta: { hidden: true, title: '添加日志' },

                },

            ],

        },

        // 404路由不存在匹配,放在路由最下面

        {

            path: '/:pathMatch(.*)*',

            component: () => import('@/views/NotPage.vue'),

        },

    ],

})

export default router

table表格组件、dialog弹框组件、upload上传组件、popconfirm气泡确认框组件、pagination分页组件

  • <el-table :data="goodsList" @selection-chanege=”onchange”>@selection-change拿到复选框选中的数据(批量删除)
  • <el-table-column prop=”date” label=”Date” type=”selection”>表格列,prop对应键名填入数据,label表格列名,type表格复选框(批量删除)
  • <el-dialog v-model=”” ttitle=””>设v-mode接收boolean,true是显示弹框
  • <el-upload>图片上传
  • <el-popconfirm>气泡确认框
  • <el-pagination>分页

增删改查功能

List.vue

<!-- eslint-disable prettier/prettier -->
<template>

    <div> 

         <!-- 搜索 -->

        <el-row :gutter="20" style="margin-bottom: 10px">

            <el-col :span="4" :offset="0">

                <el-input v-model="product.name" placeholder="名称搜索" clearable></el-input>

            </el-col>

            <el-col :span="4" :offset="0">

                <el-input v-model="product.shop" placeholder="店铺名称搜索" clearable></el-input>

            </el-col>

            <el-col :span="4" :offset="0">

                <el-input v-model="product.startPrice" placeholder="开始价格搜索" clearable></el-input>

            </el-col>

            <el-col :span="4" :offset="0">

                <el-input v-model="product.endPrice" placeholder="结束价格搜索" clearable></el-input>

            </el-col>

            <el-col :span="4" :offset="0">

                <el-button type="primary" @click="bindSearch">搜索产品</el-button>

            </el-col>

 

        </el-row>

        <el-button-group>

            <el-button type="success" size="small" @click="bindAddGood">添加</el-button>

            <el-button type="success" size="small" @click="bindRefresh">刷新</el-button>

            <el-button type="success" size="small" @click="bindBatchDelete">批量删除</el-button>

            <el-button type="success" size="small" @click="bindExcelExport">导出excel</el-button>

        </el-button-group>

        <!-- 表格 -->

        <el-table :data="goodsList" style="width: 100%" @selection-change="handleSelectionChange">

            <el-table-column type="selection" ></el-table-column>

            <el-table-column label="序列号" prop="id" width="100"></el-table-column>

            <el-table-column label="名称" prop="product"></el-table-column>

            <el-table-column label="店铺名称" prop="shop"></el-table-column>

            <el-table-column label="图片">

                <template #default="scope">

                    <!-- <el-image

                        :src="scope.row.picture?.indexOf('http') === -1 ? 'http://10.7.163.142:8089/' + scope.row.picture : scope.row.picture"

                        style="width: 100px; height: 100px"></el-image> -->

                    <el-image :src="filterUrl(scope.row.picture)" style="width: 100px; height: 100px"></el-image>

                </template>

            </el-table-column>

            <el-table-column label="价格" prop="price"></el-table-column>

            <el-table-column label="类型" prop="categoryname"></el-table-column>

            <el-table-column label="操作">

                <template #default="scope">

                    <el-button type="success" size="small" @click="bindEdit(scope.row)">编辑</el-button>

                    <!-- <el-button type="primary" size="small" @click="bindDelete(scope.row.id)">删除</el-button> -->

                    <el-popconfirm title="确认要删除此记录吗?" @confirm="bindDelete(scope.row.id)" confirm-button-text="Yes"

                        cancel-button-text="No">

                        <template #reference>

                            <el-button type="primary" size="small">删除</el-button>

                        </template>

                    </el-popconfirm>

                </template>

            </el-table-column>

        </el-table>

        <!-- 分页 -->

        <el-pagination background layout="total, sizes, prev, pager, next,jumper" :total="total"

            :page-sizes="[5, 10, 20]" @size-change="handleSizeChange" @current-change="handleCurrentChange" />

        <!-- total-总记录条数

        sizes-选择每页几条

        :page-sizes="[5,10,20]"

        jumper-跳转

        @size-change="handleSizeChange"//page-size 改变时触发

         -->

        <!-- 弹出对话框 -->

        <el-dialog :title="type === 'ADD' ? '添加产品' : '编辑产品'" v-model="dialogGoodsFormVisible" width="40%">

            <GoodsDialog v-if="dialogGoodsFormVisible" @close="bindClose" :goods="goods" :type="type"></GoodsDialog>

        </el-dialog>

    </div>

</template>

<!-- eslint-disable prettier/prettier -->

<script>

import { RequestShopList, RequestDeleteGoods, RequestBatchDeletefrom '@/api/index.js'

import GoodsDialog from '@/components/GoodsDialog.vue'

import { ElMessagefrom 'element-plus'

import { excelExport2 } from '@/utils/xlsxutil.js'

export default {

    components: {

        GoodsDialog,

    },

    data() {

        return {

            // tableData: [

            //     { id:1,name: '回锅肉', price: 30, category: '荤', url: 'https://image5.suning.cn/uimg/b2c/newcatentries/0000000000-000000000834870991_1_800x800.jpg' },

            //     { id:2,name: '土豆丝', price: 20, category: '素', url: 'https://image5.suning.cn/uimg/b2c/newcatentries/0000000000-000000000834870991_1_800x800.jpg' },

            // ]

            goodsList: [],

            dialogGoodsFormVisiblefalse,

            total'',//总记录条数

            pageSize5,//每页记录条数

            pageNo1,//当前页号

            type'ADD',//EDIT 编辑 ADD 添加

            goodsnull,

            product: {

                name'',//搜索产品名称

                shop'',//搜索店铺名称

                price'',//搜索价格

                startPrice'',

                endPrice'',

            },

            ids'',//删除商品id集合

        }

    },

    created() {

        this.getShopList()

    },

    methods: {

        filterUrl(url) {

            return url?.indexOf('http') === -1 ? 'http://10.7.163.142:8089/' + url : url

        },

        /**

         * 产品列表

         */

        async getShopList() {

            const data = await RequestShopList(this.pageSize, this.pageNo, this.product.name, this.product.shop, this.product.startPrice, this.product.endPrice)

            const { resultCode, resultInfo } = data

            if (resultCode === 1) {

                this.goodsList = resultInfo.list

                this.total = resultInfo.total// 总记录条数

            }

        },

        /**

         * 编辑产品

         */

        async bindEdit(row) {

            // console.log('row ', row)

            this.goods = row

            this.type'EDIT'

            this.dialogGoodsFormVisibletrue

        },

        /**

         * 删除商品

         */

        async bindDelete(id) {

            const data = await RequestDeleteGoods(id)

            const { resultCode } = data

            if (resultCode === 1) {

                ElMessage({

                    message'删除成功',

                    type'success',

                })

                this.getShopList()

            }

        },

        /**

         * 添加-弹出添加表单对话框

         */

        bindAddGood() {

            this.type'ADD'

            this.dialogGoodsFormVisibletrue

        },

        /**

        * 页大小改变事件

        */

        handleSizeChange(value) {

            this.pageSize = value

            this.getShopList()

        },

        /**

         * 页号改变事件

         */

        handleCurrentChange(value) {

            this.pageNo = value

            this.getShopList()

        },

        /**

         * 刷新

         */

        bindRefresh() {

            this.product = {}//重置搜索数据

            this.getShopList()

        },

        bindClose() {

            this.dialogGoodsFormVisiblefalse

            this.getShopList()

        },

        /**

         * 搜索产品

         */

        bindSearch() {

            this.getShopList()

        },

        /*

        多选

         */

        handleSelectionChange(value) {

            // console.log('value ', value) // [{id:10,name:''}] => [10,12,34] => '10,12,34'

            const list = value.map(item => item.id)

            const ids = list.join(',')

            this.ids = ids

        },

        /*

        批量删除

         */

        async bindBatchDelete() {

            if (this.ids?.split(',').length <= 0) {

                ElMessage({

                    message'请选择删除产品',

                    type'info',

                })

                return

            }

            const data = await RequestBatchDelete(this.ids)

            const { resultCode } = data

            if (resultCode === 1) {

                ElMessage({

                    message'批量删除成功',

                    type'success',

                })

                this.getShopList()

            }

        },

        /**

         * 导出excel

         */

         bindExcelExport() {

            excelExport2(

                this.goodsList,

                {

                    id'序列号',

                    product'产品名称',

                    shop'店铺名称',

                    picture'图片',

                    price'价格',

                    oldprice'原价',

                    categoryname'类型名称',

                },

                '产品列表'

            )

        },

    }

}

</script>

<!-- eslint-disable prettier/prettier -->

<style lang="scss" scoped>

</style>

GoodsDialog.vue

<!-- eslint-disable prettier/prettier -->

<template>

    <el-form :model="goodForm" ref="goodFormRef" :rules="rules" label-width="80px">

        <el-form-item label="店铺名称">

            <el-input v-model="goodForm.shop"></el-input>

        </el-form-item>

        <el-form-item label="产品名称" prop="product">

            <el-input v-model="goodForm.product"></el-input>

        </el-form-item>

        <el-form-item label="产品分类">

            <el-select v-model="goodForm.categoryId" clearable placeholder="选择产品分类">

                <el-option v-for="item in category" :key="item.id" :label="item.name" :value="item.id" />

            </el-select>

        </el-form-item>

        <el-form-item label="现价">

            <el-input v-model="goodForm.price"></el-input>

        </el-form-item>

        <el-form-item label="原价">

            <el-input v-model="goodForm.oldprice"></el-input>

        </el-form-item>

        <el-form-item label="图片">

            <!--

                list-type: picture-card 卡片样式

                action: 图片上传url地址

                auto-upload: 是否自动上传,true自动上传,选中图片直接上传到action指定url

                show-file-list: false单文件上传,true支持多文件上传

                before-upload: 上传前回调, 当前auto-upload为true时执行,回调函数中返回false终止上传

             -->

            <!-- <el-input v-model="goodForm.picture"></el-input> -->

            <el-upload list-type="picture-card" action="#" :auto-upload="true" :show-file-list="false"

                :before-upload="beforeAvatarUpload">

                <img v-if="imageUrl" :src="imageUrl" class="avatar" />

                <el-icon v-else>

                    <Plus />

                </el-icon>

            </el-upload>

        </el-form-item>

        <el-form-item label="上下架">

            <el-radio-group v-model="goodForm.putaway">

                <el-radio :label="1">上架</el-radio>

                <el-radio :label="0">下架</el-radio>

            </el-radio-group>

        </el-form-item>

        <el-form-item>

            <el-button type="primary" @click="onSubmit">确定</el-button>

            <el-button type="primary" @click="onClose">关闭</el-button>

        </el-form-item>

    </el-form>

</template>

<!-- eslint-disable prettier/prettier -->

<script>

import {

    RequestCategoryList,

    RequestAddGoods,

    RequestUpdateGoods,

} from '@/api/index.js'

import { ElMessagefrom 'element-plus'

import { Plusfrom '@element-plus/icons-vue'

export default {

    components: {

        Plus,

    },

    props: ['goods', 'type'],

    emits: ['close'],

    data() {

        return {

            goodForm: {

                shop'',

                product'',

                price'',

                oldprice'',

                picture'',

                putaway1,

                categoryId'',

            },

            category: [

                // { label: '海鲜', value: 1 },

                // { label: '川湘菜', value: 2 },

                // { label: '日韩料理', value: 3 },

            ],

            rules: {

                product: [

                    {

                        requiredtrue,

                        message'请输入产品名称',

                        trigger'blur',

                    },

                ],

            },

            imageUrl'', //图片预览地址

            imageFilenull, //上传图片文件

        }

    },

    created() {

        if (this.type === 'EDIT') {

            this.goodForm = { ...this.goods }

            this.imageUrlthis.filterUrl(this.goodForm.picture)

        }

        this.getCategoryList()

    },

    methods: {

        filterUrl(url) {

            return url?.indexOf('http') === -1

                ? 'http://10.7.163.142:8089/' + url

                : url

        },

        /**

         * 分类列表

         */

        async getCategoryList() {

            const data = await RequestCategoryList()

            const { resultCode, resultInfo } = data

            if (resultCode === 1) {

                this.category = resultInfo.list

            }

        },

        /**

         * 保存产品

         *  图片上传http注意事项

         *     1. post请求

         *     2. 请求头header 上传内容类型 content-type:multipart/form-data

         *         headers: { 'Content-Type': 'multipart/form-data' },

         *     3. FormData

         *         const formData = new FormData()

         *         formData.append('product',product)

         *         formData.append('picture',file)

         */

        async onSubmit() {

            // if (this.type === 'ADD') {

            //     // console.log('this.goodForm ', this.goodForm)

            //     const formData = new FormData()

            //     formData.append('categoryId', this.goodForm.categoryId)

            //     formData.append('product', this.goodForm.product)

            //     formData.append('picture', this.imageFile)

            //     formData.append('shop', this.goodForm.shop)

            //     formData.append('price', this.goodForm.price)

            //     formData.append('oldprice', this.goodForm.oldprice)

            //     formData.append('putaway', this.goodForm.putaway)

            //     const data = await RequestAddGoods(formData)

            //     const { resultCode } = data

            //     if (resultCode === 1) {

            //         ElMessage({

            //             message: '添加产品成功!',

            //             type: 'success',

            //         })

            //     }

            //     this.$emit('close')

            // } else {

            //     const formData = new FormData()

            //     formData.append('id', this.goodForm.id)

            //     formData.append('categoryId', this.goodForm.categoryId)

            //     formData.append('product', this.goodForm.product)

            //     formData.append('picture', this.imageFile)

            //     formData.append('shop', this.goodForm.shop)

            //     formData.append('price', this.goodForm.price)

            //     formData.append('oldprice', this.goodForm.oldprice)

            //     formData.append('putaway', this.goodForm.putaway)

            //     const data = await RequestUpdateGoods(formData)

            //     const { resultCode } = data

            //     if (resultCode === 1) {

            //         ElMessage({

            //             message: '编辑产品成功!',

            //             type: 'success',

            //         })

            //     }

            //     this.$emit('close')

            // }

            // console.log('this.goodForm ', this.goodForm)

            const formData = new FormData()

            formData.append('categoryId', this.goodForm.categoryId)

            formData.append('product', this.goodForm.product)

            formData.append('picture', this.imageFile)

            formData.append('shop', this.goodForm.shop)

            formData.append('price', this.goodForm.price)

            formData.append('oldprice', this.goodForm.oldprice)

            formData.append('putaway', this.goodForm.putaway)

            // 添加  编辑

            let data

            if (this.type === 'EDIT') {

                formData.append('id', this.goodForm.id)

                data = await RequestUpdateGoods(formData)

            } else {

                data = await RequestAddGoods(formData)

            }

            const { resultCode } = data

            if (resultCode === 1) {

                ElMessage({

                    messagethis.type === 'EDIT' ? '编辑产品成功!' : '添加产品成功!',

                    type'success',

                })

            }

            this.onClose()

        },

        onClose() {

            this.$emit('close')

        },

        /**

         * 文件上传之前执行

         */

        beforeAvatarUpload(rawFile) {

            const arr = ['image/jpeg', 'image/png', 'image/jpg']

            // 图片格式验证

            if (!arr.includes(rawFile.type)) {

                ElMessage({

                    message'上传图片格式不正确!!',

                    type'error',

                })

                return false

            }

            // 图片大小验证

            if (rawFile.size1024 / 1024 > 2) {

                ElMessage({

                    message'上传图片大小不能超过2M!',

                    type'error',

                })

                return false

            }

            // 图片预览

            //1. 选中的本地图片转成Base64编码 赋值给 imageUrl

            //2. FileReader 读文件

            this.imageUrlURL.createObjectURL(rawFile)

            // 上传图片

            this.imageFile = rawFile

            return false // 不向下执行

        },

    },

}

</script>

<!-- eslint-disable prettier/prettier -->
<style lang="scss" scoped>

.avatar {
    width: 170px;
    height: 170px;
    display: block;
}

</style>

util→request.js

import axios from 'axios'

import { ElMessage } from 'element-plus'

 

const instance = axios.create({

    baseURL'http://10.7.163.142:8089', //服务器根地址

    // baseURL: 'http://127.0.0.1:5173', //服务器根地址

    timeout3000, //超时时间

})

/**

 * 请求拦截器

 */

instance.interceptors.request.use(

    config => {

        // 请求拦截处理

        // console.log('请求拦截处理 >>> ', config)

        const token = localStorage.getItem('TOKEN')

        if(token){

            config.headers['Authorization'] = token

        }

        // token && config.headers['Authorization'] = 'token'

        return config

    },

    error => {

        // 请求出错处理

        return Promise.reject(error)

    }

)

/**

 * 响应拦截器

 */

instance.interceptors.response.use(

    response => {

        // 响应拦截处理

        // console.log('响应拦截器处理 >>> ', response)

        // return response

        return response.data

    },

    error => {

        const { response } = error

        if (response) {

            const status = response.status

            switch (status) {

                case 404:

                    // console.log('资源不存在 404')

                    ElMessage({

                        message'资源不存在 404',

                        type'error',

                    })

                    break

                case 401:

                    // console.log('Unauthorized 身份验证凭证缺失!')

                    ElMessage({

                        message'Unauthorized 身份验证凭证缺失!',

                        type'error',

                    })

                    break

                case 403:

                    // console.log('403 Forbidden - 拒绝访问!')

                    ElMessage({

                        message'403 Forbidden - 拒绝访问!',

                        type'error',

                    })

                    break

                case 500:

                    // console.log('服务器出错!')

                    ElMessage({

                        message'服务器出错!',

                        type'error',

                    })

                    break

                default:

                    // console.log('出现异想不到的错误!')

                    ElMessage({

                        message'出现异想不到的错误!',

                        type'error',

                    })

                    break

            }

        } else {

            // 说明服务器连结果都没有返回,可能的原因有两种:

            /**

             * 1. 服务器崩掉了

             * 2. 前端客户端断网状态

             */

            if (!window.navigator.onLine) {

                // 判断为断网,可以跳转到断网页面

                // console.log('网络不可用,请检查您的网络连接!')

                ElMessage({

                    message'网络不可用,请检查您的网络连接!',

                    type'error',

                })

                return

            } else {

                // console.log('连接服务端出错!' + error?.message)

                ElMessage({

                    message'连接服务端出错!' + error?.message,

                    type'error',

                })

                return Promise.reject(error)

            }

        }

        return Promise.reject(error)

    }

)

export default instance

api→index.js

/* eslint-disable prettier/prettier */

import instance from "@/utils/request.js";

/**

 * 登录接口

 * @param {*} username

 * @param {*} password

 * @returns

 */

export const RequestLogin = (username, password) => {

  return instance({

    method"post",

    url"/api/login",

    //post请求参数使用data选项, get参数 params选项

    data: {

      username,

      password,

    },

  });

};

 

/**

 * 商品列表接口

 * @returns

 */

export const RequestShopList = (pageSize,pageNo,productKey,shopKey,startPrice,endPrice) => {

  return instance({

    method"get",

    url"/api/shop",

    //post请求参数使用data选项, get参数 params选项

    params:{

      pageSize,

      pageNo,

      productKey,//商品名

      shopKey,//产品名

      startPrice,

      endPrice

    }

  });

};

/**

 * 商品分类接口

 * @returns

 */

export const RequestCategoryList = () => {

  return instance({

    method"get",

    url"/api/category",

  });

};

/**

 * 添加产品

 */

export const RequestAddGoods = (formData)=>{

  // const token = localStorage.getItem('TOKEN')

  return instance({

      method:'post',

      url:'/api/shop/insert',

      headers: { 'Content-Type''multipart/form-data' },

      data:formData

  })

}

/**

 * 删除产品

 */

export const RequestDeleteGoods = (id)=>{

  // const token = localStorage.getItem('TOKEN')

  return instance({

      method:'get',

      url:'/api/shop/delete',

      params:{

        id

      }

  })

}

/**

 * 编辑产品

 */

export const RequestUpdateGoods = (formData)=>{

  // const token = localStorage.getItem('TOKEN')

  return instance({

      method:'post',

      url:'/api/shop/edit',

      headers: { 'Content-Type''multipart/form-data' },

      data:formData

  })

}

/**

 * 批量删除产品

*  ids: 商品id '1,2,3,4'

 */

export const RequestBatchDelete = (ids)=>{

  // const token = localStorage.getItem('TOKEN')

  return instance({

      method:'get',

      url:'/api/shop/batchdelete',

      params:{

        ids

      }

  })

}