商城后台管理系统 06,类目选择实现

类目选择实现 实现代码如下
复制代码
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
动态获取数据
复制代码
methods: {
	loadNode(node,resolve) {
		if(node.level === 0) {
			this.$api.getSelectCategory()
			.then(res=>{
				return resolve(res.data.result);
			})
		}
		if (node.level >= 1) {
			this.$api.getSelectCategory({
				id:node.data.cid
			})
			.then(res => {
				if (res.data.status === 200) {
					return resolve(res.data.result);
				} else {
					return resolve([])
				}
			})
		}
	}
}

类目选择接口(后端代码)

复制代码
/** 
* 类目选择
* 接口说明:接口不同的参数 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,
				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,
				data: result
			})
		} else {
			res.send({
				status: 500,
				msg: '暂无数据'
			})
		}
	})
})
上传图片接口(后端代码)
复制代码
/**
* 上传图片 post 请求 upload
* 说明:
* 1, 后台安装 multer 模块 同时引入 fs 模块
* 2,router.js 入口文件导入 模块
*	const fs = require('fs')
*	const multer=require('multer')
* 3, 上传图片 不能跨域 需要配置 cors index.js 导入文件,并配置 cors跨域
*/
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
	});
});
相关推荐
少年张二狗2 小时前
Vue + Element-UI 图片上传实现拖拽排序功能
前端·vue.js·ui
qingyun9892 小时前
使用递归算法深度收集数据结构中的点位信息
开发语言·javascript·ecmascript
哆啦A梦15882 小时前
【vue实战】商城后台管理系统 01 项目介绍
前端·javascript·vue.js
布茹 ei ai3 小时前
5、基于 GEE 的 Sentinel-1 SAR 地震滑坡变化检测系统:2022 泸定地震案例
javascript·sentinel·遥感·gee·云平台
一字白首3 小时前
Vue Router 进阶,声明式 / 编程式导航 + 重定向 + 404 + 路由模式
前端·javascript·vue.js
d111111111d3 小时前
C语言中static修斯局部变量,全局变量和函数时分别由什么特性
c语言·javascript·笔记·stm32·单片机·嵌入式硬件·学习
GIS好难学3 小时前
0帧起手《Vue零基础教程》,从前端框架到GIS开发系列课程
前端·vue.js·前端框架
李瑞丰_liruifengv3 小时前
使用 Claude Agent SDK 写一个 DeepResearch Agent
javascript·aigc·agent
LYFlied3 小时前
Vue Vapor模式与AI时代前端发展的思考:虚拟DOM与框架的未来
前端·vue.js·人工智能·前端框架