发布时间:2023-06-08 11:30
1:什么是后台管理系统项目?
注意:前端领域当中,开发后台管理系统项目,并非是java、php等后台语言项目。
build
mock
node_modules
public
src - 程序员源代码存储的地方
------api文件夹:涉及请求相关的
------assets文件夹:里面放置一些静态资源(一般共享的),放在aseets文件夹里面静态资源,在webpack打包的时候,会进行编译
------components文件夹:一般放置非路由组件获取全局组件
------icons这个文件夹的里面放置了一些svg矢量图
------layout文件夹:他里面放置一些组件与混入
------router文件夹:与路由相关的
-----store文件夹:一定是与vuex先关的
-----style文件夹:与样式先关的
------utils文件夹:request.js是axios二次封装文件****
------views文件夹:里面放置的是路由组件
-------------------------------------------
App.vue:根组件
main.js:入口文件
permission.js:与导航守卫先关、
settings:项目配置项文件
.env.development
.env.producation
background: url(~@/assets/deadlift.jpg);
background-size: 100% 100%;
书写API (store -> modules -> user.js)
一般是先在vuex中书写相应的api如:actions,commit等 然后在具体想用的组件中进行dispatch操作
axios二次封装
二次封装的时候,有些内容我们也要进行修改,如服务器返回的接口,有时候返回真实的结果可能是200,也可能是20000
if (res.code !== 20000 && res.code != 200) { ... }
// 引入axios(axios进行了二次封装操作)
import request from '@/utils/request'
// 对外暴露接口接口函数
export function login(data) {
return request({
url: '/admin/acl/index/login',
method: 'post',
data
})
}
// 对外暴露用户信息的函数
export function getInfo(token) {
return request({
url: '/admin/acl/index/info',
method: 'get',
params: { token }
})
}
export function logout() {
return request({
url: '/admin/acl/index/logout',
method: 'post'
})
}
换成真实的url接口,以及method方法也是需要进行更改操作
在换入接口以后还需要解决跨域问题 – 在vue.config.js文件中进行配置
devServer: {
port: port,
open: true,
overlay: {
warnings: false,
errors: true
},
proxy: {
'/dev-api': {
// 数据来自于哪一台代理服务器 就使用哪一台代理服务器即可
target: 'http://39.98.123.211:8170',
pathRewrite: { '^/dev-api': '' }
},
}
// before: require('./mock/mock-server.js')
// 配置代理跨域
},
proxy: {
'/dev-api': {
// 数据来自于哪一台代理服务器 就使用哪一台代理服务器即可
target: 'http://39.98.123.211:8170',
pathRewrite: { '^/dev-api': '' }
},
}
整个路由的项目框架都是依赖于Layout外部框架
/* 整个项目的框架 */
import Layout from '@/layout'
路由组件的配置是通过懒加载的形式进行的 - component:箭头函数的形式
component: () => import('@/views/login/index'),
需要注意的是,二级路由是放在Children属性下面的
export const constantRoutes = [
{
path: '/login',
component: () => import('@/views/login/index'),
hidden: true
},
{
path: '/404',
component: () => import('@/views/404'),
hidden: true
},
{
path: '/',
component: Layout,
// 访问 / 会进入到整个大的一个结构,但是它会立马重定向到它的二级路由
redirect: '/dashboard',
children: [{
path: 'dashboard',
name: 'Dashboard',
component: () => import('@/views/dashboard/index'),
meta: { title: 'Dashboard', icon: 'dashboard' }
}]
},
/*
在搭建路由的时候,在总体框架 component: Layout,中书写完全以后,
组件的内容就在其中了
*/
{
path: '/product',
// 我们在进行搭建组件的时候都是在layout组件下面进行的
component: Layout,
name: 'Product',
meta: { title: '商品管理', icon: 'el-icon-goods' },
children: [
{
path: 'trademark',
name: 'Trademark',
component: () => import('@/views/product/tradeMark'),
meta: { title: '品牌管理' }
},
{
path: 'attr',
name: 'Attr',
component: () => import('@/views/product/Attr'),
meta: { title: '平台属性管理' }
},
{
path: 'spu',
name: 'Spu',
component: () => import('@/views/product/Spu'),
meta: { title: 'Spu管理' }
},
{
path: 'sku',
name: 'Sku',
component: () => import('@/views/product/Sku'),
meta: { title: 'Sku管理' }
}
]
},
// 404 page must be placed at the end !!!
{ path: '*', redirect: '/404', hidden: true }
]
然后对外暴露一个箭头函数,而且这个箭头函数一致性会返回一个路由器对象
const createRouter = () => new Router({
// mode: 'history', // require service support
scrollBehavior: () => ({ y: 0 }),
routes: constantRoutes
})
重置路由操作,了解
// 重置路由操作
export function resetRouter() {
const newRouter = createRouter()
router.matcher = newRouter.matcher // reset router
}
export default router // 对外暴露
首先项目图,product下应该有很多的二级路由 先创建二级路由组件
<!-- 像element-ui组件库里面的内容可以直接书写 -->
<el-button type="primary" icon="el-icon-plus" style="margin: 10px 0px"
>添加</el-button
>
其中,任何标签中还可以书写相应的样式 style标签,形式如下
style="margin: 10px 0px"
<!--
表格组件
- data:表格组件将来要展示的数据 --- 数组类型
- border:表格的边框
- el-table-column :代表一列
- label:显示的标题
- width:对应列的宽度
- align:标题的对齐方式
-->
<el-table :data="data" style="width: 100%" border>
<el-table-column prop="prop" label="序号" width="80px" align="center">
</el-table-column>
<el-table-column prop="prop" label="品牌名称" width="width">
</el-table-column>
<el-table-column prop="prop" label="品牌Logo" width="width">
</el-table-column>
<el-table-column prop="prop" label="操作" width="width">
</el-table-column>
</el-table>
<!--
分页器
当前第几页、数据总条数、每一页展示的条数、连续页码数
@size-change="handleSizeChange"
@current-change="6"
- current-page 当前第几页
- total 代表分页器一共需要展示数据条数
- page-size 代表每一页需要展示多少条数据
- page-sizes 可以设置每一页展示多少条数据
- layout 可以实现分页器的布局
- pager-count 按钮的数量 如果9 连续页码数是7
- 都是el-pagination里面的属性内容 是包裹在el-pagination标签内部的,注意结束位置
-->
<el-pagination
style="margin-top:20px;textAlign:center"
:current-page="6"
:total="99"
:pager-count='9'
:page-size="3"
:page-sizes="[3, 5, 10]"
layout="prev, pager, next, jumper, ->, sizes, total">
</el-pagination>
书写相关的API接口
/*
将四个模块请求的接口统一对外暴露
*/
import * as trademark from './product/tradeMark'
import * as attr from './product/attr'
import * as spu from './product/spu'
import * as sku from './product/sku'
// 对外暴露 kv一致
export default {
trademark,
attr,
spu,
sku
}
/*
引入相关API请求接口
组件实例的原型的原型指向的是Vue.prototype
任意组件可以使用API相关的接口
*/
import API from '@/api'
Vue.prototype.$API = API
proxy: {
/* '/dev-api': {
// 数据来自于哪一台代理服务器 就使用哪一台代理服务器即可
target: 'http://39.98.123.211:8170',
pathRewrite: { '^/dev-api': '' }
}, */
'/dev-api/admin/acl': {
// 数据来自于哪一台代理服务器 就使用哪一台代理服务器即可
target: 'http://39.98.123.211:8170',
pathRewrite: { '^/dev-api': '' }
},
'/dev-api/admin/product': {
// 数据来自于哪一台代理服务器 就使用哪一台代理服务器即可
// 配置了多个跨域问题
target: 'http://39.98.123.211:8510',
pathRewrite: { '^/dev-api': '' }
},
}
获取数据在组件内部静态展示
data() {
return {
// 分页器第几页
page: 1,
// 当前页数展示数据条数
limit: 3,
// 总共数据条数
total: 0,
// 列表展示的数据
list: [],
};
},
// 解构出参数
const { page, limit } = this;
async getPageList(pager = 1) {
this.page = pager
// 解构出参数
const { page, limit } = this;
// 获取品牌列表的接口,需要带两个参数(在data中声明)
let result = await this.$API.trademark.reqTradeMarkList(page, limit);
// console.log(result);
if(result.code == 200) {
this.total = result.data.total
this.list = result.data.records
}
},
handleCurrentChange(pager) {
// 修改参数
this.page = pager
this.getPageList()
}
// 当分页器某一页需要展示数据条数发生变化的时候出现
handleSizeChange(limit) {
// console.log(limit)
// 整理参数,再次发送请求
this.limit = limit
this.getPageList()
}
// 点击添加品牌的按钮
showDialog() {
// 显示对话框
this.dialogFormVisible = true;
// 为了防止用于点击取消,然后点进去又有可以每次进去都清楚数据
this.tmForm = {
tmName: "",
logoUrl: "",
};
},
this.dialogFormVisible = true;
<el-dialog title="收货地址" :visible.sync="dialogFormVisible">
async addOrUpdateTradeMark() {
this.dialogFormVisible = false
// 发请求(添加品牌|修改品牌)
let result = await this.$API.trademark.reqAddOrUpdateTradeMark(this.tmForm)
if(result.code == 200) {
// 弹出一个信息框:添加品牌成功、修改品牌成功
this.$message(this.tmForm.id ? '修改品牌成功' : '添加品牌成功')
// 成功以后,将数据进行展示即可
this.getPageList()
}
}
this.$message()
@click="updateTradeMark(row)"
updateTradeMark(row) {
// console.log(row) row是整个品牌的对象 - 用于选中的品牌的信息
// 显示对话框
this.dialogFormVisible = true;
/*
将已有的品牌信息赋值给tmForm进行展示
将服务器返回的品牌信息,直接赋值给了tmForm进行展示
也就是说tmForm存储的即为服务器返回的品牌信息
获取到的对象,进行浅拷贝操作 {...row}
*/
// this.tmForm = row;
this.tmForm = { ...row };
},
需要注意的是: 有时候获取到的对象是和v-model的属性绑定了的 需要用到浅拷贝,这样修改此处不会影响到别的地方
// 表单验证规则
rules: {
/*
required 必须要验证字段
message 提示信息
trigger 用户行为设置(事件的设置blur change)
min max 字段长度
*/
// 品牌名称的验证规则
tmName: [
{ required: true, message: "请输入品牌名称", trigger: "blur" },
{
min: 2,
max: 10,
message: "长度在 2 到 10 个字符",
trigger: "change",
},
],
// 品牌logo的验证规则
logoUrl: [
{
required: true,
message: "请选择品牌的图片区域",
trigger: "change",
},
],
},
<!-- form表单
:model属性 - 这个属性的作用是 把表单的数据收集到那个对象身上,将来表单验证,也需要这个属性
-->
<el-form style="width: 80%" :model="tmForm" :rules="rules" ref="ruleForm">
addOrUpdateTradeMark() {
// 当全部验证字段通过 再去书写业务逻辑
this.$refs.ruleForm.validate(async (success) => {
if (success) {
// 如果全部字段符合条件
this.dialogFormVisible = false;
// 发请求(添加品牌|修改品牌)
let result = await this.$API.trademark.reqAddOrUpdateTradeMark(
this.tmForm
);
if (result.code == 200) {
// 弹出一个信息框:添加品牌成功、修改品牌成功
// this.$message(this.tmForm.id ? "修改品牌成功" : "添加品牌成功");
this.$message({
type: "success",
message: this.tmForm.id ? "修改品牌成功" : "添加品牌成功",
});
// 成功以后,将数据进行展示即可
// 如果添加品牌,停留在第一页,修改品牌应该停留在当前页面
this.getPageList(this.tmForm.id ? this.page : 1);
}
} else {
console.log("Submit Error!!!");
return false;
}
});
},
/*
删除品牌的信息
删除品牌 /admin/product/baseTrademark/remove/{id} delete
在书写方法的时候 中间是有参数的
*/
export const reqDeleteTradeMark = (id) => request({
url: `/admin/product/baseTrademark/remove/${id} `,
method: 'delete'
})
@click="deleteTradeMark(row)"
// 删除品牌的操作
deleteTradeMark(row) {
this.$confirm(`你确定删除${row.tmName}?`, "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(async () => {
// 当用户点击确定按钮的时候会触发
// 向服务器发请求
let result = await this.$API.trademark.reqDeleteTradeMark(row.id);
if (result.code == 200) {
this.$message({
type: "success",
message: "删除成功!",
});
// 再次获取列表数据 但是展示数据的页码数需要发生变化
this.getPageList(this.list.length>1 ? this.page : this.page-1)
}
})
.catch(() => {
// 当用户点击取消按钮的时候会触发
this.$message({
type: "info",
message: "已取消删除",
});
});
},
this.getPageList(this.list.length>1 ? this.page : this.page-1)
import CategorySelect from '@/components/CategorySelect'
// 注册全局组件 因为一开始名字中用到了,所以直接用名字来进行注册操作
Vue.component(CategorySelect.name, CategorySelect)
<template>
<div>
<el-card style="margin: 20px 0px">
<!-- 子给父传数据用自定义事件 -->
<CategorySelect @getCategoryId="getCategoryId"></CategorySelect>
</el-card>
<el-card></el-card>
</div>
</template>
注意: 子给父传数据我们用的是自定义事件
<div>
<!--
inline:代表的是行内表单,代表一行可以放置多个表单元素
-->
<el-form :inline="true" :model="cForm" class="demo-form-inline">
<el-form-item label="一级分类">
<!-- select双向绑定收集到的是value -->
<el-select
placeholder="请选择"
v-model="cForm.category1Id"
@change="handler1"
>
<el-option
:label="c1.name"
:value="c1.id"
v-for="(c1, index) in list1"
:key="c1.id"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="二级分类">
<el-select
placeholder="请选择"
v-model="cForm.category2Id"
@change="handler2"
>
<el-option
:label="c2.name"
:value="c2.id"
v-for="(c2, index) in this.list2"
:key="c2.id"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="三级分类">
<el-select
placeholder="请选择"
v-model="cForm.category3Id"
@change="handler3"
>
<el-option
:label="c3.name"
:value="c3.id"
v-for="(c3, index) in list3"
:key="c3.id"
></el-option>
</el-select>
</el-form-item>
</el-form>
</div>
/*
平台属性管理模块请求文件
*/
import request from "@/utils/request"
/*
获取一级分类数据接口
/admin/product/getCategory1 get
*/
export const reqCategory1List = () =>request({
url:'/admin/product/getCategory1',
method:'get',
})
/*
获取二级分类数据接口
/admin/product/getCategory2/{category1Id} get
*/
export const reqCategory2List = (category1Id) =>request({
url:`/admin/product/getCategory2/${category1Id}`,
method:'get',
})
/*
获取三级分类数据接口
/admin/product/getCategory2/{category1Id} get
*/
export const reqCategory3List = (category2Id) =>request({
url:`/admin/product/getCategory3/${category2Id}`,
method:'get',
})
因为组件内部需要收集到相应的数据进行展示 - 且数据收集时候的初始值都是[] 分类的id数量有多个可以放在一个对象中
data() {
return {
// 一级分类的数据
list1: [],
// 二级分类的数据
list2: [],
// 三级分类的数据
list3: [],
// 收集相应的一级二级三级分类的id
cForm: {
category1Id: "",
category2Id: "",
category3Id: "",
},
};
},
// 组件挂载完毕,向服务器发请求,获取相应的一级分类的数据
mounted() {
// 获取一级分类的数据的方法
this.getCategory1List();
},
methods: {
// 获取一级分类数据的方法
async getCategory1List() {
// 获取一级分类的请求,不需要携带参数
let result = await this.$API.attr.reqCategory1List();
// console.log(result)
// 不使用vuex来操作数据的话,就需要通过在data属性中设置相应的数据来进行·保存
if (result.code == 200) {
this.list1 = result.data;
}
},
/*
一级分类的select事件的回调
- 当一级分类的option发生变化的时候获取相应二级分类的数据
*/
async handler1() {
this.list2 = [];
this.list3 = [];
this.cForm.category2Id = "";
this.cForm.category3Id = "";
// console.log(111)
// 解构出一级分类的id
const { category1Id } = this.cForm;
/* 为了区分开传的到底是几id 可以进行相应的打标第操作 */
// this.$emit("getCategoryId", category1Id);
this.$emit("getCategoryId", { categoryId: category1Id, level: 1 });
// 通过一级分类的id 获取二级分类的数据
let result = await this.$API.attr.reqCategory2List(category1Id);
// console.log(result);
if (result.code == 200) {
this.list2 = result.data;
}
},
/*
二级分类的select事件的回调
- 当二级分类的option发生变化的时候获取相应三级分类的数据
*/
async handler2() {
this.list2 = [];
this.cForm.category3Id = "";
const { category2Id } = this.cForm;
this.$emit("getCategoryId", { categoryId: category2Id, level: 2 });
let result = await this.$API.attr.reqCategory3List(category2Id);
if (result.code == 200) {
this.list3 = result.data;
}
},
// 三级分类的事件回调
handler3() {
const { category3Id } = this.cForm;
this.$emit("getCategoryId", { categoryId: category3Id, level: 3 });
},
},
父组件需要用到子组件中的数据来进行相应展示 - 父组件发送请求,主要是需要用到子组件传递过来的三级分类的id
因为书写API接口方法需要用到,因为在子组件中触发自定义函数用到了level来区分具体是哪一个级别分类的id
父组件Attr
methods: {
/*
自定义事件的回调
*/
getCategoryId({ categoryId, level }) {
// 通过level来区别是几级id
if (level == 1) {
this.category1Id = categoryId;
this.category2Id = "";
this.category3Id = "";
} else if (level == 2) {
this.category2Id = categoryId;
this.category3Id = "";
} else {
// 代表三级分类已经有了
this.category3Id = categoryId;
// 发请求获取品牌属性
this.getAttrList()
}
},
// 获取平台属性的数据
getAttrList() {
console.log('发请求')
}
},
/*
获取平台属性的接口
/admin/product/attrInfoList/{category1Id}/{category2Id}/{category3Id}
*/
export const reqAttrList = (category1Id, category2Id, category3Id) => request({
url: `/admin/product/attrInfoList/${category1Id}/${category2Id}/${category3Id}`,
method: 'get'
})
获取数据来进行展示
async getAttrList() {
// console.log('发请求')
// 获取分类的id
const { category1Id, category2Id, category3Id } = this;
let result = await this.$API.attr.reqAttrList(
category1Id,
category2Id,
category3Id
);
// console.log(result)
if (result.code == 200) {
this.attrList = result.data;
}
},
// 接收平台属性的字段
attrList: [],
将上述获取到的属性 展示到 el-card组件当中即可
<el-card>
<div v-show="isShowTable">
<el-button
type="primary"
icon="el-icon-plus"
:disabled="!category3Id"
@click="isShowTable = false"
>添加属性</el-button
>
<!-- 表格 展示平台属性 -->
<el-table style="width: 100%" border :data="attrList">
<el-table-column
type="index"
prop="prop"
label="序号"
width="80"
align="center"
>
</el-table-column>
<el-table-column prop="attrName" label="属性名称" width="150">
</el-table-column>
<el-table-column prop="prop" label="属性值列表" width="width">
<template slot-scope="{ row, $index }">
<el-tag
type="success"
v-for="(attrValue, index) in row.attrValueList"
:key="attrValue.id"
style="margin: 0 10px"
>{{ attrValue.valueName }}</el-tag
>
</template>
</el-table-column>
<el-table-column prop="prop" label="操作" width="150">
<template slot-scope="{ row, $index }">
<el-button
type="warning"
icon="el-icon-edit"
size="mini"
@click="isShowTable = false"
></el-button>
<el-button
type="danger"
icon="el-icon-delete"
size="mini"
></el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 添加属性 | 修改属性的结构 -->
<!-- 一个进行展示,一个不进行展示,进行取反即可 -->
<div v-show="!isShowTable">
<el-form ref="form" :inline="true" label-width="80px" :model="attrInfo">
<el-form-item label="属性名">
<el-input
placeholder="请输入属性名"
v-model="attrInfo.attrName"
></el-input>
</el-form-item>
</el-form>
<el-button type="primary" icon="el-icon-plus" @click="addAttrValue" :disabled="!attrInfo.attrName">添加属性值</el-button>
<el-button @click="isShowTable = true">取消</el-button>
<el-table
style="width: 100%; margin: 20px 0px"
border
:data="attrInfo.attrValueList"
>
<el-table-column
type="index"
prop="prop"
label="序号"
width="80"
align="center"
>
</el-table-column>
<el-table-column prop="prop" label="属性值名称" width="width">
<template slot-scope="{row, $index}">
<el-input
v-model="row.valueName"
placeholder="请输入属性值名称"
size="mini"
></el-input>
</template>
</el-table-column>
<el-table-column prop="prop" label="操作" width="width">
<template slot-scope="{row, $index}">
<el-button type="danger" icon="el-icon-delete" size="mini"></el-button>
</template>
</el-table-column>
</el-table>
<el-button type="primary">保存</el-button>
<el-button @click="isShowTable = true">取消</el-button>
</div>
</el-card>
**el-table中是以列属性来进行展示的,**其中el-table-column是用来展示每一列的属性的
**点击添加或者修改按钮以后,**会跳转展示到另一个界面当中, 使用 v-show来完成即可 且通过isShowTable来控制完成
<div v-show="!isShowTable">
同样的有内部有内容框 每一个el-table-column中有el-input时候就需要用到作用域插槽来实现该效果
<el-table-column prop="prop" label="属性值名称" width="width">
<template slot-scope="{row, $index}">
<el-input
v-model="row.valueName"
placeholder="请输入属性值名称"
size="mini"
></el-input>
</template>
</el-table-column>
收集属性名首先也是一样的,需要先书写接口函数 - 当有多个接口的时候,我们可以用一个data对象来进行表示
/*
添加属性与属性值接口
/admin/product/saveAttrInfo post
写data,用一个对象来表示数据即可
data:
"attrName":"" 属性名
"attrValueList":[ 属性名中属性值,因为属性值可以是多个,因此需要的是数组
{
"attrId":0, // 属性的ID
"valueName":'string' // 相应的属性值
}
],
"categoryId":0, category3Id
"categoryLevel":3
*/
export const reqAddAttr = (data) => request({
url: "/admin/product/saveAttrInfo",
method: 'post',
data
})
在组件中进行调用的时候,不用vuex以后就需要在data中设置相应的数据来进行接收
attrInfo: {
attrName: "", // 属性名
attrValueList: [
// 属性值
/* {
attrId: 0, // 相应的属性名的id
valueName: "",
}, */
],
categoryId: 0, // 三级分类的id
categoryLevel: 3, // 因为服务器也需要区分是几级Id
},
一开始是不应该有内容的,只有每次点击了才会加上相应的内容,所以在data中,一开始attrValueList中是不应该有内容的
添加属性值
// 添加属性值的按钮
addAttrValue() {
// 向属性值的数组里面添加元素
this.attrInfo.attrValueList.push({
attrId:undefined,
valueName:'',
})
}
**点击取消以后,我们需要对数据进行取消操作 ** 清除数据,this.attrInfo直接进行赋值操作即可,里面相应的内容设为空即可
但是我们在添加商品以后,需要设置商品的id即可
// 清除数据
this.attrInfo = {
attrName: "", // 属性名
attrValueList: [
// 属性值
/* {
attrId: 0, // 相应的属性名的id
valueName: "",
}, */
],
// categoryId: 0, // 三级分类的id 点击的那一刻 3级分类的id是可以拿到的
categoryId: this.category3Id,
categoryLevel: 3, // 因为服务器也需要区分是几级Id
};
**修改某一个属性的时候,先切换页面 在将attrInfo的数据进行相应的替换操作
// 修改某一个属性
updateAttr(row) {
// console.log(row)
this.isShowTable = false;
// 将选中的属性,赋值给attrInfo
// 由于数据结构当中存在对象里面套数组 数组里面套对象 因此需要使用深拷贝解决这类问题
// 深拷贝 浅拷贝在面试的时候出现的频率很高,切记达到手写深拷贝和浅拷贝
this.attrInfo = cloneDeep(row);
},
// 添加属性值的按钮
addAttrValue() {
// 向属性值的数组里面添加元素
this.attrInfo.attrValueList.push({
// attrId: undefined,
/*
对于修改某一个属性的时候
可以在已有的属性值基础之上新增新的属性值
- 新增属性值的时候,需要把已有的属性的id带上
此处直接解决了两个地方,添加的时候没有id就undefined
修改的时候有id 就直接添加上即可
*/
attrId: this.attrInfo.id,
valueName: "",
/*
flag属性
- 给每一个属性值添加一个标记flag
- 用户切换查看模式与编辑模式
- 好处:每一个属性值可以控制自己的模式切换
- 当前的flag属性是响应式数据(数据变化视图跟着变化)
- 因为flag本质上是attrValueList上面的,在data中有定义过
*/
flag: true,
});
},
el-input与span之间的显示与隐藏通过 v-if 以及 v-else来解决处理即可
<template slot-scope="{ row, $index }">
<!--
这里的结构用到了span与input进行来回切换操作
-->
<el-input
v-model="row.valueName"
placeholder="请输入属性值名称"
size="mini"
v-if="row.flag"
@blur="toLook(row)"
@keyup.native.enter="toLook(row)"
></el-input>
<span v-else @click="row.flag = true" style="display:block">{{ row.valueName }}</span>
</template>
失去焦点的事件
// 失去焦点的事件,切换为查看模式 显示span
toLook(row) {
row.flag = false
}
// 如果属性值为空 不能作为新的属性值 需要给用户提示 让他输入一个其他属性值
if (row.valueName.trim() == "") {
this.$message("请输入一个正常的属性值:");
return;
}
// 失去焦点的事件,切换为查看模式 显示span
toLook(row) {
// 如果属性值为空 不能作为新的属性值 需要给用户提示 让他输入一个其他属性值
if (row.valueName.trim() == "") {
this.$message("请输入一个正常的属性值:");
return;
}
// 新增的属性值,不能与已有的属性值重复
let isRepat = this.attrInfo.attrValueList.some((item) => {
// 需要将row从数组里面判断的时候去除
/*
row是最新的属性值【数组的最后一项元素】
判断的时候,需要把已有的数组当中新增的这个属性值去除
*/
if (row !== item) {
return row.valueName == item.valueName;
}
});
// console.log(isRepat)
if (isRepat) return;
// row是当前用户添加的最新的属性值
// console.log(row)
// 当前的编辑模式变为查看模式【让input消失,显示span】
row.flag = false;
},
this.attrInfo.attrValueList.forEach((item) => {
// 这样书写也可以给属性值添加flag属性,但是会发现视图不会跟着变化(因为flag不是响应式数据)
// item.flag = false 之前用put是可以探测到的,但是这里是给一个对象进行操作
/*
上述:因为vue无法探测普通的新增property,这样书写的属性并非响应式属性
响应式属性(数据变化视图跟着变化)
Vue.set 向响应式
参数一:对象
参数二:添加新的响应式属性
参数三:添加新的响应式属性的属性值
*/
this.$set(item, "flag", false);
});
点击span自动变成编辑模式
@click="toEdit(row, $index)"
// 点击span的回调,变为编辑模式
toEdit(row, index) {
row.flag = true;
// 获取input节点 实现自动聚焦
// console.log(this.$refs[index])
/*
- 获取input节点,实现自动聚焦
- 需要注意:点击span的时候,切换为input变为编辑模式
但是需要注意:对于浏览器而言页面的重绘会会消耗时间
- 点击span的时候,重绘一个input是需要耗费时间的,不可能一点击span就立马获取到input
*/
this.$nextTick(() => {
// 获取相应的input表单元素实现聚焦
this.$refs[index].focus();
});
},
// 添加添加按钮以后 自动聚焦操作
this.$nextTick(() => {
this.$refs[this.attrInfo.attrValueList.length - 1].focus();
});
<template slot-scope="{ row, $index }">
<!--
这里的结构用到了span与input进行来回切换操作
-->
<el-input
v-model="row.valueName"
placeholder="请输入属性值名称"
size="mini"
v-if="row.flag"
@blur="toLook(row)"
@keyup.native.enter="toLook(row)"
:ref="$index"
></el-input>
<span
v-else
@click="toEdit(row, $index)"
style="display: block"
>{{ row.valueName }}</span
>
</template>
其中删除属性我们用的是 el-popconfirm 标签
<el-popconfirm
:title="`确定删除${row.valueName}?`"
@onConfirm="deleteAttrValue($index)"
>
<el-button
type="danger"
icon="el-icon-delete"
size="mini"
slot="reference"
></el-button>
</el-popconfirm>
其中确认删除也是有固定的 点击事件 onConfirm – 因为我们确认删除是需要知道具体删除的是哪一个事件
书写相应的点击事件 – 需要传入相应的参数,即为点击属性的相应索引值
deleteAttrValue(index) {
// alert(111)
// 当前删除属性值的操作是不需要发请求的
this.attrInfo.attrValueList.splice(index, 1);
},
当用户在输入框中输入完毕以后,我们需要将数据进行保存的操作
// 保存按钮 进行添加属性或修改属性的操作
async addOrUpdateAttr() {
// alert(111)
/*
整理参数
- 1、如果用户添加很多属性值,且属性值为空的不应该提交给服务器
- 2、提交给服务器数据中不应该出现flag字段
*/
this.attrInfo.attrValueList = this.attrInfo.attrValueList.filter(
(item) => {
// 过滤掉属性值不是空的
if (item.valueName != "") {
// 删除掉flag属性
delete item.flag;
return true;
}
}
);
// 向服务器发送请求来保存数据
// 成功干什么 失败干什么 可以使用try...catch
try {
// 发请求
await this.$API.attr.reqAddOrUpdateAttr(this.attrInfo);
// 展示平台属性的信号量进行切换
this.isShowTable = true;
// 提示消息
this.$message({
type: "success",
message: "保存成功",
});
// 再次调用服务器全新的数据来进行调用展示全新的数据
this.getAttrList();
} catch (error) {
/*
有很多数据是服务器提供的数据,是不支持进行修改的
系统数据进行修改会发生报错
*/
this.$message("保存失败");
}
},
父亲给儿子传递事件直接使用props进行操作即可
通过下面属性值的输入,来控制上面三级联动的效果, 只需传入isShowTable属性即可
<!-- 子给父传数据用自定义事件 -->
<CategorySelect
@getCategoryId="getCategoryId"
:show="!isShowTable"
></CategorySelect>
子组件CategorySelect直接进行接受即可 然后在每一个下拉框中加上disabled属性即可
<el-select
placeholder="请选择"
v-model="cForm.category1Id"
@change="handler1"
:disabled="show"
>
<el-card style="margin: 20px 0px">
<!-- 三级联动已经是全局组件了 -->
<CategorySelect
@getCategoryId="getCategoryId"
:show="!show"
></CategorySelect>
</el-card>
/*
0代表展示SPU列表数据,1
1添加SPU|修改SPU
2添加SKU
*/
scene: 2,
参数 需要三个,其中如果参数个数在路径中没有体现出来的话,可以通过params参数来进行携带操作
// 1、先引入我们二次封装的axios
import request from '@/utils/request'
// 2、获取SPU列表数据的接口
/*
/admin/product/{page}/{limit} get操作
参数:page、limit、category3Id
*/
export const reqSpuList = (page, limit, category3Id) => request({
url: `/admin/product/${page}/${limit}`,
method: 'get',
// 还有一个参数在路径中没有体现出来,可以通过params参数进行携带
params: { category3Id }
})
async getSpuList() {
// 解构出函数需要的三个参数
const { page, limit, category3Id } = this;
/*
携带三个参数
1、page 第几页
2、limit 每一页需要展示多少条数据
3、三级分类id
*/
let result = await this.$API.spu.reqSpuList(page, limit, category3Id);
// console.log(result);
if (result.code == 200) {
this.total = result.data.total;
this.records = result.data.records;
}
},
在从服务器获取数据的时候,像Spu列表数据,总条数等都需要在data中预先定义出来
data() {
return {
// 分类的id
category1Id: "",
category2Id: "",
category3Id: "",
// 控制三级联动的可是测试性
show: true,
page: 1, // 分页器当前第几页
limit: 3, // 分页器每一页需要展示多少条数据
records: [], // spu列表的数据
total: 0, // 分页器一共需要展示数据的条数
/*
0代表展示SPU列表数据,1
1添加SPU|修改SPU
2添加SKU
*/
scene: 2,
};
},
handleCurrentChange(page) {
this.page = page;
this.getSpuList();
},
handleSizeChange(limit) {
// 修改参数
this.limit = limit;
// 再次发送请求
this.getSpuList();
},
因为此处有多个状态需要进行展示,为了方便出来可以拆分为子组件
然后在父组件中,通过v-show里面的scene值来决定是否进行展示该组件状态
<SpuForm v-show="scene == 1"></SpuForm>
<SkuForm v-show="scene == 2"></SkuForm>
有很多按钮的时候,我们可以封装一个hint-button全局组件
<template>
<a :title="title" style="margin:10px">
<el-button v-bind="$attrs" v-on="$listeners"></el-button>
</a>
</template>
<script>
export default {
name: "HintButton",
props: ["title"],
};
</script>
<style>
</style>
import HintButton from '@/components/HintButton'
Vue.component(HintButton.name, HintButton)
然后在Spu组件中进行使用即可
<template slot-scope="{ row, $index }">
<!-- 这里按钮将来用hintButton替换 -->
<hint-button
type="success"
icon="el-icon-plus"
size="mini"
title="添加sku"
></hint-button>
<hint-button
type="warning"
icon="el-icon-edit"
size="mini"
title="修改spu"
></hint-button>
<hint-button
type="info"
icon="el-icon-info"
size="mini"
title="查看当前spu全部sku列表"
></hint-button>
<hint-button
type="danger"
icon="el-icon-delete"
size="mini"
title="删除spu"
></hint-button>
</template>
想要鼠标放上去显示相应的内容,传入自己想要的title值即可
<el-form ref="form" label-width="80px">
<el-form-item label="Spu名称">
<el-input placeholder="Spu名称"></el-input>
</el-form-item>
<el-form-item label="品牌">
<el-select placeholder="请选择品牌" value="">
<el-option label="label" value="value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="Spu描述">
<el-input type="textarea" rows="4" placeholder="描述"></el-input>
</el-form-item>
<el-form-item label="Spu图片">
<el-upload
action="https://jsonplaceholder.typicode.com/posts/"
list-type="picture-card"
:on-preview="handlePictureCardPreview"
:on-remove="handleRemove"
>
<i class="el-icon-plus"></i>
</el-upload>
<el-dialog :visible.sync="dialogVisible">
<img width="100%" :src="dialogImageUrl" alt="" />
</el-dialog>
</el-form-item>
<el-form-item label="销售属性">
<el-select placeholder="还有3未选择" value="">
<el-option label="label" value="value"></el-option>
</el-select>
<el-button type="primary" icon="el-icon-plus">添加销售属性</el-button>
<el-table style="width: 100%" border>
<el-table-column
type="index"
prop="prop"
label="序号"
width="80"
align="center"
>
</el-table-column>
<el-table-column prop="prop" label="属性名" width="width">
</el-table-column>
<el-table-column prop="prop" label="属性名称列表" width="width">
</el-table-column>
<el-table-column prop="prop" label="操作" width="width">
</el-table-column>
</el-table>
</el-form-item>
<el-form-item>
<el-button type="primary">保存</el-button>
<el-button @click="$emit('changeScene', 0)">取消</el-button>
</el-form-item>
</el-form>
<SpuForm v-show="scene == 1" @changeScene="changeScene" ref="spu"></SpuForm>
// 修改某一个Spu
updateSpu(row) {
this.scene = 1
// 在修改某一个子组件的时候,完全可以获取一个子组件的相关内容
// console.log(this.$refs.spu) // VueComponent
// 在父组件中可以获取到子组件,那么对于子组件的数据和方法都可以拿到
// 在父组件中可以通过$ref获取子组件等等
this.$refs.spu.initSpuData(row)
},
/*
3、获取Spu信息
/admin/product/getSpuById/{SpuId} get
*/
export const reqSpu = (spuId) => request({
url: `/admin/product/getSpuById/${spuId}`,
method: 'get'
})
/*
4、获取品牌的信息
/admin/product/baseTrademark/getTrademarkList
*/
export const reqTradeMarkList = () => request({
url: "/admin/product/baseTrademark/getTrademarkList",
method: 'get',
})
/*
5、获取Spu图片的请求
/admin/product/spuImageList/{spuId} get
*/
export const reqSpuImageList = (spuId) => request({
url: `/admin/product/spuImageList/${spuId}`,
method: 'get'
})
/*
6、获取平台全部的销售属性 整个平台销售属性一共就三个
/admin/product/baseSaleAttrList
*/
export const reqBaseSaleAttrList = () => request({
url:"/admin/product/baseSaleAttrList",
method:"get",
})
/*
3、获取Spu信息
/admin/product/getSpuById/{SpuId} get
*/
export const reqSpu = (spuId) => request({
url: `/admin/product/getSpuById/${spuId}`,
method: 'get'
})
/*
4、获取品牌的信息
/admin/product/baseTrademark/getTrademarkList
*/
export const reqTradeMarkList = () => request({
url: "/admin/product/baseTrademark/getTrademarkList",
method: 'get',
})
/*
5、获取Spu图片的请求
/admin/product/spuImageList/{spuId} get
*/
export const reqSpuImageList = (spuId) => request({
url: `/admin/product/spuImageList/${spuId}`,
method: 'get'
})
/*
6、获取平台全部的销售属性 整个平台销售属性一共就三个
/admin/product/baseSaleAttrList
*/
export const reqBaseSaleAttrList = () => request({
url:"/admin/product/baseSaleAttrList",
method:"get",
})
// 初始化SpuForm数据
async initSpuData(spu) {
// console.log('发数据', spu)
// 获取Spu信息的数据
let spuResult = await this.$API.spu.reqSpu(spu.id);
// console.log(spuResult);
if(spuResult.code == 200) {
this.spu = spuResult.data
}
// 获取品牌的信息
let tradeMarkResult = await this.$API.spu.reqTradeMarkList()
// console.log(tradeMarkResult)
if(tradeMarkResult.code == 200) {
this.tradeMarkList = tradeMarkResult.data
}
// 获取spu图片的数据
let spuImageResult = await this.$API.spu.reqSpuImageList(spu.id)
// console.log(spuImageResult)
if(spuImageResult.code == 200) {
this.spuImageList = spuImageResult.data
}
// 获取平台全部的销售属性
let saleResult = await this.$API.spu.reqBaseSaleAttrList()
if(saleResult.code == 200) {
this.saleAttrList = saleResult.data
}
},
data() {
return {
dialogImageUrl: "",
dialogVisible: false,
spu: {}, // 存储SPU信息属性
tradeMarkList:[], // 存储品牌信息
spuImageList:[], // 存储Spu图片的数据
saleAttrList:[], // 销售属性的数据
};
},
我们在展示数据的时候,还需要将数据同步收集
<el-form-item label="Spu图片">
<!--
上传图片
action:图片上传的地址
list-type 文件列表的类型
:on-preview 图片预览的时候触发
:on-remove 照片删除的时候触发
- 对于照片墙的使用,我们也需特别清楚一点就是
- 照片墙需要字段的名称分别是imgName、imgUrl
-->
<el-upload
action="/dev-api/admin/product/fileUpload"
list-type="picture-card"
:on-preview="handlePictureCardPreview"
:on-remove="handleRemove"
:file-list="spuImageList"
>
<i class="el-icon-plus"></i>
</el-upload>
<el-dialog :visible.sync="dialogVisible">
<img width="100%" :src="dialogImageUrl" alt="" />
</el-dialog>
</el-form-item>
// 获取spu图片的数据
let spuImageResult = await this.$API.spu.reqSpuImageList(spu.id);
// console.log(spuImageResult)
if (spuImageResult.code == 200) {
// this.spuImageList = spuImageResult.data;
// 可以将数据进行处理,然后在返回给数组进行接收操作
let listArr = spuImageResult.data;
/*
由于照片墙显示图片的数据需要数组,
数组里面的元素需要有name和url字段
需要把服务器返回的数据进行修改再赋予新的值
*/
listArr.forEach((item) => {
item.name = item.imgName;
item.url = item.imgUrl;
});
// 把整理好的数据赋值给一开始设置好的初始数据
this.spuImageList = listArr;
}
<el-select placeholder="请选择品牌" v-model="spu.tmId">
<!-- label属性是用来展示用,value是用来收集信息用的 -->
<el-option
:label="tm.tmName"
:value="tm.id"
v-for="(tm, index) in tradeMarkList"
:key="tm.id"
></el-option>
</el-select>
el-tag数据进行展示
这一类用到el-tag组件,这一类内容都用到作用域插槽来进行
<template slot-scope="{ row, $index }">
<el-tag
:key="tag.categoryId"
v-for="tag in row.spuSaleAttrValueList"
closable
:disable-transitions="false"
@close="handleClose(tag)"
>
{{ tag.saleAttrValueName }}
</el-tag>
<!-- el-input结构可以看成是el-input和span的切换操作 -->
<el-input
class="input-new-tag"
v-if="row.inputVisible"
v-model="row.inputValue"
ref="saveTagInput"
size="small"
@keyup.enter.native="handleInputConfirm"
@blur="handleInputConfirm"
>
</el-input>
<el-button
v-else
class="button-new-tag"
size="small"
@click="showInput"
>+ New Tag</el-button
>
</template>
同时我们还需要很注意,我们在进行收集用户输入的数据的时候,需要在data中创建初始数据的形态
spu: {
//三级分类的id
category3Id: 0,
//描述
description: "",
//spu名称
spuName: "",
//平台的id
tmId: 0,
//收集SPU图片的信息
spuImageList: [],
//平台属性与属性值收集
spuSaleAttrList: [],
},
/*
整个平台的销售属性就三个
- 颜色、尺寸、版本 saleAttrList
- 当前Spu拥有的属于自己的销售属性 Spu.spuSaleAttrList -- 颜色
- 数组过滤的方法,可以从已有的数据当中过滤出用户需要的元素,并返回一个新的数据
- 对于filter而言,我们需要返回的是boolean值
*/
filter - 返回真表示 过滤出来的元素,我们是需要的
every- 数组的过滤方法::可以从已有的数组当中过滤出用户需要的元素,并返回一个新的数据
过滤出来的结果需要用一个全新的字段来进行接收
// 计算出还未选择的销售属性
unSelectSaleAttr() {
/*
整个平台的销售属性就三个
- 颜色、尺寸、版本 saleAttrList
- 当前Spu拥有的属于自己的销售属性 Spu.spuSaleAttrList -- 颜色
- 数组过滤的方法,可以从已有的数据当中过滤出用户需要的元素,并返回一个新的数据
- 对于filter而言,我们需要返回的是boolean值
*/
let result = this.saleAttrList.filter((item) => {
// filter 返回真表示 过滤出来的元素,我们是需要的
// every数组的过滤方法:可以从已有的数组当中过滤出用户需要的元素,并返回一个新的数据
// 过滤出来的结果就是一个数组
return this.spu.spuSaleAttrList.every(item1 => {
return item.name != item1.saleAttrName
})
})
// return '二哈'
// 对于一个计算属性肯定要有一个返回值
return result
}
照片墙何时收集数据
照片墙进行上传的时候,需要用到我们特定的地址
action="/dev-api/admin/product/fileUpload"
file-list属性是用于存放照片墙需要展示的数据 照片墙需要展示的数据有name和url属性
:file-list="spuImageList"
同时对于照片墙我们有一个照片预览和移除的功能,我们需要写上相应的回调
:on-preview="handlePictureCardPreview"
:on-remove="handleRemove"
书写回调的时候,有三个方法,其中还有一个是照片墙上传成功的回调
handleRemove(file, fileList) {
/*
file:代表删除的那张图片
fileList:照片墙删除某一张图片以后,剩余其他的图片
*/
// console.log(file, fileList);
/*
收集照片墙图片的数据
对于已有的图片【照片墙中显示的图片,有name、url字段】
因为照片墙显示数据务必要有这两个属性
而对服务器而言,不需要name和url字段
将来对于有的图片的数据在提交给服务器的时候,需要进行处理
*/
this.spuImageList = fileList;
},
handlePictureCardPreview(file) {
// 将图片地址赋值给这个属性
this.dialogImageUrl = file.url;
// 对话框显示
this.dialogVisible = true;
},
handlerSuccess(response, file, fileList) {
this.spuImageList = fileList;
},
:value="`${unselect.id}:${unselect.name}`"
// 添加新的销售属性
addSaleAttr() {
// 已经收集到了需要添加的销售属性的信息
// 把收集到的销售属性的数据进行分割
const [baseSaleAttrId, saleAttrName] = this.attrIdAndAttrName.split(":");
// 向SPU对象的spuSaleAttrList属性里面添加新的销售属性
let newSaleAttr = { baseSaleAttrId, saleAttrName, spuSaleAttrList: [] };
// 添加新的销售属性
/*
收集数据可以收集到很多位置,但是收集到spu可以直接进行展示
*/
this.spu.spuSaleAttrList.push(newSaleAttr);
// 将数据进行清空操作
this.attrIdAndAttrName = "";
},
// 添加按钮的回调,其中row是当前点击的这一行销售属性
addSaleAttrValue(row) {
// 点击销售属性中的添加按钮,需要将button变为input,通过当前销售属性的inputVisible控制
// 挂载在销售属性身上的响应式数据inputVisible控制这button与input切换
this.$set(row, "inputVisible", true);
// 通过响应式数据inputValue字段收集新增的销售属性值
this.$set(row, "inputValue", "");
},
在进行添加或者过滤方法的时候,总是报错说push方法或者啥filter未undefined 一般都是前面的调用者不是一个数组,
我们可以这样书写
let arr = row.spuSaleAttrValueList || []
// 因为可能就一开始没有获取到相应的内容
let result = arr.every((item) => item.saleAttrValueName != inputValue)
if(!result) return
row.spuSaleAttrValueList = arr
// el-input 失去焦点的事件
handleInputConfirm(row) {
console.log(row)
/*
新增的销售属性值需要收集的字段
baseSaleAttrId
saleAttrValueName
应该往当前的销售属性的属性值添加新的属性才行
需要解构出销售属性当中收集数据
*/
const { baseSaleAttrId, inputValue } = row;
// console.log(baseSaleAttrId, inputValue)
if(inputValue.trim() == "") {
this.$message('属性值不能为空')
return
}
let arr = row.spuSaleAttrValueList || []
// 直接书写下面
// let result = row.spuSaleAttrValueList.every((item) => item.saleAttrValueName != inputValue)
let result = arr.every((item) => item.saleAttrValueName != inputValue)
if(!result) return
// 新增的销售属性值
let newSaleAttrValue = { baseSaleAttrId, saleAttrValueName: inputValue };
row.spuSaleAttrValueList = arr
arr.push(newSaleAttrValue);
// 因为上面添加的inputVisible为响应式的,修改其值为false就显示button了
row.inputVisible = false;
},
删除销售属性值的话,其实在删除的时候,是将一行整个属性进行删除 - 用到数组里面的splice,索引在这个时候就很有作用了,我们就是通过索引来进行删除操作,删除往往是获取到数据以后跟根据数组的索引从数组当中删除数据
@click="spu.spuSaleAttrList.splice($index, 1)"
/*
7、修改Spu || 添加Spu
对于修改或者添加,携带给服务器参数大致是一样的,
唯一的区别就是携带的参数是否携带id
*/
export const reqAddOrUpdateSpu = (spuInfo) => {
// 携带的参数带有id -- 修改spu
if (spuInfo.id) {
return request({
url: "/admin/product/updateSpuInfo",
method: 'post',
data: spuInfo
})
} else {
// 携带的参数不带有id --- 添加Spu
return request({
url: "/admin/product/saveSpuInfo",
method: 'post',
data: spuInfo
})
}
}
<el-button type="primary" @click="addOrUpdateSpu">保存</el-button>
// 保存按钮的回调
async addOrUpdateSpu() {
/*
整理参数,需要整理照片墙的数据
携带参数,对于图片,需要携带imageName和imageUrl字段
map方法会返回一个全新的对象
*/
this.spu.spuImageList = this.spuImageList.map((item) => {
return {
imageName: item.name,
// 新图放在response当中,老图放在url当中
// 数组的map会返回一个新数组,我们可以返回给我们spuImageList字段
imageUrl: (item.response && item.response.data) || item.url,
};
});
// 发请求
// 在进行修改保存的时候,有可能会失败,可能是修改到了官方的数据
let result = await this.$API.spu.reqAddOrUpdateSpu(this.spu);
// console.log(result)
if (result.code == 200) {
// 提示
this.$message({
type: "success",
message: "保存成功",
});
// 也是一样的,通知父组件回到场景0那里即可
// this.$emit("changeScene", 0);
this.$emit("changeScene", {
scene: 0,
flag: this.spu.id ? "修改" : "添加",
});
}
// 清除数据
Object.assign(this._data, this.$options.data());
},
其中 this._data 可以操作data中的响应式数据,this.$options可以获取配置对象,配置对象的data函数执行,进行将响应式中的数据进行清零操作
cancel() {
// 取消按钮的回调,通知父亲切换场景
this.$emit("changeScene", { scene: 0, flag: "" });
/*
清理数据
Object.assign:es6中新增的方法 可以合并对象
组件实例 this._data 可以操作data当中的响应式数据
this.$options可以获取配置对象,配置对象的data函数执行,
返回的响应式数据为空的
*/
Object.assign(this._data, this.$options.data());
},
// 添加Spu按钮的回调
addSpu() {
// 切换场景为1
this.scene = 1
// 通知子组件spuForm发请求 -- 两个
// 因为在子组件当中是不能有这几个数据的,在传入的时候,可以通过父组件传送过去,因为父组件这个时候是有的
this.$refs.spu.addSpuData(this.category3Id)
},
// 点击添加SPU按钮的时候,发请求的函数
async addSpuData(category3Id) {
// 添加Spu的时候收集三级分类的id 收集到spu的category3Id大当中
this.category3Id = category3Id;
// console.log("tianjia")
// 获取品牌的信息
let tradeMarkResult = await this.$API.spu.reqTradeMarkList();
// console.log(tradeMarkResult)
if (tradeMarkResult.code == 200) {
this.tradeMarkList = tradeMarkResult.data;
}
// 获取平台全部的销售属性
let saleResult = await this.$API.spu.reqBaseSaleAttrList();
if (saleResult.code == 200) {
this.saleAttrList = saleResult.data;
}
},
// 点击添加SPU按钮的时候,发请求的函数
async addSpuData(category3Id) {
// 添加Spu的时候收集三级分类的id 收集到spu的category3Id大当中
this.category3Id = category3Id;
// console.log("tianjia")
// 获取品牌的信息
let tradeMarkResult = await this.$API.spu.reqTradeMarkList();
// console.log(tradeMarkResult)
if (tradeMarkResult.code == 200) {
this.tradeMarkList = tradeMarkResult.data;
}
// 获取平台全部的销售属性
let saleResult = await this.$API.spu.reqBaseSaleAttrList();
if (saleResult.code == 200) {
this.saleAttrList = saleResult.data;
}
},
// 自定义事件回调(Spu)修改场景值
changeScene({scene, flag}) {
// flag 是为了区分保存按钮是添加还是修改
// console.log(scene)
this.scene = scene
if(flag == "修改") {
this.getSpuList(this.page)
} else {
this.getSpuList()
}
// 修改了具体哪一页,在后面进行返回的时候,需要停留在当前页才行
// this.getSpuList(this.page)
}
<el-popconfirm title="这是一段内容确定删除吗?" @onConfirm="deleteSpu(row)">
/*
8、删除Spu
/admin/product/deleteSpu/{spuId}
*/
export const reqDeleteSpu = (spuId) => request({
url: `/admin/product/deleteSpu/${spuId}`,
method: 'delete',
})
// 删除spu按钮的回调
async deleteSpu(row) {
// 删除Spu肯定是要去发请求的
// alert(111)
let result = await this.$API.spu.reqDeleteSpu(row.id)
if(result.code == 200) {
this.$message({
type:'success',
message:"删除成功"
})
// 提示成功,删除成功以后还需要回调数据
this.getSpuList(this.records.length>1 ? this.page : this.page-1)
}
},
<div>
<el-form ref="form" label-width="80px">
<el-form-item label="SPU名称"> 海绵宝宝 </el-form-item>
<el-form-item label="SKU名称">
<el-input placeholder="SKU名称"></el-input>
</el-form-item>
<el-form-item label="价格(元)">
<el-input placeholder="价格(元素)"></el-input>
</el-form-item>
<el-form-item label="重量(千克)">
<el-input placeholder="重量(千克)"></el-input>
</el-form-item>
<el-form-item label="规格描述">
<el-input type="textarea" rows="4"></el-input>
</el-form-item>
<el-form-item label="平台属性">
<el-form :inline="true" ref="form" label-width="80px">
<el-form-item label="屏幕尺寸">
<el-select placeholder="请选择" value="">
<el-option label="label" value="value"></el-option>
</el-select>
</el-form-item>
</el-form>
</el-form-item>
<el-form-item label="销售属性">
<el-form :inline="true" ref="form" label-width="80px">
<el-form-item label="屏幕尺寸">
<el-select placeholder="请选择" value="">
<el-option label="label" value="value"></el-option>
</el-select>
</el-form-item>
</el-form>
</el-form-item>
<el-form-item label="图片列表">
<el-table style="width: 100%" border>
<el-table-column
prop="prop"
label="label"
width="width"
type="selection"
></el-table-column>
<el-table-column prop="prop" label="图片" width="width">
</el-table-column>
<el-table-column prop="prop" label="名称" width="width">
</el-table-column>
<el-table-column prop="prop" label="操作" width="width">
</el-table-column>
</el-table>
</el-form-item>
<el-form-item>
<el-button type="primary">保存</el-button>
<el-button>取消</el-button>
</el-form-item>
</el-form>
</div>
// 1、先引入我们二次封装的axios
import request from '@/utils/request'
/*
1、获取图片的数据
/admin/product/spuImageList/{spuId} get
*/
export const reqSpuImageList = (spuId) => request({
url: `/admin/product/spuImageList/${spuId}`,
method: 'get'
})
/*
2、获取销售属性的数据
/admin/product/spuSaleAttrList/{spuId} get
*/
export const reqSpuSaleAttrList = (spuId) => request({
url: `/admin/product/spuSaleAttrList/${spuId}`,
method: 'get'
})
/*
3、获取平台属性的数据
/admin/product/attrInfoList/{category1Id}/{category2Id}/{category3Id} get
*/
export const reqAttrInfoList = (category1Id, category2Id, category3Id) => request({
url: `/admin/product/attrInfoList/${category1Id}/${category2Id}/${category3Id}`,
method: 'get'
})
// 添加Sku按钮的回调
addSku(row) {
// 切换场景为2
this.scene = 2
// 通过父组件调用子组件的方法,让子组件发请求 -- 三个请求
// 父组件身上有的参数,可以直接传送过去
this.$refs.sku.getData(this.category1Id, this.category2Id, row)
}
data() {
return {
// 有需要获取到数据的时候,设置一个初始属性进行相应接收才行
// 存储图片的信息
spuImageList: [],
// 存储销售的属性
spuSaleAttrList: [],
// 存储平台属性的数据
reqAttrInfoList: [],
};
},
methods: {
// 获取SkuForm数据
async getData(category1Id, category2Id, spu) {
// console.log("获取数据")
// 获取图片的数据
let result = await this.$API.sku.reqSpuImageList(spu.id);
// console.log(result);
if (result.code == 200) {
this.spuImageList = result.data;
}
// 获取销售属性的数据
let result1 = await this.$API.sku.reqSpuSaleAttrList(spu.id);
// console.log(result1)
if (result1.code == 200) {
this.spuSaleAttrList = result1.data;
}
// 获取平台属性的数据
let result2 = await this.$API.sku.reqAttrInfoList(
category1Id,
category2Id,
spu.category3Id
);
// console.log(result2);
if(result2.code == 200) {
this.reqAttrInfoList = result2.data
}
},
},
// 存储图片的信息
spuImageList: [],
// 存储销售的属性
spuSaleAttrList: [],
// 存储平台属性的数据
attrInfoList: [],
skuInfo: {
// 我们是添加sku,已有的才有id
// id: 0,
// isSale: 0,
// 第一类收集的数据:父组件给的数据,
category3Id: 0,
spuId: 0,
tmId: 0,
// 第二类:需要通过数据双向绑定v-model收集
skuName: "",
price: 0,
weight: "",
skuDesc: "",
// 第三类:需要自己书写代码
// 平台属性
skuAttrValueList: [
{
attrId: 0,
valueId: 0,
// attrName: "string",
// id: 0,
// skuId: 0,
// valueName: "string",
},
],
// 设置默认图片
skuDefaultImg: "",
// 收集图片的字段,默认为一个空数组
skuImageList: [
// {
// id: 0,
// imgName: "string",
// imgUrl: "string",
// isDefault: "string",
// skuId: 0,
// spuImgId: 0,
// },
],
// 销售属性
skuSaleAttrValueList: [
// {
// id: 0,
// saleAttrId: 0,
// saleAttrName: "string",
// saleAttrValueId: 0,
// saleAttrValueName: "string",
// skuId: 0,
// spuId: 0,
// },
],
},
// 收集父组件给予的数据
this.skuInfo.category3Id = spu.category3Id;
this.skuInfo.spuId = spu.id;
this.skuInfo.tmId = spu.tmId;
this.spu = spu;
// 将列表中的每个对象加上一个默认属性字段
list.forEach((item) => {
// 给每一个图片的信息,加上这个需要的字段即可
// 0代表显示设置默认,1代表默认
item.isDefault = 0;
});
// 这种往默认属性字段中添加了相应的属性,乃至属性值,其实就是对原有数组进行了改变
// 将数组赋值到原有数组进行存储
this.spuImageList = list;
// table表格复选框按钮的事件
handleSelectionChange(params) {
/*
获取到用户选中图片的信息数据
但是需要注意,当前收集的数据当中,缺少isDefault字段
现在收集到的数据是不完整的,所以不能存储在skuImageList当中,
因为这一块将来是需要提交给服务器的
*/
this.imageList = params;
},
排他的操作
// 排他的操作
changeDefault(row) {
// 图片列表的isDefault字段变为0,只有你点击的那个变为1
this.spuImageList.forEach((item) => {
// 所有的isDefault字段变为0
item.isDefault = 0;
});
// 点击的那个图片的数据变为1
row.isDefault = 1;
// 收集一下默认图片的地址
this.skuInfo.skuDefaultImg = row.imgUrl;
},
其他的内容数据收集操作都是通过v-model来进行收集信息 - 实现数据的交互操作
先完成取消操作
cancel() {
// 自定义事件,让父组件切换场景为0
this.$emit("changeScenes", 0);
// 清除数据
Object.assign(this._data, this.$options.data());
},
// 整理平台属性的数据方式一
// 新建一个数组
let arr = []
// 把收集到的数据整理一下
attrInfoList.forEach(item => {
// 当前平台属性用户进行了选择
if(item.attrIdAndValueId) {
const [attrId, valueId] = item.attrIdAndValueId.split(":")
// 携带给服务器的参数,应该是一个对象
let obj = {attrId, valueId}
arr.push(obj)
}
})
// 将整理好的参数字段赋值给skuInfo.skuAttrValueList
skuInfo.skuAttrValueList = arr
/*
prev初始值为[] 初始值为一个空数组
item为遍历到的每一个元素
常用于:求数组累加和、最大值、当前情况
*/
// 返回的是最后一次执行的结果
skuInfo.skuAttrValueList = attrInfoList.reduce((prev, item) => {
// 用户已经选择了
if (item.attrIdAndValueId) {
const [attrId, valueId] = item.attrIdAndValueId.split(":");
prev.push({ attrId, valueId });
}
// 最后一次返回执行的结果 reduce需要将下一次的结果返回,当做下一次的
return prev;
}, []);
// 整理销售属性 spuSaleAttrList
skuInfo.skuSaleAttrValueList = spuSaleAttrList.reduce((prev, item) => {
if (item.attrIdAndValueId) {
const [saleAttrId, saleAttrValueId] =
item.attrIdAndValueId.split(":");
prev.push({ saleAttrId, saleAttrValueId });
}
return prev;
}, []);
// 整理图片的数据 利用已有的图片数据来映射出一个新的数据出来
// map是映射出一个新的数组,赋值给它即可
skuInfo.skuImageList = imageList.map((item) => {
return {
imgName: item.imgName,
imgUrl: item.imgUrl,
isDefault: item.isDefault,
spuImgId: item.id,
};
});
整理好的数据都放在了skuInfo当中 通过发送请求,成功以后弹出提示信息,然后提示父组件进行场景调换即可
// 整理好了数据以后,发送请求即可
let result4 = await this.$API.sku.reqAddSku(skuInfo)
// console.log(result4)
if(result4.code == 200) {
this.$message({
type:"success",
message:"添加Sku成功"
})
this.$emit('changeScenes', 0)
}
let arr = [
// 对象里面肯定是放键值对冒号的形式出来
{ imgName: 1 },
{ imgUrl: 2 },
{ isDefault: 3 },
{ id: 4 }
]
let arr = [
// 对象里面肯定是放键值对冒号的形式出来
{ imgName: 1 },
{ imgUrl: 2 },
{ isDefault: 3 },
{ id: 4 }
]
let skuInfo = arr.map(item => {
return {
imgName: item.imgName,
imgUrl: item.imgUrl,
isDefault: item.isDefault,
spuImgId: item.id,
}
})
console.log(skuInfo)
// 结果
[
{
imgName: 1,
imgUrl: undefined,
isDefault: undefined,
spuImgId: undefined
},
{
imgName: undefined,
imgUrl: 2,
isDefault: undefined,
spuImgId: undefined
},
{
imgName: undefined,
imgUrl: undefined,
isDefault: 3,
spuImgId: undefined
},
{
imgName: undefined,
imgUrl: undefined,
isDefault: undefined,
spuImgId: 4
}
]
let arr = [
// 对象里面肯定是放键值对冒号的形式出来
{
imgName: 1,
imgUrl: 2,
isDefault: 3,
id: 4
}
]
// 对数组里面的每个对象进行操作
let skuInfo = arr.map(item => {
return {
imgName: item.imgName,
imgUrl: item.imgUrl,
isDefault: item.isDefault,
spuImgId: item.id,
}
})
console.log(skuInfo)
// 结果 非常关键
[ { imgName: 1, imgUrl: 2, isDefault: 3, spuImgId: 4 } ]
let a = [1, 2, 3, 4, 5]
console.log(a.reduce((x, y) => x + y, 0)) // 15
console.log(a.reduce((x, y) => x * y, 1)) // 120
console.log(a.reduce((x, y) => (x > y) ? x : y)) // 5
let a = [1, 2, 3, 4, 5]
b = a.reduce((x, y) => {
// 完整的写法,工作中用的多
x = x + y
return x // 每次要将x前一个参数进行返回,作为下一次运算的参数
}, 0)
console.log(b) // 15
<!-- 像这种点击一下就进行显示的操作,放在随便哪一个位置都可以,弄成动态的才行 -->
<el-dialog
:title="`${spu.spuName}的sku列表`"
:visible.sync="dialogTableVisible"
:before-close="close"
>
<el-table :data="skuList" style="width: 100%" border v-loading="loading">
<!-- prop当中展示相应的字段操作 -->
<el-table-column prop="skuName" label="名称" width="width">
</el-table-column>
<el-table-column prop="price" label="价格" width="width">
</el-table-column>
<el-table-column prop="weight" label="重量" width="width">
</el-table-column>
<el-table-column label="默认图片" width="width">
<template slot-scope="{ row, $index }">
<img
:src="row.skuDefaultImg"
alt=""
style="width: 100px; height: 100px"
/>
</template>
</el-table-column>
</el-table>
</el-dialog>
@click="handler(row)"
async handler(spu) {
// 点击按钮的时候,对话框应该是可见的
this.dialogTableVisible = true;
// 保存spu的信息,因为我们想要在别的地方进行展示,保存到data当中
this.spu = spu;
// 刚才写了相应的参数,我们需要获取sku列表的数据进行展示
let result = await this.$API.sku.reqSkuList(spu.id);
// console.log(result);
if (result.code == 200) {
this.skuList = result.data;
// 将loading进行隐藏
this.loading = false
}
},
// 关闭对话框的回调
close(done) {
// loading属性再次变为真
this.loading = true
// 清除sku列表的数据,每次展示前将数据进行清除操作
this.skuList = []
// 关闭对话框
done()
}
data() {
return {
// 代表当前第几页
page: 1,
// 代表当前页面有几条数据
limit: 10,
// 存储Sku列表的数据
records: [],
// 存储分页器一共展示的数据
total: 0,
// 存储sku的信息的
skuInfo: {},
show: false,
};
},
获取sku列表数据的接口
/*
6、sku列表的接口
/admin/product/list/{page}/{limit}
*/
export const reqSkuList2 = (page, limit) => request({
url: `/admin/product/list/${page}/${limit}`,
method: 'get'
})
获取sku列表数据的方法
async getSkuList(pages = 1) {
this.page = pages;
// 需要用到哪些参数,我们后续需要解构出来
const { page, limit } = this;
let result = await this.$API.sku.reqSkuList2(page, limit);
// console.log(result)
if (result.code == 200) {
this.total = result.data.total;
this.records = result.data.records;
}
},
在结构进行展示的时候 尤其是在el-table中展示的时候,直接将要展示的数据,放入到:data中,
而el-table-column中的数据进行展示的时候,直接使用:data 的字段即可
<!-- 表格 -->
<el-table style="width: 100%" border :data="records">
<el-table-column type="index" label="序号" width="80" align="center">
</el-table-column>
<!-- 里面在进行展示的时候,直接进行展示即可,没有多余的操作 -->
<el-table-column prop="skuName" label="名称" width="width">
</el-table-column>
<el-table-column prop="skuDesc" label="描述" width="width">
</el-table-column>
<el-table-column prop="prop" label="默认图片" width="110">
<template slot-scope="{ row, $index }">
<img
:src="row.skuDefaultImg"
alt=""
style="width: 80px; height: 80px"
/>
</template>
</el-table-column>
<el-table-column prop="weight" label="重量" width="80"> </el-table-column>
<el-table-column prop="price" label="价格" width="80"> </el-table-column>
<el-table-column prop="prop" label="操作" width="width">
<template slot-scope="{ row, $index }">
<el-button
type="success"
icon="el-icon-sort-down"
size="mini"
v-if="row.isSale == 0"
@click="sale(row)"
></el-button>
<el-button
type="success"
icon="el-icon-sort-up"
size="mini"
v-else
@click="cancel(row)"
></el-button>
<el-button
type="primary"
icon="el-icon-edit"
size="mini"
@click="edit"
></el-button>
<el-button
type="info"
icon="el-icon-info"
size="mini"
@click="getSkuInfo(row)"
></el-button>
<el-button
type="danger"
icon="el-icon-delete"
size="mini"
></el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
style="text-align: center"
:current-page="page"
:page-sizes="[3, 5, 10]"
:page-size="limit"
layout="prev, pager, next, jumper, ->, sizes, total"
:total="total"
@current-change="getSkuList"
@size-change="handleSizeChange"
>
@current-change="getSkuList"
// 可以复用上述的getSkuList的方法,可以都是获取pages页进行数据展示,不过之前的固定是page = 1
// 获取sku列表数据的方法
async getSkuList(pages = 1) {
this.page = pages;
// 需要用到哪些参数,我们后续需要解构出来
const { page, limit } = this;
let result = await this.$API.sku.reqSkuList2(page, limit);
// console.log(result)
if (result.code == 200) {
this.total = result.data.total;
this.records = result.data.records;
}
},
@size-change="handleSizeChange"
// 修改携带的参数然后再进行展示
handleSizeChange(limit) {
// 修改参数,然后发送请求即可
this.limit = limit;
this.getSkuList();
},
上架与下架操作 这两个按钮为互斥操作,即这两个按钮也只能展示其中一个
<el-button
type="success"
icon="el-icon-sort-down"
size="mini"
v-if="row.isSale == 0"
@click="sale(row)"
></el-button>
<el-button
type="success"
icon="el-icon-sort-up"
size="mini"
v-else
@click="cancel(row)"
></el-button>
/*
7、商品的上架操作
/admin/product/onSale/{skuId}
*/
export const reqSale = (skuId) => request({
url: `/admin/product/onSale/${skuId}`,
method: 'get',
})
/*
8、商品的下架操作
/admin/product/cancelSale/{skuId}
*/
export const reqCancel = (skuId) => request({
url: `/admin/product/cancelSale/${skuId}`,
method: 'get',
})
书写上架操作和下架操作的方法的回调
// 上架的业务
async sale(row) {
let result = await this.$API.sku.reqSale(row.id);
if (result.code == 200) {
// 上架以后,将isSale字段进行相应的修改操作
row.isSale = 1;
this.$message({
type: "success",
message: "上架成功",
});
}
},
// 下架的业务
async cancel(row) {
let result = await this.$API.sku.reqCancel(row.id);
if (result.code == 200) {
row.isSale = 0;
this.$message({
type: "success",
message: "下架成功",
});
}
},
// edit
edit() {
this.$message("正在开发中");
},
获取详情数据的api书写
/*
9、获取sku详情的接口
/admin/product/getSkuById/{skuId} get
*/
export const reqSkuById = (skuId) => request({
url: `/admin/product/getSkuById/${skuId}`,
method: 'get'
})
// 获取sku详情的方法
async getSkuInfo(sku) {
// 展示抽屉
this.show = true;
// 获取Sku的数据
let result = await this.$API.sku.reqSkuById(sku.id);
if (result.code == 200) {
this.skuInfo = result.data;
}
},
<el-drawer
title="我是标题"
:visible.sync="show"
:before-close="handleClose"
:show-close="false"
size="50%"
>
<el-row>
<el-col :span="5">名称</el-col>
<el-col :span="16">{{ skuInfo.skuName }}</el-col>
</el-row>
<el-row>
<el-col :span="5">描述</el-col>
<el-col :span="16">{{ skuInfo.skuDesc }}</el-col>
</el-row>
<el-row>
<el-col :span="5">价格</el-col>
<el-col :span="16">{{ skuInfo.price }}元</el-col>
</el-row>
<el-row>
<el-col :span="5">平台属性</el-col>
<el-col :span="16">
<template>
<el-tag
type="success"
v-for="(attr, index) in skuInfo.skuAttrValueList"
:key="attr.id"
style="margin-right: 10px"
>
// 展示的话,还是使用{{}}进行展示操作
{{ attr.attrId }}-{{ attr.valueId }}
</el-tag>
</template>
</el-col>
</el-row>
<el-row>
<el-col :span="5">商品图片</el-col>
<el-col :span="16">
<el-carousel height="150px">
// data中定义的数据是可以直接获取展示的
<el-carousel-item
v-for="item in skuInfo.skuImageList"
:key="item.id"
>
<img :src="item.imgUrl" alt="" />
</el-carousel-item>
</el-carousel>
</el-col>
</el-row>
</el-drawer>
>>> 一般用于原生CSS
/deep/ 一般用于less
::v-deep 一般用户scss
echarts:vue、react
v-chart:vue
d3.js:vue、react
hightchart:vue、react
echarts:基本使用
注意点:
/*
- canvas标签任何操作务必通过JS完成
- 通过“JS” 当中的“笔”去完成
- 该图形是由像素点组成的像素群
*/
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>canvas的基本使用</title>
<style>
* {
/* 书写的时候,每次结束用分号隔开 */
margin: 0;
padding: 0;
}
canvas {
border: 1px solid black;
}
</style>
</head>
<body>
<!--
canvas画布
- 是HTML5中新增的一个特性,双闭合标签
- 该标签默认具有宽度与高度 300 * 150
- 浏览器认为canvas标签是一张图片,可以另存为
- 给canvas画布添加文本内容没有任何意义
- 给canvas添加子节点也是没有任何意义
- 你想操作canvas画布:画布当中绘制图形,显示一个文字,都必须通过JS完成
- canvas标签的w|h务必通过canvas标签属性width||height设置
- 切记不能通过样式去设置画布的宽度与高度
-->
<canvas width="600" height="400"></canvas>
</body>
<script>
/*
- canvas标签任何操作务必通过JS完成
- 通过“JS” 当中的“笔”去完成
- 该图形是由像素点组成的像素群
*/
let canvas = document.querySelector('canvas')
// 获取画布的笔【上下文】
let ctx = canvas.getContext('2d')
// console.log(ctx)
// 绘制线段:绘制线段的起点的设置
ctx.moveTo(100, 100)
// 其他点的设置(可以有多个)
ctx.lineTo(100, 200)
ctx.lineTo(200, 100)
// 设置图形填充的颜色
ctx.fillStyle = "red"
ctx.fill()
// 设置图型的线段的颜色与宽度
ctx.strokeStyle = "purple"
ctx.lineWidth = "20"
// 可以设置起点与最终的结束点连接在一起
ctx.closePath()
// 调用stroke方法去绘制线段
ctx.stroke()
</script>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0px;
padding: 0px;
}
canvas {
border: 1px solid black;
}
</style>
</head>
<body>
<canvas width="600px" height="400px"></canvas>
</body>
<script>
// 获取dom节点
let canvas = document.querySelector("canvas")
// 获取上下文
let ctx = canvas.getContext('2d')
// 绘制矩形的第一种方式
/*
参数:x, y, w, h
这种矩形没有办法设置填充颜色
*/
ctx.strokeRect(100, 200, 100, 200)
// 绘制矩形的第二种方式:带有填充的颜色(默认为黑色)
ctx.fillStyle = 'skyblue'
ctx.fill()
ctx.fillRect(300, 200, 100, 200)
</script>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0px;
padding: 0px;
}
canvas {
border: 1px solid black;
}
</style>
</head>
<body>
<canvas height="400" width="600"></canvas>
</body>
<script>
// 获取canvas节点
let canvas = document.querySelector('canvas')
// 获取上下文
let ctx = canvas.getContext('2d')
// 绘制圆形
ctx.beginPath()
// 绘制圆形的方法
// x, y 是圆心距离水平轴和y轴
// x, y, r, 起始的弧度, 结束的弧度, 是否逆时针绘制
ctx.arc(100, 100, 50, 0, 2 * Math.PI, true)
// 设置填充的颜色
ctx.fillStyle = 'red'
ctx.fill()
// 再次绘制 false代表的是顺时针
// 且 2 * π * 弧度 = 360°
ctx.beginPath()
ctx.arc(200, 200, 50, 0, 1, false)
// 绘制圆形
ctx.stroke()
</script>
**清除画布使用clearRect()**其有四个参数
**绘制文字使用fillText()**其有三个参数
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
canvas {
border: 1px solid black;
}
</style>
</head>
<body>
<!--
画布的宽度与高度通过属性设置
千万不要通过样式设置
-->
<canvas width="600" height="400"></canvas>
</body>
<script>
// 获取节点
let canvas = document.querySelector('canvas')
// 获取上下文 - 笔
let ctx = canvas.getContext('2d')
// 绘制矩形
ctx.fillRect(100, 200, 100, 200)
// 清除画布,从(0, 0)点开始清除600宽400高 整个画布
// ctx.clearRect(0, 0, 600, 400)
// 清除部分的画布
ctx.clearRect(100, 200, 50, 100)
// 设置文字字体
ctx.font = "20px 微软雅黑"
ctx.fillStyle = 'red'
// 绘制文字
ctx.fillText("数据可视化", 50, 20)
</script>
所要实现效果图
实现代码
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
canvas {
border: 1px solid red;
}
</style>
</head>
<body>
<canvas width="800" height="420"></canvas>
</body>
<script>
// 获取dom节点
let canvas = document.querySelector('canvas')
// 获取上下文
let ctx = canvas.getContext('2d')
// 绘制文本 - 左上角的文本
ctx.font = "16px 微软雅黑"
ctx.fillText("数据可视化", 50, 80)
// 绘制线段
ctx.moveTo(100, 100)
ctx.lineTo(100, 400)
ctx.lineTo(700, 400)
ctx.stroke()
// 绘制其他的线段
ctx.moveTo(100, 100)
ctx.lineTo(700, 100)
ctx.fillText('150', 70, 110)
ctx.stroke()
ctx.moveTo(100, 160)
ctx.lineTo(700, 160)
ctx.fillText('120', 70, 170)
ctx.stroke()
ctx.moveTo(100, 220)
ctx.lineTo(700, 220)
ctx.fillText('90', 70, 230)
ctx.stroke()
ctx.moveTo(100, 280)
ctx.lineTo(700, 280)
ctx.fillText('60', 70, 290)
ctx.stroke()
ctx.moveTo(100, 340)
ctx.lineTo(700, 340)
ctx.fillText('30', 70, 350)
ctx.stroke()
// 绘制水平轴 底部的线段
ctx.moveTo(250, 400)
ctx.lineTo(250, 410)
ctx.fillText('0', 70, 400)
ctx.stroke()
// 底部的文字
ctx.fillText('食品', 170, 415)
ctx.moveTo(400, 400)
ctx.lineTo(400, 410)
ctx.fillText('数码', 320, 415)
ctx.stroke()
ctx.moveTo(550, 400)
ctx.lineTo(550, 410)
ctx.fillText('服饰', 450, 415)
ctx.fillText('箱包', 600, 415)
ctx.stroke()
// 绘制矩形
ctx.fillStyle = 'red'
ctx.fillRect(120, 200, 100, 200)
</script>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0px;
padding: 0px;
}
.box {
width: 800px;
height: 600px;
}
</style>
</head>
<body>
<!--
svg双闭合标签
- 默认宽度与高度为300 * 150
- svg绘制图形务必在svg标签内部使用绘制图形
-->
<svg class="box">
<!-- x1,y1是第一个点的坐标,x2,y2是第二个点的坐标 -->
<line x1="100" y1="100" x2="200" y2="200" stroke="red"></line>
<line x1="100" y1="200" x2="200" y2="100" stroke="red"></line>
<!-- 绘制折线:可以多个点,多个点的时候,最好带有逗号 -->
<polyline points="300, 300, 50, 100, 120, 400" fill-opacity="0" stroke="cyan"></polyline>
<!-- 绘制矩形 -->
<!-- fill属性:设置填充的颜色的 fill-opacity:设置填充颜色的透明度 stroke:线的颜色-->
<rect x="400" y="400" width="150" height="50" fill="pink"></rect>
<!-- 绘制圆形 -->
<circle cx="370" cy="95" r="50" style="stroke: cyan;fill:none"></circle>
<!-- 绘制圆形 | 椭圆 -->
<ellipse cx="500" cy="500" rx="100" ry="50" style="fill:black"></ellipse>
<!-- 绘制多边形 -->
<polygon points="600 100, 300 400, 750 100" stroke="red" fill-opacity="0" />
<!-- 绘制任意图形 -->
<path></path>
</svg>
</body>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- 引入echarts依赖包 -->
<script src="https://cdn.bootcdn.net/ajax/libs/echarts/5.3.3/echarts.common.js"></script>
<style>
* {
margin: 0px;
padding: 0px;
}
div {
width: 800px;
height: 400px;
}
</style>
</head>
<body>
<!-- 准备一个容器:容器就是显示图标的区域 -->
<div></div>
</body>
<script>
// 基于准备好的dom初始化一个echart实例
let dom = document.querySelector('div')
// 创建echarts实例
let mycharts = echarts.init(dom)
// 准备指定图表的配置项与数据
mycharts.setOption({
// 图标的标题
title: {
// 主标题的设置
text: "数据可视化",
// 子标题
subtext: "echarts的基本使用",
// 主标题的颜色
textStyle: {
color: "cyan"
},
// 设置标题位置
left: "center"
},
// x轴的配置项
xAxis: {
// 数据
data: ["衣服", "直播", "游戏", "电影"]
},
// y轴的配置项
yAxis: {
// 显示Y轴的线条
axisLine: {
show: true,
},
// 显示Y轴的刻度
axisTick: {
show: true,
}
},
// 系列的设置
series: [
{
// 图表类型的设置
type: "bar",
// 图表的数据
data: [10, 20, 30, 40],
// 颜色
color: "red",
}
]
})
</script>
既然要展示多个表格,就需要准备多个容器,然后创建多个实例 - 然后根据配置项和数据来创建相应的表格
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- 引入echarts依赖包 -->
<script src="https://cdn.bootcdn.net/ajax/libs/echarts/5.3.3/echarts.common.js"></script>
<style>
* {
margin: 0px;
padding: 0px;
}
div {
width: 800px;
height: 400px;
}
</style>
</head>
<body>
<!-- 准备一个容器:容器就是显示图标的区域 -->
<div class="box1"></div>
<div class="box2"></div>
</body>
<script>
// 基于准备好的dom初始化一个echart实例
let dom1 = document.querySelector('.box1')
let dom2 = document.querySelector('.box2')
// 创建echarts实例
let mycharts1 = echarts.init(dom1)
let mycharts2 = echarts.init(dom2)
// 准备指定图表的配置项与数据
mycharts1.setOption({
// 图标的标题
title: {
// 主标题的设置
text: "数据可视化",
// 子标题
subtext: "echarts的基本使用",
// 主标题的颜色
textStyle: {
color: "cyan"
},
// 设置标题位置
left: "center"
},
// x轴的配置项
xAxis: {
// 数据
data: ["衣服", "直播", "游戏", "电影"]
},
// y轴的配置项
yAxis: {
// 显示Y轴的线条
axisLine: {
show: true,
},
// 显示Y轴的刻度
axisTick: {
show: true,
}
},
// 系列的设置
series: [
{
// 图表类型的设置
type: "bar",
// 图表的数据
data: [10, 20, 30, 40],
// 颜色
color: "red",
}
]
})
// 第二个图表的配置项
mycharts2.setOption({
title: {
// 主标题
text: "折线图",
left: "center",
textStyle: {
color: "red"
},
subtext: "echarts的基本使用",
subtextStyle: {
color: "cyan"
}
},
// x轴
xAxis: {
data: ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日']
},
yAxis: {
},
// 类型图表与数据
series: [
{
type: 'line',
data: [10, 20, 15, 44, 2, 19, 100],
color: "cyan"
}
]
})
</script>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- 引入echarts依赖包 -->
<script src="https://cdn.bootcdn.net/ajax/libs/echarts/5.3.3/echarts.common.js"></script>
<style>
* {
margin: 0px;
padding: 0px;
}
div {
width: 800px;
height: 400px;
}
</style>
</head>
<body>
<!-- 准备一个容器:容器就是显示图标的区域 -->
<div></div>
</body>
<script>
// 基于准备好的dom初始化一个echart实例
let dom = document.querySelector('div')
// 创建echarts实例
let mycharts = echarts.init(dom)
let data = [
["衣服", 10, 22, 'x', 10],
["直播", 12, 55, 'y', 60],
["游戏", 16, 44, 'z', 50],
["电影", 19, 32, 't', 70],
]
// 准备指定图表的配置项与数据
mycharts.setOption({
// 设置字符集
dataset: {
// 数据源
source: data,
},
// 图标的标题
title: {
// 主标题的设置
text: "数据可视化",
// 子标题
subtext: "echarts的基本使用",
// 主标题的颜色
textStyle: {
color: "cyan"
},
// 设置标题位置
left: "center"
},
// x轴的配置项
xAxis: {
// 数据
data: ["衣服", "直播", "游戏", "电影"]
},
// y轴的配置项
yAxis: {
// 显示Y轴的线条
axisLine: {
show: true,
},
// 显示Y轴的刻度
axisTick: {
show: true,
}
},
// 系列的设置
series: [
// 柱状图
{
// 图表类型的设置
type: "bar",
// 图表的数据
// data: [10, 20, 30, 40],
// 颜色
color: "red",
encode: {
y: 1,
}
},
// 折线图
{
// 图表类型的设置
type: "line",
// 图表的数据
// data: [10, 20, 30, 40],
color: "pink",
encode: {
y: 2,
}
},
// 饼图
{
type: "pie",
// 想要展示文字和数字可以data里面配置对象
// 饼图你可以显示文字,data写法如下
/* data: [
{ name: 'x', value: 10 },
{ name: 'y', value: 20 },
{ name: 'z', value: 30 },
{ name: 't', value: 40 },
], */
// 饼图的宽度与高度
width: 250,
height: 250,
// 饼图的位置
left: 150,
top: 100,
// 饼图的半径
radius: 25,
encode: {
// 饼图旁边的文字,
itemName: 3,
value: 4,
}
}
]
})
</script>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.bootcdn.net/ajax/libs/echarts/5.3.3/echarts.common.js"></script>
<style>
* {
margin: 0px;
padding: 0px;
}
.box {
width: 100%;
height: 400px;
/* border: 1px solid black; */
}
</style>
</head>
<body>
<!-- 准备容器 -->
<div class="box"></div>
</body>
<script>
// 初始化echarts实例
// 获取容器
let dom = document.querySelector('.box')
let myCharts = echarts.init(dom)
// 配置数据
myCharts.setOption({
dataZoom: {},
// 标题
title: {
text: "echarts组件",
},
xAxis: {
data: ['游戏', '电影', '直播', '娱乐'],
},
yAxis: {},
series: [
{
name: "柱状图",
type: "bar",
data: [10, 20, 30, 40]
},
{
name: "折线图",
type: "line",
data: [30, 40, 50, 60]
}
],
// 提示组件
tooltip: {
// 提示框文字的颜色
textStyle: {
color: 'red',
}
},
// 系列切换组件
legend: {
data: ['柱状图', '折线图']
},
toolbox: {
show: true,
feature: {
dataZoom: {
yAxisIndex: 'none'
},
dataView: { readOnly: false },
magicType: { type: ['line', 'bar'] },
restore: {},
saveAsImage: {}
}
},
// 调整图表的布局
grid: {
left: 30,
right: 0,
}
})
</script>
例子:散点图
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- 引入echarts依赖包 -->
<script src="https://cdn.bootcdn.net/ajax/libs/echarts/5.3.3/echarts.common.js"></script>
<style>
* {
margin: 0px;
padding: 0px;
}
div {
width: 100%;
height: 400px;
}
</style>
</head>
<body>
<!-- 准备一个容器:容器就是显示图标的区域 -->
<div></div>
</body>
<script>
// 基于准备好的dom初始化一个echart实例
let dom = document.querySelector('div')
// 创建echarts实例
let mycharts = echarts.init(dom)
// 准备指定图表的配置项与数据
mycharts.setOption({
// 标题
title: {
text: "一个坐标系",
},
// X轴和Y轴 的使用
xAxis: {
type: "category"
},
yAxis: {},
// 散点图
series: [
{
type: "scatter",
// 散点图的数据 是一个二维数组
data: [
[10, 20],
[13, 66],
[50, 9],
[44, 22],
[15, 10]
]
}
]
})
</script>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- 引入echarts依赖包 -->
<script src="https://cdn.bootcdn.net/ajax/libs/echarts/5.3.3/echarts.common.js"></script>
<style>
* {
margin: 0px;
padding: 0px;
}
div {
width: 100%;
height: 400px;
}
</style>
</head>
<body>
<!-- 准备一个容器:容器就是显示图标的区域 -->
<div></div>
</body>
<script>
// 基于准备好的dom初始化一个echart实例
let dom = document.querySelector('div')
// 创建echarts实例
let mycharts = echarts.init(dom)
// 准备指定图表的配置项与数据
mycharts.setOption({
// 标题
title: {
text: "双坐标",
},
// X轴和Y轴 的使用
xAxis: {
data: ['游戏', '直播', '经济', '娱乐'],
},
yAxis: [
{
// 显示Y轴的线条
axisLine: {
show: true,
},
// 显示Y轴的刻度
axisTick: {
show: true,
},
},
{
// 显示Y轴的线条
axisLine: {
show: true,
},
// 显示Y轴的刻度
axisTick: {
show: true,
},
}
],
// 散点图
series: [
{
type: "line",
data: [10, 20, 30, 40],
yAxisIndex: 0
},
{
type: "bar",
data: [6, 10, 80, 20],
yAxisIndex: 1
}
]
})
</script>
<template>
<div>
<div class="card-header">
<span>{{ title }}</span>
<svg
t="1663466055240"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="2421"
width="20"
height="20"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64z m0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"
p-id="2422"
></path>
<path
d="M512 336m-48 0a48 48 0 1 0 96 0 48 48 0 1 0-96 0Z"
p-id="2423"
></path>
<path
d="M536 448h-48c-4.4 0-8 3.6-8 8v272c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V456c0-4.4-3.6-8-8-8z"
p-id="2424"
></path>
</svg>
</div>
<div class="card-content">{{ count }}</div>
<div class="card-charts">
<slot name="charts"> </slot>
</div>
<div class="card-footer">
<slot name="footer"> </slot>
</div>
</div>
</template>
<el-col :span="6">
<!-- 想要有阴影的效果,可以外面放上el-card -->
<el-card>
<!-- 父组件进行传递,子组件进行接收的时候,不需要动态进行传递 -->
<Detail title="总销售额" count="¥126560">
<template slot="charts">
<span>周同比 56.67%</span
><svg
t="1663467150019"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="3501"
width="16"
height="16"
>
<path
d="M786.295467 485.000533l-6.126934 2.082134v-18.295467h6.024534l0.1024 16.213333zM221.5424 485.239467h-8.9088l-0.085333-16.247467 8.994133-0.512v16.759467z"
fill="#996E28"
p-id="3502"
></path>
<path
d="M784.469333 480.290133L504.456533 178.978133a6.826667 6.826667 0 0 0-10.001066 0L214.459733 480.290133a6.826667 6.826667 0 0 0 5.922134 11.4176l158.1056-21.435733L358.4 853.333333v16.162134a6.826667 6.826667 0 0 0 6.826667 7.168h268.356266a6.826667 6.826667 0 0 0 6.826667-7.168l-0.119467-16.3328-19.848533-382.8736 158.122667 21.418666a6.826667 6.826667 0 0 0 5.922133-11.4176z"
fill="#996E28"
p-id="3503"
></path>
<path
d="M191.3344 734.6176l103.031467 72.789333 103.048533-72.789333a127.197867 127.197867 0 0 0-206.097067 0z"
fill="#D5382E"
p-id="3504"
></path>
<path
d="M294.382933 681.984a127.010133 127.010133 0 0 1 103.048534 52.6336l-103.048534 72.789333-103.031466-72.789333a127.010133 127.010133 0 0 1 103.048533-52.6336m0-10.24a137.915733 137.915733 0 0 0-111.342933 56.849067 10.24 10.24 0 0 0 2.269866 14.2848l0.136534 0.1024L288.426667 815.786667a10.24 10.24 0 0 0 11.810133 0l103.031467-72.772267a10.24 10.24 0 0 0 2.4064-14.3872 137.915733 137.915733 0 0 0-111.342934-56.849067z"
fill="#E8D4AB"
p-id="3505"
></path>
<path
d="M384.1536 444.142933h1.5872l-20.48 409.6h268.2368l-20.48-409.6"
fill="#D5382E"
p-id="3506"
></path>
<path
d="M633.4976 860.5696H365.226667a6.826667 6.826667 0 0 1-6.826667-7.168l20.48-409.6 5.239467 0.256v-6.826667h1.5872a6.826667 6.826667 0 0 1 6.826666 7.168l-20.1216 402.432H626.346667l-20.1216-402.432 13.653333-0.682666 20.48 409.6a6.826667 6.826667 0 0 1-6.826667 7.168z"
fill="#E8D4AB"
p-id="3507"
></path>
<path
d="M613.0176 446.276267l166.365867 22.545066L499.387733 167.509333 219.374933 468.821333l164.778667-22.545066"
fill="#D5382E"
p-id="3508"
></path>
<path
d="M633.4976 860.5696H365.226667a6.826667 6.826667 0 0 1-6.826667-7.168l19.968-399.240533-158.122667 21.435733a6.826667 6.826667 0 0 1-5.922133-11.4176L494.370133 162.8672a6.826667 6.826667 0 0 1 10.001067 0L784.384 464.213333a6.826667 6.826667 0 0 1-5.922133 11.4176l-158.122667-21.418666L640.3072 853.333333a6.826667 6.826667 0 0 1-6.826667 7.168z m-261.12-13.653333H626.346667L606.3104 446.634667a6.826667 6.826667 0 0 1 7.7312-7.099734l147.3536 19.950934L499.370667 177.493333 237.3632 459.485867l147.3536-19.968a6.826667 6.826667 0 0 1 7.7312 7.099733z"
fill="#E8D4AB"
p-id="3509"
></path>
<path
d="M557.3632 617.5744l149.5552 105.659733 149.572267-105.659733a184.644267 184.644267 0 0 0-299.1616 0z"
fill="#D5382E"
p-id="3510"
></path>
<path
d="M706.935467 541.184a184.32 184.32 0 0 1 149.572266 76.3904l-149.572266 105.659733-149.5552-105.659733a184.32 184.32 0 0 1 149.572266-76.3904m0-10.24a194.56 194.56 0 0 0-157.866666 80.622933 10.24 10.24 0 0 0 2.184533 14.2336l0.221867 0.1536 149.5552 105.659734a10.24 10.24 0 0 0 11.810133 0l149.5552-105.642667a10.24 10.24 0 0 0 2.4064-14.3872 194.56 194.56 0 0 0-157.866667-80.622933z"
fill="#E8D4AB"
p-id="3511"
></path>
</svg>
<span>日同比 19.96%</span
><svg
t="1663467232683"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="4715"
width="16"
height="16"
>
<path
d="M252.484267 566.749867h11.776v16.6912h-11.776zM815.684267 566.8352h10.461866v16.9984h-10.461866z"
fill="#996E28"
p-id="4716"
></path>
<path
d="M825.361067 580.642133a6.826667 6.826667 0 0 0-6.980267-3.618133l-158.0032 21.418667L680.2432 201.386667a6.826667 6.826667 0 0 0 0-1.058134v-1.109333a6.826667 6.826667 0 0 0-6.826667-7.168H405.1968a6.826667 6.826667 0 0 0-6.826667 7.168v1.109333a6.826667 6.826667 0 0 0 0 1.058134l19.848534 397.038933-158.0032-21.4016a6.826667 6.826667 0 0 0-5.922134 11.4176l280.0128 301.277867a6.826667 6.826667 0 0 0 10.001067 0L824.32 588.424533a6.826667 6.826667 0 0 0 1.041067-7.7824z"
fill="#996E28"
p-id="4717"
></path>
<path
d="M339.780267 523.7248H103.6288a17.066667 17.066667 0 1 1 0-34.133333h236.151467a13.380267 13.380267 0 1 0 0-26.760534h-33.467734a44.782933 44.782933 0 1 1 0.631467-89.565866h238.250667a17.066667 17.066667 0 0 1 0 34.133333h-238.250667a11.093333 11.093333 0 0 0-11.229867 9.5744 10.717867 10.717867 0 0 0 10.5984 11.7248h33.467734a47.5136 47.5136 0 1 1 0 95.0272z"
fill="#D5382E"
p-id="4718"
></path>
<path
d="M545.194667 373.282133a17.066667 17.066667 0 0 1 0 34.133334h-238.250667a11.093333 11.093333 0 0 0-11.229867 9.5744 10.717867 10.717867 0 0 0 10.5984 11.7248h33.467734a47.5136 47.5136 0 1 1 0 95.0272H103.6288a17.066667 17.066667 0 1 1 0-34.133334h236.151467a13.380267 13.380267 0 1 0 0-26.760533h-33.467734a44.782933 44.782933 0 1 1 0.631467-89.565867h238.250667m0-10.24h-238.250667a55.022933 55.022933 0 1 0-0.631467 110.045867h33.467734a3.140267 3.140267 0 1 1 0 6.280533H103.6288a27.306667 27.306667 0 1 0 0 54.613334h236.151467a57.7536 57.7536 0 1 0 0-115.5072h-33.467734a0.512 0.512 0 0 1-0.290133-0.170667 0.3584 0.3584 0 0 1-0.119467-0.221867 1.416533 1.416533 0 0 1 1.041067-0.426666h238.250667a27.306667 27.306667 0 0 0 0-54.613334z"
fill="#E8D4AB"
p-id="4719"
></path>
<path
d="M654.523733 591.854933h-1.5872l20.48-409.6H405.1968l20.48 409.6"
fill="#AF3131"
p-id="4720"
></path>
<path
d="M654.523733 593.5616h-1.5872l20.48-409.6H405.1968l20.48 409.6"
fill="#D5382E"
p-id="4721"
></path>
<path
d="M654.523733 600.388267h-1.5872a6.826667 6.826667 0 0 1-6.826666-7.168l20.1216-402.432H412.3648l20.1216 402.432-13.653333 0.682666-20.48-409.6a6.826667 6.826667 0 0 1 6.826666-7.168h268.253867a6.826667 6.826667 0 0 1 6.826667 7.168l-20.48 409.6-5.239467-0.256z"
fill="#E8D4AB"
p-id="4722"
></path>
<path
d="M425.6768 589.704533l-166.365867-22.528L539.306667 868.471467l279.995733-301.294934-164.778667 22.528"
fill="#D5382E"
p-id="4723"
></path>
<path
d="M425.6768 591.4112l-166.365867-22.528L539.306667 870.178133l279.995733-301.294933-164.778667 22.528"
fill="#D5382E"
p-id="4724"
></path>
<path
d="M539.306667 874.837333a6.826667 6.826667 0 0 1-5.000534-2.184533L254.293333 571.357867a6.826667 6.826667 0 0 1 5.922134-11.4176l158.122666 21.418666-19.968-399.223466a6.826667 6.826667 0 0 1 6.826667-7.168h268.2368a6.826667 6.826667 0 0 1 6.826667 7.168l-19.968 399.240533 158.122666-21.435733a6.826667 6.826667 0 0 1 5.922134 11.4176L544.3072 872.6528a6.826667 6.826667 0 0 1-5.000533 2.184533zM277.2992 576.034133L539.306667 857.975467l262.007466-281.941334-147.3536 19.968a6.826667 6.826667 0 0 1-7.7312-7.099733l20.0192-400.2816H412.3648l20.0192 400.264533a6.826667 6.826667 0 0 1-7.7312 7.099734z"
fill="#E8D4AB"
p-id="4725"
></path>
<path
d="M780.7488 226.338133h105.540267a17.066667 17.066667 0 0 1 0 34.133334h-105.540267a13.380267 13.380267 0 1 0 0 26.760533h8.0384a44.782933 44.782933 0 1 1 0.631467 89.565867H503.7056a17.066667 17.066667 0 0 1 0-34.133334h285.730133a10.717867 10.717867 0 0 0 10.5984-11.7248 11.076267 11.076267 0 0 0-11.229866-9.5744h-8.0384a47.5136 47.5136 0 1 1 0-95.0272z"
fill="#D5382E"
p-id="4726"
></path>
<path
d="M886.289067 226.338133a17.066667 17.066667 0 0 1 0 34.133334h-105.540267a13.380267 13.380267 0 1 0 0 26.760533h8.0384a44.782933 44.782933 0 1 1 0.631467 89.565867H503.7056a17.066667 17.066667 0 0 1 0-34.133334h285.730133a10.717867 10.717867 0 0 0 10.5984-11.7248 11.076267 11.076267 0 0 0-11.229866-9.5744h-8.0384a47.5136 47.5136 0 1 1 0-95.0272h105.540266m0-10.24h-105.557333a57.7536 57.7536 0 1 0 0 115.5072h8.0384a1.416533 1.416533 0 0 1 1.041067 0.426667 0.341333 0.341333 0 0 1-0.119467 0.221867 0.802133 0.802133 0 0 1-0.221867 0.170666H503.7056a27.306667 27.306667 0 0 0 0 54.613334h285.730133a55.022933 55.022933 0 1 0-0.631466-110.045867h-8.0384a3.140267 3.140267 0 1 1 0-6.280533h105.540266a27.306667 27.306667 0 0 0 0-54.613334z"
fill="#E8D4AB"
p-id="4727"
></path>
</svg>
</template>
<template slot="footer">
<span>日销售额¥12423</span>
</template>
</Detail>
</el-card>
</el-col>
<template>
<!-- 容器 -->
<div class="charts" ref="charts"></div>
</template>
<script>
// 引入echarts
import * as echarts from "echarts";
export default {
name: "",
// 可以在组件挂载完毕以后,初始化我们的echarts实例
mounted() {
// 初始化echarts实例
let lineCharts = echarts.init(this.$refs.charts);
// 配置数据
lineCharts.setOption({
xAxis: {
// 隐藏x轴
show: false,
type: "category",
},
yAxis: {
// 隐藏Y轴
show: false,
},
// 系列
series: [
{
type: "line",
data: [10, 7, 33, 12, 48, 9, 29, 10, 44],
// 拐点样式的设置
itemStyle: {
opacity: 0,
},
// 线条的样式
lineStyle: {
color: "purple",
},
// 填充颜色
areaStyle: {
color: {
type: "linear",
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: "purple", // 0% 处的颜色
},
{
offset: 1,
color: "#fff", // 100% 处的颜色
},
],
global: false, // 缺省为 false
},
},
},
],
// 布局调整
grid: {
left: 0,
top: 0,
right: 0,
bottom: 0,
},
});
},
};
</script>
<style scoped>
.charts {
width: 100%;
height: 100%;
}
</style>