复制代码
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">商品图片</el-button>
</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>
</el-dialog>
</div>
</template>
<script>
import TreeGoods from '@/views/Goods/TreeGoods.vue';
export default {
components: {
TreeGoods
},
// 接收父组件(Goods.vue)传值dialogVisible
props: ['dialogVisible'],
data() {
return {
// dialogVisible: false, // 外弹框
innerVisible: false, // 内弹框
treeData: {}, // 接收 tree 数据
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: {
// 显示 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/TreeGoods.vue
<template>
<!-- props="props" 渲染的数据
配置选项:
label: 'name', // 指定节点标签为节点对象的某个属性值
children: 'zones', // 指定子树为节点对象的某个属性值
isLeaf: 'leaf' // 指定节点是否为叶子节点,仅在指定了 lazy 属性的情况下生效
:load="loadNode" // 加载子树数据的方法,仅当 lazy 属性为true 时生效 自动执行函数 -- 异步请求数据
lazy // 是否懒加载子节点,需与 load 方法结合使用
show-checkbox> // 节点是否可被选择 选择框
accordion // 是否每次只打开一个同级树节点展开
node-click // 节点被点击时的回调 共三个参数,依次为:传递给 data 属性的数组中该节点所对应的对象、节点对应的 Node、节点组件本身。
-->
<el-tree :props="props" :load="loadNode" lazy accordion @node-click="nodeClick">
</el-tree>
</template>
<script>
export default {
data() {
return {
props: {
label: 'name', // 指定节点标签为节点对象的某个属性值
children: 'zones', // 指定子树为节点对象的某个属性值
isLeaf: 'leaf' // 指定节点是否为叶子节点,仅在指定了 lazy 属性的情况下生效
},
};
},
methods: {
// 点击 tree 获取数据
nodeClick(data, node) {
console.log(data, node);
// 传递数据给父组件
this.$emit('sendTreeData', data)
},
loadNode(node, resolve) { // resolve() 成功的返回数据结果
// console.log('load--自动执行', node);
if (node.level === 0) {
// 进入页面 获取第一层的tree数据
// this.$api.getSelectCategory()
// .then(res => {
// console.log('一级tree', res.data);
// return resolve(res.data.result);
// })
return resolve([{
name: '家用电器'
}, {
name: '手机/运营商/数码'
}, {
name: '电脑/办公'
}, {
name: '家具/家居'
}]);
}
// 合并 所有级别(level)大于等1
// if (node.level >= 1) { // 合并
// // 请求当前的点击的 tree 下面的数据
// this.$api.getSelectCategory({
// id: node.data.cid
// })
// .then(res => {
// console.log('二级tree', res.data);
// if (res.data.status === 200) {
// return resolve(res.data.result);
// } else {
// return resolve([])
// }
// })
// }
if (node.level == 1) {
// 请求当前的点击的 tree 下面的数据
// this.$api.getSelectCategory({ // 动态从数据库中拿数据
// id: node.data.cid
// })
// .then(res => {
// console.log('二级tree', res.data);
// if (res.data.status === 200) {
// return resolve(res.data.result);
// } else {
// return resolve([])
// }
// })
return resolve([{
name: '电视'
}, {
name: '空调'
}, {
name: '洗衣机'
}, {
name: '冰箱'
}], [{
name: '手机通讯'
}, {
name: '运营商'
}, {
name: '摄影'
}, {
name: '摄像'
}], [{
name: '电脑整机'
}, {
name: '电脑配件'
}, {
name: '外设产品'
}, {
name: '游戏设备'
}], [{
name: '厨具'
}, {
name: '家纺'
}, {
name: '灯具'
}, {
name: '家具'
}]);
}
// if (node.level == 2) {
// // 请求当前的点击的 tree 下面的数据
// this.$api.getSelectCategory({
// id: node.data.cid;
// })
// .then(res => {
// console.log('三级tree', res.data);
// if (res.data.status === 200) {
// return resolve(res.data.result);
// } else {
// return resolve([])
// }
// })
// return resolve([{
// name: '超薄电视'
// }, {
// name: '全屏电视'
// }]);
// }
}
}
};
</script>
<style>
</style>
3, src/router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Layout from '@/views/Layout/index.vue'
import Home from '@/views/Home/index.vue'
import Login from '@/views/Login/Login.vue'
// 异步
const Goods = () => import('../views/Goods/Goods.vue')
const Params = () => import('../views/Params/Params.vue')
const Advert = () => import('../views/Advert/Advert.vue')
const My = () => import('../views/My/My.vue')
const Logistics = () => import('../views/Logistics/Logistics.vue')
const Order = () => import('../views/Order/index.vue')
const OrderList = () => import('../views/Order/OrderList/index.vue')
const OrderBack = () => import('../views/Order/OrderBack/index.vue')
// 子级路由
const AddGoods = () => import('../views/Goods/AddGoods.vue')
Vue.use(VueRouter)
const routes = [{
path: '',
component: Layout,
children: [{
path: '/',
name: 'Home',
component: Home
}, {
path: '/goods',
name: 'Goods',
component: Goods
}, {
path: '/add-goods',
name: 'AddGoods',
component: AddGoods
}, {
path: '/params',
name: 'Params',
component: Params
}, {
path: '/advert',
name: 'Advert',
component: Advert
}, {
path: '/my',
name: 'My',
component: My
}, {
path: '/logistics',
name: 'Logistics',
component: Logistics
}, {
path: '/order',
name: 'Order',
component: Order,
redirect: '/order/order-list',
children: [{
path: 'order-list',
component: OrderList
}, {
path: 'order-back',
component: OrderBack
}]
}]
},
{
path: '/login',
name: 'Login',
component: Login,
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
4, src/api/base.js
/**
* 接口的路径配置:
* 一般文件目录: base.js index.js
* base.js : 放所有路径的配置
* index.js: 放所有请求的方法
*/
const base = {
goodsList: '/api/api/projectList', // 商品列表
search: '/api/api/search', // 商品的搜索功能
selectCategory: '/api/api/backend/itemCategory/selectItemCategoryByParentId', // 类目选择
}
export default base;
5, src/api/index.js
/**
* 所有请求的方法
*/
import axios from "axios";
import base from "./base";
const api = {
/**
* 商品列表方法
*/
getGoodsList(params) { // {page:xx}
return axios.get(base.goodsList, {
params
})
},
/**
* 搜索商品数据方法
* search
*/
getSearch(params) { // {search: xx}
return axios.get(base.search, {
params
})
},
/**
* 获取类目选择
* {id: cid}
*/
getSelectCategory(params) {
return axios.get(base.selectCategory, {
params
})
},
}
export default api;
6, server/router.js
// 专门放所有的接口 这里只写一部分大约有二十几个接口
// 导入 express
const express = require('express')
// 使用里面的 Router() 这个方法
const router = express.Router()
// 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: '暂无数据'
})
}
})
})
module.exports = router