复制代码
1, src/views/Goods/GoodsDialog.vue
<template>
<div>
<!--
title="添加商品" 弹框的标题
:visible.sync="dialogVisible" 控制弹框的显示与隐藏 boolean true 表示显示
width="70%" 宽度 大小
-->
<el-dialog title="添加商品" :visible.sync="dialogVisible" width="70%">
<!-- 中间弹框内容区域 -->
<!-- 添加商品表单数据 -->
<div class="myform">
<el-form :model="goodsForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
<el-form-item label="类目选择" prop="category">
<el-button type="primary" @click="innerVisible=true">类目选择</el-button>
<span>{{goodsForm.category}}</span>
</el-form-item>
<el-form-item label="商品名称" prop="title">
<el-input v-model="goodsForm.title"></el-input>
</el-form-item>
<el-form-item label="商品价格" prop="price">
<el-input v-model="goodsForm.price"></el-input>
</el-form-item>
<el-form-item label="商品数量" prop="num">
<el-input v-model="goodsForm.num"></el-input>
</el-form-item>
<el-form-item label="发布时间" required>
<el-col :span="11">
<el-form-item prop="date1">
<el-date-picker type="date" placeholder="选择日期" v-model="goodsForm.date1"
style="width: 100%;"></el-date-picker>
</el-form-item>
</el-col>
<el-col class="line" :span="2">-</el-col>
<el-col :span="11">
<el-form-item prop="date2">
<el-time-picker placeholder="选择时间" v-model="goodsForm.date2"
style="width: 100%;"></el-time-picker>
</el-form-item>
</el-col>
</el-form-item>
<el-form-item label="商品卖点" prop="sellPoint">
<el-input v-model="goodsForm.sellPoint"></el-input>
</el-form-item>
<el-form-item label="商品图片" prop="image">
<el-button type="primary" @click="innerVisibleImg=true">上传图片</el-button>
<img :src="goodsForm.image" height="200px" style="margin-left: 10px;" alt="" />
</el-form-item>
<el-form-item label="商品描述" prop="descs">
<textarea name="" id="" cols="30" rows="10"></textarea>
</el-form-item>
<el-form-item label="活动区域" prop="region">
<el-select v-model="goodsForm.region" placeholder="请选择活动区域">
<el-option label="区域一" value="shanghai"></el-option>
<el-option label="区域二" value="beijing"></el-option>
</el-select>
</el-form-item>
<el-form-item label="即时配送" prop="delivery">
<el-switch v-model="goodsForm.delivery"></el-switch>
</el-form-item>
<el-form-item label="活动性质" prop="type">
<el-checkbox-group v-model="goodsForm.type">
<el-checkbox label="美食/餐厅线上活动" name="type"></el-checkbox>
<el-checkbox label="地推活动" name="type"></el-checkbox>
<el-checkbox label="线下主题活动" name="type"></el-checkbox>
<el-checkbox label="单纯品牌曝光" name="type"></el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="特殊资源" prop="resource">
<el-radio-group v-model="goodsForm.resource">
<el-radio label="线上品牌商赞助"></el-radio>
<el-radio label="线下场地免费"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="活动形式" prop="desc">
<el-input type="textarea" v-model="goodsForm.desc"></el-input>
</el-form-item>
<!-- <el-form-item>
<el-button type="primary" @click="submitForm('goodsForm')">确定</el-button>
<el-button @click="resetForm('goodsForm')">取消</el-button>
</el-form-item> -->
</el-form>
</div>
<!-- 弹框的底部区域 -->
<span slot="footer" class="dialog-footer">
<!-- ref -->
<!-- <el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="dialogVisible = false">确 定</el-button> -->
<!-- 父传子 -->
<el-button @click="close">取消</el-button>
<el-button type="primary" @click="close">确 定</el-button>
</span>
<!-- 1, 内弹框 --类目选择-->
<el-dialog width="40%" title="类目选择" :visible.sync="innerVisible" append-to-body>
<!-- 父组件接收 sendTreeData 数据 -->
<TreeGoods @sendTreeData="sendTreeData" />
<!-- 弹框的底部区域 -->
<span slot="footer" class="dialog-footer">
<!-- ref -->
<!-- <el-button @click="innerVisible = false">取 消</el-button>
<el-button type="primary" @click="innerVisible = false">确 定</el-button> -->
<!-- 父传子 -->
<el-button @click="close">取消</el-button>
<el-button type="primary" @click="showTreeData">确 定</el-button>
</span>
</el-dialog>
<!-- 2, 内弹框 --上传图片-->
<el-dialog width="40%" title="上传图片" :visible.sync="innerVisibleImg" append-to-body>
<!-- 父组件接收 sendImg 数据 -->
<UploadImg @sendImg="sendImg" />
<!-- 弹框的底部区域 -->
<span slot="footer" class="dialog-footer">
<!-- ref -->
<el-button @click="innerVisibleImg = false">取 消</el-button>
<el-button type="primary" @click="showImg">确 定</el-button>
<!-- <el-button type="primary" @click="innerVisibleImg = false">确 定</el-button> -->
<!-- 父传子 -->
<!-- <el-button @click="close">取消</el-button>
<el-button type="primary" @click="showTreeData">确 定</el-button> -->
</span>
</el-dialog>
</el-dialog>
</div>
</template>
<script>
import TreeGoods from '@/views/Goods/TreeGoods.vue';
import UploadImg from '@/views/Goods/UploadImg.vue';
export default {
components: {
TreeGoods,
UploadImg
},
// 接收父组件(Goods.vue)传值dialogVisible
props: ['dialogVisible'],
data() {
return {
// dialogVisible: false, // 外弹框
innerVisible: false, // 内弹框
innerVisibleImg: false, // 图片弹框
treeData: {}, // 接收 tree 数据
imgUrl: '', // 图片地址
goodsForm: { // 表单容器-对象
title: '', // 商品的名称
price: '', // 商品的价格
num: '', // 商品的数量
sellPoint: '', // 商品的卖点
image: '', // 商品的图片
descs: '', // 商品的描述
category: '', // 商品的类目
// time: '', // 商品发布时间
region: '',
date1: '',
date2: '',
delivery: false,
type: [],
resource: '',
desc: ''
},
rules: { // 效验规则
title: [{
required: true,
message: '请输入商品名称',
trigger: 'blur'
},
{
min: 2,
max: 8,
message: '长度在 2 到 8 个字符',
trigger: 'blur'
}
],
price: [{
required: true,
message: '请输入商品价格',
trigger: 'blur'
}
// {
// min: 3,
// max: 5,
// message: '长度在 3 到 5 个字符',
// trigger: 'blur'
// }
],
num: [{
required: true,
message: '请输入商品数量',
trigger: 'blur'
}
// {
// min: 3,
// max: 5,
// message: '长度在 3 到 5 个字符',
// trigger: 'blur'
// }
],
name: [{
required: true,
message: '请输入活动名称',
trigger: 'blur'
},
{
min: 3,
max: 5,
message: '长度在 3 到 5 个字符',
trigger: 'blur'
}
],
region: [{
// required: true,
message: '请选择活动区域',
trigger: 'change'
}],
date1: [{
type: 'date',
required: true,
message: '请选择日期',
trigger: 'change'
}],
date2: [{
type: 'date',
required: true,
message: '请选择时间',
trigger: 'change'
}],
type: [{
type: 'array',
// required: true,
message: '请至少选择一个活动性质',
trigger: 'change'
}],
resource: [{
// required: true,
message: '请选择活动资源',
trigger: 'change'
}],
desc: [{
// required: true,
message: '请填写活动形式',
trigger: 'blur'
}]
}
};
},
methods: {
// 显示图片地址
sendImg(val) {
console.log('显示图片地址', val);
this.imgUrl = val;
},
// 显示图片---确定按钮
showImg() {
// 让内弹框隐藏
this.innerVisibleImg = false;
// 渲染图片到页面
this.goodsForm.image = this.imgUrl;
},
// 显示 tree 的数据
showTreeData() {
// 关闭内弹框
this.innerVisible = false;
// 显示 tree 数据
this.goodsForm.category = this.treeData.name;
},
// 获取 tree 数据
sendTreeData(val) {
console.log('tree数据', val);
this.treeData = val;
},
// 自定义事件--通知父组件--修改变量 dialogVisible
close() {
// 赋值
// this.$emit('changeDialog', false);
this.$emit('changeDialog');
},
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
alert('submit!');
} else {
console.log('error submit!!');
return false;
}
});
},
resetForm(formName) {
this.$refs[formName].resetFields();
}
}
}
</script>
<style lang="less" scoped>
.myform {
background: #fff;
padding: 20px;
padding-right: 30px;
}
.line {
text-align: center;
}
</style>
2, src/views/Goods/UploadImg.vue
<template>
<!--
ref="upload" // 获取DOM元素 获取--upload 就可以使用upload的方法
action // 必选参数,上传的地址
action="https://jsonplaceholder.typicode.com/posts/"
on-preview // 点击文件列表中已上传的文件时的钩子 类型:function(file)
:on-preview="handlePreview"
on-remove // 文件列表移除文件时的钩子 类型: function(file, fileList)
:on-remove="handleRemove"
file-list // 上传的文件列表 类型: Array []
:file-list="fileList"
auto-upload // 是否在选取文件后立即进行上传 类型:boolean 默认值为 true
:auto-upload="false">
on-success // 文件上传成功时的钩子 function(response, file, fileList)
on-error 文件上传失败时的钩子 function(err, file, fileList)
on-progress 文件上传时的钩子 function(event, file, fileList)
on-change 文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用 function(file, fileList)
multiple // 是否支持多选文件
-->
<el-upload class="upload-demo" ref="upload" :action="url" :on-preview="handlePreview" :on-remove="handleRemove"
:on-success="successUpload" :file-list="fileList" :auto-upload="false">
<el-button slot="trigger" size="small" type="primary">选取文件</el-button>
<el-button style="margin-left: 10px;" size="small" type="success" @click="submitUpload">上传到服务器</el-button>
<!-- <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div> -->
</el-upload>
</template>
<script>
import base from '@/api/base.js'
export default {
data() {
return {
url: base.uploadUrl, // 图片地址服务器
fileList: [],
// fileList: [{
// name: 'food.jpeg',
// url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100'
// }, {
// name: 'food2.jpeg',
// url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100'
// }]
};
},
methods: {
// 点击 '上传图片' submit() 手动上传文件列表 按钮触发的事件 这个方法是当前库(elemnet-ui)提供的
submitUpload() {
this.$refs.upload.submit();
},
// 上传成功的函数
successUpload(response, file, fileList) {
console.log('上传成功', response, file, fileList);
this.$message({
message: '恭喜你,图片上传成功',
type: 'success'
});
// 把成功的数据接口 response 传递给 父组件
// http://localhost:8989/1764489007859-server-mesh7.png
let imgUrl = base.host + '/' + response.url.slice(7);
this.$emit('sendImg', imgUrl);
},
handleRemove(file, fileList) {
console.log(file, fileList);
},
handlePreview(file) {
console.log(file);
}
}
}
</script>
<style>
</style>
3, src/api/base.js
/**
* 接口的路径配置:
* 一般文件目录: base.js index.js
* base.js : 放所有路径的配置
* index.js: 放所有请求的方法
*/
const base = {
host: 'http://localhost:8989', // 基础域名
goodsList: '/api/api/projectList', // 商品列表
search: '/api/api/search', // 商品的搜索功能
selectCategory: '/api/api/backend/itemCategory/selectItemCategoryByParentId', // 类目选择
uploadUrl: '/api/api/upload', // 图片上传 post请求
}
export default base;
4, src/api/index.js
// 搭建 express 服务
const express = require('express')
const app = express()
// post 请求表单数据
app.use(express.urlencoded({
extended: true
}))
// 静态文件托管 --- 访问: http://localhost:8989/图片.jpg
app.use(express.static('upload'))
// 导入路由
const router = require('./router.js')
// 配置根路径
app.use('/api', router)
// 监听
app.listen(8989, () => {
console.log(8989);
})
5, server/router.js
// 专门放所有的接口 这里只写一部分大约有二十几个接口
// 导入 express
const express = require('express')
// 使用里面的 Router() 这个方法
const router = express.Router()
// 导入 multer
const multer = require('multer')
// 导入 fs
const fs = require('fs')
// token
// const jwt = require('jsonwebtoken')
// 秘钥
// const config = require('./secert.js')
// 导入数据库 sqlFn('sql',[],res=>{})
const sqlFn = require('./mysql.js')
// 图片上传支持的模块
// const multer = require('multer')
// const fs = require('fs')
// 测试接口
// router.get('/', (req, res) => {
// res.send('hello')
// })
// 路由接口
// 登录接口
/**
* 语法
* 如 60,'2 day','10h','7d',expiration time 过期时间
* jwt.sign({},'秘钥','过期时间',{expiresIn: 20*1,'1 day','1h'})
*/
/**
* 登录 login
*/
// router.post('/login', (req, res) => {
// //
// })
/**
* 注册接口 /register
*/
// router.post('/register', (req, res) => {
// //
// })
/**
* 商品列表:获取分页 {total: '',arr:[{},{},{}],pagesize:8,}
* 参数:page 页码
*/
router.get('/projectList', (req, res) => {
const page = req.query.page || 1;
const sqlLen = "select * from project where id";
sqlFn(sqlLen, null, data => {
let len = data.length;
const sql = "select * from project order by id desc limit 8 offset" + (page - 1) * 8;
sqlFn(sql, null, result => {
if (result.length > 0) {
res.send({
status: 200,
data: result,
pageSize: 8,
total: len
})
} else {
res.send({
status: 200,
msg: "暂无数据"
})
}
})
})
})
// router.get('/projectList', (req, res) => {
// // 接收页码 可以不传 默认为1
// const page = req.query.page || 1;
// // 根据 id 去查 project 表
// const sqlLen = "select * from project where id";
// sqlFn(sqlLen, null, data => {
// let len = data.length;
// const sql = "select * from project order by id desc limit 8 offset" + (page - 1) * 8;
// sqlFn(sql, null, result => {
// if (result.length > 0) {
// // 返回数据
// res.send({
// status: 200,
// data: result,
// pageSize: 8,
// total: len
// })
// } else {
// // 返回数据
// res.send({
// status: 500,
// msg: "暂无数据"
// })
// }
// })
// })
// })
/**
* 商品查询接口 search
* 参数: search
*/
router.get("/search", (req, res) => {
var search = req.query.search;
const sql = "select * from project where concat(`title`,`sellPoint`,`descs`) like '%" + search + "%'";
sqlFn(sql, null, (result) => {
if (result.length > 0) {
res.send({
status: 200,
data: result
})
} else {
res.send({
status: 500,
msg: '暂无数据'
})
}
})
})
/** 类目选择
* 接口说明:接口不同的参数 cid 返回不同的类目数据,后台接受变量 id
*/
router.get('/backend/itemCategory/selectItemCategoryByParentId', (req, res) => {
const id = req.query.id || 1;
const sql = 'select * from category where id=?'
var arr = [id];
sqlFn(sql, arr, result => {
if (result.length > 0) {
res.send({
status: 200,
result
// data: result
})
} else {
res.send({
status: 500,
msg: '暂无数据'
})
}
})
})
/**
* 类目结构数据获取
*/
router.get('/category/data', (req, res) => {
var cid = req.query.cid;
var sql = "select * from params where itemCatId=?";
sqlFn(sql, [cid], result => {
if (result.length > 0) {
res.send({
status: 200,
result
// data: result
})
} else {
res.send({
status: 500,
msg: '暂无数据'
})
}
})
})
/**
* 上传图片 post 请求 upload
* 说明:
* 1, 后台安装 multer 图片模块 同时引入 fs 文件模块
* 2,router.js 入口文件导入 模块
* const fs = require('fs') //fs是属于nodejs,只需引入即可
* const multer=require('multer') // multer是需要安装的
* 3, 上传图片 可以跨域 需要配置 cors index.js 导入文件,并配置 cors跨域
* 4, 在服务端 server 根目录下创建 upload 文件夹,专门装图片的文件
*/
var storage = multer.diskStorage({
destination: function(req, file, cb) {
cb(null, './upload/')
},
filename: function(req, file, cb) {
cb(null, Date.now() + "-" + file.originalname)
}
})
var createFolder = function(folder) {
try {
fs.accessSync(folder);
} catch (e) {
fs.mkdirSync(folder);
}
}
var uploadFolder = './upload';
createFolder(uploadFolder);
var upload = multer({
storage: storage
});
router.post('/upload', upload.single('file'), function(req, res, next) {
var file = req.file;
console.log('文件类型,%s', file.mimetype);
console.log('原始文件名,%s', file.originalname);
console.log('文件大小,%s', file.size);
console.log('文件保存路径,%s', file.path);
res.json({
res_code: '0',
name: file.originalname,
url: file.path
});
});
module.exports = router