# 一、初步创建界面
# 1. 总路由
app/router/admin/shop.js
...
//商品参数goods_param处理
router.get('/shop/admin/goods-/:goods_id/createGoodsParam', controller.admin.goods.createGoodsParam);
router.post('/shop/admin/goods-/:goods_id/saveGoodsParam', controller.admin.goods.saveGoodsParam);
router.get('/shop/admin/goods-/:goods_id/indexGoodsParam', controller.admin.goods.indexGoodsParam);
router.get('/shop/admin/goods-/:id/editGoodsParam', controller.admin.goods.editGoodsParam);
router.post('/shop/admin/goods-/:id/updateGoodsParam', controller.admin.goods.updateGoodsParam);
router.get('/shop/admin/goods-/:id/deleteGoodsParam', controller.admin.goods.deleteGoodsParam);
//商品图片goods_banner处理
...
# 2.新建模型
app/model/goods_param.js
'use strict';
module.exports = app => {
const { INTEGER, STRING, DATE, ENUM, TEXT, BIGINT } = app.Sequelize;
const GoodsParam = app.model.define('goods_param', {
id: {
type: INTEGER(20).UNSIGNED,
primaryKey: true,
autoIncrement: true,
comment: '主键id'
},
goods_id: {
type: INTEGER(20).UNSIGNED,
allowNull: false,
// defaultValue:0,
comment: '商品id',
references: { //关联关系
model: 'goods', //关联的表
key: 'id' //关联表的主键
},
onDelete: 'cascade', //删除时操作
onUpdate: 'restrict', // 更新时操作
},
paraminfo: {
type: STRING(5000),
allowNull: false,
defaultValue: '',
comment: '商品参数信息'
},
order: {
type: INTEGER,//不限定长度.默认int(11)
allowNull: true,
defaultValue: 50,
comment: '排序,默认50'
},
status: {
type: INTEGER(1),
allowNull: false,
defaultValue: 1,
comment: '状态:1:启用,0:禁用'
},
// sex: { type: ENUM, values: ['男','女','保密'], allowNull: true, defaultValue: '保密', comment: '留言用户性别'},
create_time: {
type: DATE,
allowNull: false,
defaultValue: app.Sequelize.fn('NOW'),
get() {
return app.formatTime(this.getDataValue('create_time'));
}
},
update_time: { type: DATE, allowNull: false, defaultValue: app.Sequelize.fn('NOW') }
});
// 模型关联关系
GoodsParam.associate = function (models) {
// 关联商品 反向一对多
GoodsParam.belongsTo(app.model.Goods,{
foreignKey: 'goods_id', // 关联外键
});
}
return GoodsParam;
}
# 3. 传递一个id值,根据id和pid的关系,得到它下面的所有子集,并返回树形结构的数据
//传递一个id值,根据id和pid的关系,得到它下面的所有子集,并返回树形结构的数据
async datatree_byid(id,modelname = 'GoodsClass') {
const { ctx, app } = this;
try {
// 获取请求中的查询参数 id
// const id = ctx.params._id ? parseInt(ctx.params._id, 10) : null;
// 看一下数据库分类表所有数据
let allCategories = await ctx.model[modelname].findAll({
order: [
['order', 'asc'],
['id', 'asc'],
],
});
// 将所有分类数据转换为对象,方便查找
let categoriesMap = {};
allCategories.forEach(category => {
categoriesMap[category.id] = { ...category.toJSON(), children: [] };
});
// 构建树形结构
allCategories.forEach(category => {
if (categoriesMap[category.pid]) {
categoriesMap[category.pid].children.push(categoriesMap[category.id]);
}
});
// 添加level属性的递归函数
const addLevel = (nodes, level = 0) => {
nodes.forEach(node => {
node.level = level;
if (node.children.length > 0) {
addLevel(node.children, level + 1);
}
});
};
// 如果传入了 id,则找到对应的节点及其子节点
let result = [];
if (id !== null) {
const findNode = (nodes, targetId) => {
for (let node of nodes) {
if (node.id === targetId) {
return node;
}
let found = findNode(node.children, targetId);
if (found) {
return found;
}
}
return null;
};
// 确保 nodes 是一个数组
let rootCategories = Object.values(categoriesMap).filter(category => category.pid === 0);
result = findNode(rootCategories, id);
if (result) {
result = [result]; // 返回数组形式
addLevel(result); // 添加level属性
} else {
result = [];
}
} else {
// 如果没有传入 id,则返回整个树形结构
result = Object.values(categoriesMap).filter(category => category.pid === 0);
addLevel(result); // 添加level属性
}
// 返回数据
// ctx.body = result;
// 返回数据
// ctx.body = {
// datatree: result,
// };
return result;
} catch (error) {
// 错误处理
console.error('Error fetching categories:', error);
ctx.apiFail('Failed to fetch categories');
}
}
# 4.控制器
app/controller/admin/goods.js
//商品参数部分
//新增商品参数界面
async createGoodsParam(){
const { ctx, app } = this;
//1.参数验证
this.ctx.validate({
goods_id: {
type: 'int',
required: true,
desc: '商品id',
// defValue: 0,
range:{
min:1,
}
},
});
// 参数
const goods_id = ctx.params.goods_id;
let data = await app.model.Goods.findOne({ where: { id:goods_id } });
if (!data) {
return ctx.apiFail('该商品不存在');
}
// 根据商品id获取商品分类,然后获取分类里面的skus值
let goodsClassId = data.goods_class_id;
// ctx.body = goodsClassId; return;
// 根据商品分类id读取商品分类及其子分类,并转成树形结构
const goodsClassTree = await this.datatree_byid(goodsClassId,'GoodsClass');
// ctx.body = goodsClassTree; return;
//渲染公共模版
/*
await ctx.renderTemplate({
title: '创建商品参数',//现在网页title,面包屑导航title,页面标题
tempType: 'form', //模板类型:table表格模板 ,form表单模板
form: {
//提交地址
action: "",
// 字段
fields: [
{
label: '商品分类',
type: 'dropdown', //下拉框
name: 'pid',
default: JSON.stringify(goodsClassTree),
placeholder: '请选择一个分类',
},
],
},
//新增成功之后跳转到哪个页面
successUrl: '',
});
*/
//看一下当前商品分类在商品skus里面有没有创建的信息
let skusdata = await app.model.Skus.findAll({ where: { goods_class_id:goodsClassId } });
// ctx.body = skusdata; return;
if(skusdata){
// ctx.body = '有对应的skus信息,可继续添加skus信息'
await ctx.renderTemplate({
title: '创建商品参数',//现在网页title,面包屑导航title,页面标题
tempType: 'for_form', //模板类型:table表格模板 ,form表单模板,for_form循环表单模板
form: {
//提交地址
action: "",
// 字段
fields: [
{
label: '商品分类',
type: 'dropdown', //下拉框
name: 'pid',
default: JSON.stringify(goodsClassTree),
placeholder: '请选择一个分类',
},
],
},
//新增成功之后跳转到哪个页面
successUrl: '',
});
}else{
ctx.body = '没有对应的skus信息,先添加skus信息'
}
}
//新增商品参数数据
async saveGoodsParam(){}
//商品参数列表
async indexGoodsParam(){}
//修改商品参数界面
async editGoodsParam(){}
//修改商品参数数据
async updateGoodsParam(){}
//删除商品参数
async deleteGoodsParam(){}
// 传递一个id值,根据id和pid得到相关所有数据,并返回树形结构的数据
async datatree_byid(id,modelname = 'GoodsClass') {
const { ctx, app } = this;
try {
// 获取请求中的查询参数 id
// const id = ctx.params._id ? parseInt(ctx.params._id, 10) : null;
// 看一下数据库分类表所有数据
let allCategories = await ctx.model[modelname].findAll({
order: [
['order', 'asc'],
['id', 'asc'],
],
});
// 将所有分类数据转换为对象,方便查找
let categoriesMap = {};
allCategories.forEach(category => {
categoriesMap[category.id] = { ...category.toJSON(), children: [] };
});
// 构建树形结构
allCategories.forEach(category => {
if (categoriesMap[category.pid]) {
categoriesMap[category.pid].children.push(categoriesMap[category.id]);
}
});
// 添加level属性的递归函数
const addLevel = (nodes, level = 0) => {
nodes.forEach(node => {
node.level = level;
if (node.children.length > 0) {
addLevel(node.children, level + 1);
}
});
};
// 如果传入了 id,则找到对应的节点及其子节点
let result = [];
if (id !== null) {
const findNode = (nodes, targetId) => {
for (let node of nodes) {
if (node.id === targetId) {
return node;
}
let found = findNode(node.children, targetId);
if (found) {
return found;
}
}
return null;
};
// 确保 nodes 是一个数组
let rootCategories = Object.values(categoriesMap).filter(category => category.pid === 0);
result = findNode(rootCategories, id);
if (result) {
result = [result]; // 返回数组形式
addLevel(result); // 添加level属性
} else {
result = [];
}
} else {
// 如果没有传入 id,则返回整个树形结构
result = Object.values(categoriesMap).filter(category => category.pid === 0);
addLevel(result); // 添加level属性
}
// 返回数据
// ctx.body = result;
// 返回数据
// ctx.body = {
// datatree: result,
// };
return result;
} catch (error) {
// 错误处理
console.error('Error fetching categories:', error);
ctx.apiFail('Failed to fetch categories');
}
}
# 5. 调整一下模版
# 1. app/view/admin/common/template.html
...
{% if tempType === 'table' %}
{% include "admin/layout/_table.html" %}
{% endif %}
{% if tempType === 'form' %}
{% include "admin/layout/_form.html" %}
{% endif %}
{% if tempType === 'for_form' %}
{% include "admin/layout/_for_form.html" %}
{% endif %}
...
# 2. 新建 app/view/admin/layout/_for_form.html
<div class="card">
<div class="card-body">
{% if form %}
<form action="{{form.action}}" method="post">
<div class="text-right mt-3">
<button type="submit" class="btn btn-primary"
@click.stop.prevent="submit">提 交</button>
</div>
</form>
{% endif %}
</div>
</div>
# 二、创建商品参数
# 1. 控制器
app/controller/admin/goods.js
//新增商品参数界面
async createGoodsParam(){
const { ctx, app } = this;
//1.参数验证
this.ctx.validate({
goods_id: {
type: 'int',
required: true,
desc: '商品id',
// defValue: 0,
range:{
min:1,
}
},
});
// 参数
const goods_id = ctx.params.goods_id;
let data = await app.model.Goods.findOne({ where: { id:goods_id } });
if (!data) {
return ctx.apiFail('该商品不存在');
}
// 根据商品id获取商品分类,然后获取分类里面的skus值
let goodsClassId = data.goods_class_id;
// ctx.body = goodsClassId; return;
// 根据商品分类id读取商品分类及其子分类,并转成树形结构
let goodsClassTree = await this.datatree_byid(goodsClassId, 'GoodsClass');
// ctx.body = goodsClassTree; return;
/*
//渲染公共模版
await ctx.renderTemplate({
title: '创建商品参数',//现在网页title,面包屑导航title,页面标题
tempType: 'form', //模板类型:table表格模板 ,form表单模板
form: {
//提交地址
action: "",
// 字段
fields: [
{
label: '商品分类',
type: 'dropdown', //下拉框
name: 'pid',
default: JSON.stringify(goodsClassTree),
placeholder: '请选择一个商品分类',
},
],
},
//新增成功之后跳转到哪个页面
successUrl: '',
});
*/
//看一下当前商品分类在商品skus里面有没有创建的信息
let skusdata = await app.model.Skus.findAll({
where:{
goods_class_id:goodsClassId
}
});
// ctx.body = skusdata; return;
if(skusdata){
// ctx.body = '有对应的skus信息,可继续添加skus信息'
await ctx.renderTemplate({
title: '创建商品参数',//现在网页title,面包屑导航title,页面标题
tempType: 'form', //模板类型:table表格模板 ,form表单模板,for_form循环模版
form: {
//提交地址
action: "/shop/admin/goods-/"+goods_id+"/saveGoodsParam",
// 字段
fields: [
// {
// label: '商品分类',
// type: 'dropdown', //下拉框
// name: 'pid',
// default: JSON.stringify(goodsClassTree),
// placeholder: '请选择一个商品分类',
// },
{
label: '商品参数',
type: 'for_form', //循环遍历数组数据到表单
name: 'paraminfo',
default: JSON.stringify(skusdata),
placeholder: '',
},
],
},
//新增成功之后跳转到哪个页面
successUrl: '/shop/admin/goods-/'+goods_id+'/indexGoodsParam',
});
}else{
ctx.body = '没有对应的skus信息,先添加skus信息'
}
}
//新增商品参数数据
async saveGoodsParam(){
const { ctx, app } = this;
//1.参数验证
this.ctx.validate({
goods_id: {
type: 'int',
required: true,
desc: '商品id',
// defValue: 0,
range:{
min:1,
}
},
paraminfo: {
type: 'string',
required: true,
desc: '商品参数',
// defValue: 0,
range:{
max:5000,
}
},
});
// 参数
const goods_id = ctx.params.goods_id;
let data = await app.model.Goods.findOne({ where: { id:goods_id } });
if (!data) {
return ctx.apiFail('该商品不存在');
}
let { paraminfo } = this.ctx.request.body;
// console.log('paraminfo的信息',paraminfo);
//写入数据库
const res = await this.app.model.GoodsParam.create({
goods_id,
paraminfo
});
this.ctx.apiSuccess('添加商品参数成功');
}
# 2. 模版
app/view/admin/layout/_form.html
{# 定义一个可递归调用的子模板 #}
{% macro renderMenu(items,keyname,type) %}
{% for item in items %}
<span class="dropdown-item" style="margin-left: {{item.level * 20}}px;
cursor: pointer;background:none;"
onmouseover="this.style.color = '#000000';this.style.fontWeight = 800;"
onmouseout="this.style.color = '#002222';this.style.fontWeight = 'normal';"
@click="dropdownItemClick('{{keyname}}','{{item.name}}','{{item.id}}')">
{% if type == 'treeDataSelect' %}
<input type="checkbox" name="{{keyname}}" style="margin-right: 10px;"
data="{{item.id}}" class="checkbox-{{keyname}}"
@click.stop="treeDataSelectClick('{{keyname}}','{{item.name}}','{{item.id}}','checkbox-{{keyname}}',$event)">
{% endif %}
{{item.name}}
</span>
{# 检查是否存在子菜单项 #}
{% if item.children and item.children.length %}
<div class="dropdown-submenu">
{% call renderMenu(item.children,keyname,type) %}
{# 这里通过调用macro自身实现了递归 #}
{% endcall %}
</div>
{% endif %}
{% endfor %}
{% endmacro %}
<div class="card">
<div class="card-body">
<div class="mb-4">
<div class="bg-light">
<button class="btn btn-default btn-sm" onclick="history.back()"
title="点击返回上一页"><span class="fa fa-arrow-left"></span> 返回上一页</button>
</div>
</div>
{% if form %}
<form action="{{form.action}}" method="post">
{% for item in form.fields %}
<div class="form-group row">
<label class="col-form-label col-md-2"
style="display: {% if item.hidekeyData %}none{% else %}block{% endif %};">{{item.label}}</label>
<div class="col-md-10">
{# 如果是文件类型 #}
{% if item.type == 'file' %}
<input class="form-control" type="file" name="{{item.name}}"
@change="uploadFile($event,'{{item.name}}')">
<img :src="form.{{item.name}}" v-if="form.{{item.name}}"
class="mt-2 p-1 rounded border avatar-lg">
{# 获取字段值 #}
{# 可打印 {{form | dump}} #}
{% set fieldValue = form.data[item.name] | safe %}
{# 当字段值存在时显示图片 #}
{# {% if fieldValue and fieldValue.length > 0 %}
{# 通过 split 切割判断文件后缀 #}
{# {% set fileExtension = fieldValue.split('.').pop() | lower %} #}
{# 如果是 excel 文件则使用固定地址 #}
{# <img
src="{% if fileExtension == 'xlsx' %}/public/admin/assets/img/excel.png{% else %}{{ fieldValue }}{% endif %}"
class="mt-2 p-1 rounded border avatar-lg"> #}
{# {% endif %} #}
{# 如果是文件上传到oss云存储里面 #}
{% elif item.type == 'fileoss' %}
<input class="form-control" type="file" name="{{item.name}}"
@change="uploadFileoss($event,'{{item.name}}')">
<img :src="form.{{item.name}}" v-if="form.{{item.name}}"
class="mt-2 p-1 rounded border avatar-lg">
{# 如果是下拉框类型 #}
{% elif item.type == 'dropdown' %}
{# {{ item.ortherdata | safe }} #}
<div class="dropdown dropdown-maindiv" ortherdata="{{ item.ortherdata }}">
<button type="button"
class="btn dropdown-toggle dropdown-{{item.name}}"
data-toggle="dropdown" id="dropdownData">
{{item.placeholder if item.placeholder else '一级分类'}}
</button>
<div class="dropdown-menu" style="height: 400px;overflow: auto;">
<h5 class="dropdown-header">请选择要放在哪个分类下</h5>
<div class="dropdown-divider"></div>
{# 使用定义好的子模板递归渲染菜单 #}
<!-- 在上面的代码块之前或之外的合适位置,提前解析item.default为JSON对象 -->
{% set itemDefaultParsed = item.default | safe |
fromJson %}
{# {{item.callback | dump}} #}
{% call
renderMenu(itemDefaultParsed,item.name,item.type) %}
{% endcall %}
</div>
</div>
{% elif item.type == 'btncheck' %}
{# 如果是按钮组选择类型 #}
<div class="btn-group btn-group-{{item.name}}">
{% set itemDefaultParsed = item.default | safe |
fromJson %}
{% for btn in itemDefaultParsed %}
<button type="button"
class="btn {{'btn-success' if btn.checked else 'btn-light'}}"
@click="changeBtnStatus('{{item.name}}','btn-group-{{item.name}}','{{btn.value}}','{{ loop.index }}')">{{btn.name}}</button>
{% endfor %}
</div>
{# 如果是文本域类型 #}
{% elif item.type == 'textarea' %}
<textarea class="form-control" rows="5" name="{{item.name}}"
placeholder="{{item.placeholder}}..."
v-model="form.{{item.name}}"></textarea>
{# 如果是自定义类型组件 #}
{% elif item.type == 'diy' %}
{% if item.render %}
{{ item.render(item) | safe }}
{% endif %}
{# 如果是富文本编辑器 #}
{% elif item.type == 'editor' %}
{% include "admin/layout/_editor.html" %}
{# 如果循环遍历数组数据到表单 #}
{% elif item.type == 'for_form' %}
{# {{item.default}} #}
{% set forformParsed = item.default | safe | fromJson %}
{# {{ forformParsed | dump }} #}
{% if forformParsed.length == 0 %}
{% set ortherdataParsed = item.ortherdata | safe | fromJson %}
{# {{ ortherdataParsed | dump }} #}
{# 如果有html字段 #}
{% if ortherdataParsed.html %}
{{ ortherdataParsed.html | safe }}
{% endif %}
{% else %}
{% if item.id %}
<div class="param-groups">
{% for dataItem in forformParsed %}
<div class="param-group" data-id="{{ dataItem.id }}">
{# 参数{{ loop.index }} #}
<h5 class="mb-3">{{ dataItem.title }} 参数设置</h5>
{% set params = dataItem.data %}
<div class="row">
{% for param in params %}
{# param的值{{ param }} #}
<div class="col-lg-3 col-md-4 mb-3">
<div class="form-group">
<label data="{{ param.name | trim }}">{{ param.name | trim }}</label>
<input type="text" inputId="{{item.id}}"
class="form-control"
value="{{ param.value | trim }}"
placeholder="请输入{{ param.name | trim }}">
</div>
</div>
{% endfor %}
</div>
</div>
{% if not loop.last %}<hr>{% endif %}
{% endfor %}
</div>
{% else %}
<div class="param-groups">
{% for dataItem in forformParsed %}
<div class="param-group" data-id="{{ dataItem.id }}">
{# 参数{{ loop.index }} #}
<h5 class="mb-3">{{ dataItem.name }} 参数设置</h5>
{% set params = dataItem.default.split(',') %}
<div class="row">
{% for param in params %}
<div class="col-lg-3 col-md-4 mb-3">
<div class="form-group">
{# {{ param | trim }}--{{ dataItem.id }}-{{ loop.index0 }} #}
<label data="{{ param | trim }}">{{ param | trim }}</label>
<input type="text"
class="form-control"
placeholder="请输入{{ param | trim }}">
</div>
</div>
{% endfor %}
</div>
</div>
{% if not loop.last %}<hr>{% endif %}
{% endfor %}
</div>
{% endif %}
{% endif %}
{# 如果是树形结构数据选择 #}
{% elif item.type == 'treeDataSelect' %}
{# 使用定义好的子模板递归渲染菜单 #}
<!-- 在上面的代码块之前或之外的合适位置,提前解析item.default为JSON对象 -->
{% set itemDefaultParsed = item.default | safe | fromJson %}
{% call renderMenu(itemDefaultParsed,item.name,item.type) %}
{% endcall %}
{% else %}
<input
style="display: {% if item.hidekeyData %}none{% else %}block{% endif %};"
type="{{item.type}}" class="form-control"
name="{{item.name}}"
placeholder="{{item.placeholder}}..."
v-model="form.{{item.name}}">
{% endif %}
</div>
</div>
{% endfor %}
<div class="text-right mt-3">
<button type="submit" class="btn btn-primary"
@click.stop.prevent="submit">提 交</button>
</div>
</form>
{% endif %}
</div>
</div>
<script>
Vueapp = new Vue({
el:'#vueapp',
data(){
return {
form:{
{% for item in form.fields %}
{{item.name}} : `{{ (form.data[item.name]|safe) if form.data[item.name] else item.default }}`,
{% endfor %}
},
uploadedFiles: {}, //存储每个item.name对应的已上传文件路径
}
},
mounted(){
console.log('form',this.form);
console.log('btn',typeof this.form.status);
$('.form-group input').on('input',function(){
// console.log($(this).val());
let val = $(this).val();
$(this).attr('datavalue',val);
});
},
methods:{
getparaminfo(){
let arr = [];
$('.param-groups .param-group').each(function(index,element){
let arr_eve = {
title: $(element).find('h5').text(), // 获取标题
data:[], // 获取参数
};
let paramGroupId = $(element).attr('data-id');
let formGroups = $(element).find('.row').find('.form-group');
formGroups.each(function(index1,element1){
arr_eve.data.push({
name: $(element1).find('label').attr('data'),
value: $(element1).find('input').attr('datavalue') || $(element1).find('input').val(),
});
});
arr.push(arr_eve);
});
return arr;
},
submit(){
// console.log('提交成功',this.form);
// console.log(JSON.parse(this.form.pid.replaceAll('"','"')));
// console.log('下拉框第一项', JSON.parse(this.form.pid.replaceAll('"','"'))[0]);
// console.log('按钮组第一项', JSON.parse(this.form.status.replaceAll('"','"'))[0]);
for(const key in this.form){
const value = this.form[key];
console.log('value:',value);
if(typeof value == 'string' && value.length > 0 && value.indexOf('"')>-1){
if(key == 'paraminfo'){
this.form[key] = JSON.stringify(this.getparaminfo());
}else{
this.form[key] = JSON.parse(value.replaceAll('"','"'))[0].value;
}
}else{
// goods_param表 paraminfo修改的时候单独处理
if(key == 'paraminfo'){
// 针对 for-form 类型 写入paraminfo数据
console.log('for-form 类型的paraminfo',this.getparaminfo());
// 针对dropdown类型,写入paraminfo数据
console.log('dropdown 类型的paraminfo',this.form[key]);
if(this.getparaminfo().length){
this.form[key] = JSON.stringify(this.getparaminfo());
}
}
}
}
//提交之前,针对商品多参数的处理,如 paraminfo的处理
// console.log(this.getparaminfo());;
// console.log('提交成功1',this.form);
// return;
$.ajax({
type: 'POST',
url: "{{form.action}}?_csrf={{ctx.csrf|safe}}",
// contentType:'application/x-www-form-urlencoded',
// data: {
// username:123,
// sex:'女'
// },
contentType:'application/json;charset=UTF-8;',
data:JSON.stringify(this.form),
success: function (response, stutas, xhr) {
console.log(response)
Vueapp.$refs.toast.show({
// msg:response.data || "{{'修改' if id else '新建' }}成功",
msg:"{{'修改' if id else '新建' }}成功",
type:'success',
delay:1000,
success:function(){
// 查询当前网址是否有gourl参数
let gourl = window.location.search.match(/gourl=([^&]*)/);
gourl = decodeURIComponent(gourl).split(',');
if(gourl && gourl[1]){
// 跳转到gourl对应的页面
window.location.href = gourl[1];
} else{
// 跳转到某个页面
window.location.href = "{{successUrl}}";
}
}
});
},
error:function(e){
console.log(e)
Vueapp.$refs.toast.show({
msg:e.responseJSON.data,
type:'danger',
delay:3000
});
}
});
},
//如果是文件上传到本地服务器
uploadFile(e,name,callback=null){
// console.log('e:', e);
// console.log('name:',name);
let file = e.target.files[0];
// console.log(file);
let formData = new FormData();
formData.append('file', file);
$.ajax({
type: 'POST',
url: "/uploadStreamSingleToServerDiy/adminImg?_csrf={{ctx.csrf|safe}}",
processData: false, // 告诉jQuery不要去处理发送的数据
data: formData,
contentType: false, // 告诉jQuery不要去设置Content-Type请求头
success: (response, stutas, xhr)=> {
console.log(response)
this.form[name] = response.data.url;
Vueapp.$refs.toast.show({
msg:"上传图片成功",
type:'success',
delay:1000,
success:function(){}
});
// 使用$set将上传成功的文件URL添加到uploadedFiles对象
this.$set(this.uploadedFiles,name,response.data.url);
if(callback && typeof callback === 'function'){
callback({
event: e,
name:name,
data:response.data
});
}
},
error:function(e){
console.log(e)
Vueapp.$refs.toast.show({
msg:e.responseJSON.data,
type:'danger',
delay:3000
});
}
});
},
//如果是文件上传到oss云存储里面
uploadFileoss(e,name,callback=null){
// console.log('e:', e);
// console.log('name:',name);
let file = e.target.files[0];
// console.log(file);
let formData = new FormData();
formData.append('img', file);
$.ajax({
type: 'POST',
url: "/shop/admin/image/uploadAliyun?_csrf={{ctx.csrf|safe}}",
processData: false, // 告诉jQuery不要去处理发送的数据
data: formData,
contentType: false, // 告诉jQuery不要去设置Content-Type请求头
success: (response, stutas, xhr)=> {
console.log(response)
this.form[name] = response.data[0].url;
Vueapp.$refs.toast.show({
msg:"上传图片成功",
type:'success',
delay:1000,
success:function(){}
});
// 使用$set将上传成功的文件URL添加到uploadedFiles对象
this.$set(this.uploadedFiles,name,response.data.url);
if(callback && typeof callback === 'function'){
callback({
event: e,
name:name,
data:response.data
});
}
},
error:function(e){
console.log(e)
Vueapp.$refs.toast.show({
msg:e.responseJSON.data,
type:'danger',
delay:3000
});
}
});
},
//下拉菜单选中项
dropdownItemClick(keyname,name,id){
console.log('keyname', keyname);
console.log('name', name);
console.log('id', id);
// $('#dropdownData').text(name);
$('.dropdown-' + keyname).text(name);
this.form[keyname] = id;
let _vue_this = this;
// 如果下拉框有额外值的情况,比如:app/controller/admin/goods.js中的selectGoodsParamBytree()方法
if($('.dropdown-maindiv').attr('ortherdata')){
$('.dropdown-maindiv').parent().find('.ortherdata').remove();
//如果有额外值,根据情况读取这些额外值
let ortherdata = JSON.parse($('.dropdown-maindiv').attr('ortherdata'));
console.log('额外值',ortherdata);
let skus = ortherdata.find(v=> v.id == id);
skus = skus.skus;
console.log('skus',skus);
let str = ``;
for(let i=0;i<skus.length;i++){
// let evedata = [];
// evedata.push(encodeURIComponent(JSON.stringify([{
// title: skus[i].name,
// data: skus[i].default,
// }])));
let evedata = encodeURIComponent(JSON.stringify([{
title: skus[i].name,
data: skus[i].default,
id:skus[i].id,
}]));
let _str = `<div class="evedata"
evedata = "${evedata}"
style="margin-top:20px;font-size:12px;display:flex;">`;
_str += `
<div style="margin-right:10px;">
<input type="checkbox" checked class="evedata-checkbox" data-id="${skus[i].id}" />
</div>
<div>
<div style="margin-bottom:10px;"><span>规格名称</span>:<span>${skus[i].name}</span></div>
<div><span>规格值</span>:<span>${skus[i].default}</span></div>
</div>
`;
_str += `</div>`;
str += _str;
}
str = `<div class="ortherdata">` + str + `</div>`;
$('.dropdown-maindiv').parent().append(str);
//重新给表单字段赋值
getcheck();
$('.evedata-checkbox').on('click',getcheck);
function getcheck(){
let arr = [];
$('.dropdown-maindiv').parent().find('.ortherdata').find('.evedata').each(function(index,ele){
let input = $(ele).find('input[type="checkbox"]');
//如果input为选中状态
// console.log('input的个数'+index+'的选中状态',input.prop('checked'));
if(input.prop('checked')){
let evedata = $(ele).attr('evedata');
evedata = JSON.parse(decodeURIComponent(evedata));
// console.log('evedata',evedata[0]);
arr.push(evedata[0]);
// console.log('写进paraminfo的数据',arr);
}else{
let removeid = input.attr('data-id');
arr.forEach(function(v,i){
if(v.id == removeid){
arr.splice(i,1);
}
});
// console.log('移除之后写进paraminfo的数据',arr);
}
//最终写进paraminfo的数据
console.log('最终写进paraminfo的数据',arr);
// 需要符合paraminfo的数据特性,在改造一下
let newarr = [];
for(let i=0;i<arr.length;i++){
let _string = arr[i].data;
// console.log('看一下data',typeof arr[i].data);
let _data = _string.split(/[,,]/);
// console.log('看一下_data',_data);
let _arr = [];
for(let j=0;j<_data.length;j++){
let obj = {
name:_data[j],
value:"",
};
_arr.push(obj);
}
// console.log('看一下_arr',_arr);
// console.log('看一下其中的data',arr[i]['data']);
// arr[i]['data'] = _arr;
let _obj = {
title:arr[i].title,
data:_arr,
id:arr[i].id,
};
newarr.push(_obj);
}
console.log('看一下写入值',newarr);
_vue_this.form[keyname] = JSON.stringify(newarr);
});
}
}
},
//按钮组选中项
changeBtnStatus(keyname,classname,btnValue,index){
console.log(keyname,classname,btnValue,index);
$('.' + classname).find('button').eq(index-1).addClass('btn-success')
.siblings().removeClass('btn-success').addClass('btn-light');
this.form[keyname] = btnValue;
},
//自定义的按钮组点击效果
diyBtnGroup(clickbtn_parentClassname,classname,index){
$('.' + classname).children().eq(index).show().siblings().hide();
$('.' + clickbtn_parentClassname).children().eq(index)
.addClass('btn-success').siblings().removeClass('btn-success').addClass('btn-light');
},
treeDataSelectClick(keyname,name,id,classname,e){
// console.log('keyname', keyname);
// console.log('name', name);
// console.log('id', id);
// console.log('classname', classname);
// console.log('事件对象', e);
// 选中上一级下一级所有都选中
// console.log('父级class', $(e.target).parent().parent().attr('class'));
if($(e.target).parent().parent().attr('class') != 'dropdown-submenu'){
let checkbox = $(e.target).parent().next().find('input[type="checkbox"]');
if($(e.target).is(':checked')){
checkbox.prop('checked',true).attr('select',1);
}else{
checkbox.prop('checked',false).attr('select',0);
}
//设置自己
if($(e.target).is(':checked')){
$(e.target).attr('select',1);
}else{
$(e.target).attr('select',0);
}
}else{
//每个级别下的所有多选框数量
let all = $(e.target).parent().parent().find('input[type="checkbox"]').length;
//当前这个级别下面选了多少个框了
let len = $(e.target).parent().parent().find('input[type="checkbox"]:checked').length;
// console.log('当前级别总共多少框',all);
// console.log('当前级别选了多少框',len);
if(all == len){
$(e.target).parent().parent().prev().find('input[type="checkbox"]')
.prop('checked',true).attr('select',1);
}else{
$(e.target).parent().parent().prev().find('input[type="checkbox"]')
.prop('checked',false).attr('select',1);
}
//设置自己
if($(e.target).is(':checked')){
$(e.target).attr('select',1);
}else{
$(e.target).attr('select',0);
}
//一个都没有选
if(len == 0){
$(e.target).parent().parent().prev().find('input[type="checkbox"]')
.prop('checked',false).attr('select',0);
}
}
//选取选中的值
// console.log('选取框的长度', $(`input[name=${keyname}]:checked`).length);
let arr = [];
// $(`input[name=${keyname}]:checked`).each(function(index,element){
// console.log($(element).attr('data'));
// arr.push(parseInt($(element).attr('data')));
// });
$(`input[name=${keyname}]`).each(function(index,element){
if($(element).attr('select') == 1){
arr.push(parseInt($(element).attr('data')));
}
});
// console.log('获取权限的id值集合数据',arr);
console.log('写进数据库数据',arr.join(','));
this.form[keyname] = arr.join(',');
}
}
});
</script>
# 三、商品参数信息列表及删除
# 1. 控制器
app/controller/admin/goods.js
//商品参数列表
async indexGoodsParam(){
const { ctx, app } = this;
//1.参数验证
this.ctx.validate({
goods_id: {
type: 'int',
required: true,
desc: '商品id',
// defValue: 0,
range:{
min:1,
}
},
});
// 参数
const goods_id = ctx.params.goods_id;
let Goodsdata = await app.model.Goods.findOne({ where: { id:goods_id } });
if (!Goodsdata) {
return ctx.apiFail('该商品不存在');
}
let GoodsClassdata = await app.model.GoodsClass.findOne({
where: {
id:Goodsdata.goods_class_id
},
include: [
{ model: app.model.Skus},
]
});
// ctx.body = GoodsClassdata;return;
//分页:可以提炼成一个公共方法page(模型名称,where条件,其他参数options)
let data = await ctx.page('GoodsParam');
// let data = await ctx.service.goodsClass.datalist({ limit: 10000 });
// console.log('分类数据', data);
// ctx.body = data;
// return;
// data = data.rules;
//渲染公共模版
await ctx.renderTemplate({
title: '商品:' + Goodsdata.name + '参数列表',//现在网页title,面包屑导航title,页面标题
data,
tempType: 'table', //模板类型:table表格模板 ,form表单模板
table: {
//表格上方按钮,没有不要填buttons
buttons: [
{
url: '/shop/admin/goods-/'+goods_id+'/createGoodsParam',//新增路径
desc: '创建商品参数',//新增 //按钮名称
// icon: 'fa fa-plus fa-lg',//按钮图标
},
// {
// url: '/shop/admin/goods/create',//新增路径
// desc: '上传商品',//新增 //按钮名称
// // icon: 'fa fa-plus fa-lg',//按钮图标
// }
],
//表头
columns: [
{
title: '商品参数',
// key: 'paraminfo',
class: 'text-left',//可选
render(item) { //树形数据
// console.log('每个item',item);
// if (item.level) {
// let w = item.level * 40;
// return `<span style="display:inline-block;width:${w}px"></span>`;
// }
let str = ``;
let data = JSON.parse(item.paraminfo);
for (let i = 0; i < data.length; i++) {
let _str = `<p style="width:100%;margin-top:15px;">${data[i].title}</p>`;
let valuearr = data[i].data;
let _str_ = ``;
for(let j=0;j<valuearr.length;j++){
_str_ += `
<p style="font-size:12px;border:1px solid #ccc;margin:5px;padding:5px;">
<span >${valuearr[j].name}</span>:
<span style="color:#f00">${valuearr[j].value}</span>
</p>
`;
}
str += `<div style="display:flex;flex-wrap:wrap;">${_str}${_str_}</div>`;
}
return `<div>${str}</div>`;
}
},
// {
// title: '分类下的商品',
// // key: 'name',
// class: 'text-left',//可选
// render(item) { //树形数据
// // console.log('每个item',item);
// // if (item.level) {
// // let w = item.level * 40;
// // return `<span style="display:inline-block;width:${w}px"></span>`;
// // }
// return `<a href="/shop/admin/imageclass/${item.id}/imgList">${item.images.length}张</a>`;
// }
// },
// {
// title: '是否是导航栏栏目',
// key: 'isnav',
// width: 200,//可选
// class: 'text-center',//可选
// hidekeyData: true,//是否隐藏key对应的数据
// render(item) {
// console.log('可用状态里面每个item', item);
// let arr = [
// { value: 1, name: '是' },
// { value: 0, name: '否' },
// ];
// let str = `<div class="btn-group btn-group-${item.id}">`;
// for (let i = 0; i < arr.length; i++) {
// str += `<button type="button" class="btn btn-light" data="${item.isnav}"
// value="${arr[i].value}"
// @click="changeBtnStatus('isnav','btn-group-${item.id}',${arr[i].value},${i},${item.id},'category','Category')">${arr[i].name}</button>`;
// }
// str += `</div>`;
// return str;
// }
// },
{
title: '排序',
key: 'order',
class: 'text-center',//可选
},
{
title: '可用状态',
key: 'status',
width: 200,//可选
class: 'text-center',//可选
hidekeyData: true,//是否隐藏key对应的数据
render(item) {
// console.log('可用状态里面每个item', item);
let arr = [
{ value: 1, name: '可用' },
{ value: 0, name: '不可用' },
];
let str = `<div class="btn-group btn-group-${item.id}">`;
for (let i = 0; i < arr.length; i++) {
str += `<button type="button" class="btn btn-light" data="${item.status}"
value="${arr[i].value}"
@click="changeBtnStatus('status','btn-group-${item.id}',${arr[i].value},${i},${item.id},'/shop/admin/goods-/${goods_id}/indexGoodsParam','GoodsParam')">${arr[i].name}</button>`;
}
str += `</div>`;
return str;
}
},
{
title: '操作',
class: 'text-right',//可选
action: {
//修改
edit: function (id) {
return `/shop/admin/goods-/${id}/editGoodsParam`;
},
//删除
delete: function (id) {
return `/shop/admin/goods-/${id}/deleteGoodsParam`;
}
}
},
],
},
});
}
//删除商品参数
async deleteGoodsParam(){
const { ctx, app } = this;
const id = ctx.params.id;
let data = await app.model.GoodsParam.findOne({ where: { id } });
if (!data) {
return ctx.apiFail('该商品参数不存在');
}
await app.model.GoodsParam.destroy({
where: {
id
}
});
//提示
ctx.toast('商品参数删除成功', 'success');
//跳转
ctx.redirect('/shop/admin/goods-/'+data.goods_id+'/indexGoodsParam');
}
# 2. 商品分类的模型关联
app/model/goods_class.js
...
// 模型关联关系
GoodsClass.associate = function (models) {
// 关联到自己 (通过pid关联)
this.hasMany(app.model.GoodsClass, {
foreignKey: 'pid',
as: 'ChildGoodsClass' // 别名(可选)
});
// 关联商品
this.hasMany(app.model.Goods, {
foreignKey: 'goods_class_id',
// as: 'images' // 别名(可选)
});
// 关联skus
this.hasMany(app.model.Skus, {
foreignKey: 'goods_class_id',
// as: 'images' // 别名(可选)
});
}
...
# 四、修改商品参数信息
# 1. 控制器
app/controller/admin/goods.js
//修改商品参数界面
async editGoodsParam(){
const { ctx, app } = this;
const id = ctx.params.id;
let currentdata = await app.model.GoodsParam.findOne({
where: {
id,
// status:1
}
});
if (!currentdata) {
return ctx.apiFail('该商品参数信息不存在');
}
currentdata = JSON.parse(JSON.stringify(currentdata));
// console.log('当前商品分类数据', currentdata);
// return;
let Goodsdata = await app.model.Goods.findOne({
where: {
id: currentdata.goods_id,
// status:1
}
});
// 渲染模版前先拿到所有分类
// let data = await ctx.service.goodsClass.dropdown_goodsclass_list();
// console.log('下拉框显示的所有分类', JSON.stringify(data));
// return;
//渲染公共模版
await ctx.renderTemplate({
id,
title: '修改商品:'+Goodsdata.name+'的参数',//现在网页title,面包屑导航title,页面标题
tempType: 'form', //模板类型:table表格模板 ,form表单模板
form: {
//修改提交地址
action: '/shop/admin/goods-/' + id + '/updateGoodsParam',
// 字段
fields: [
// {
// label: '放在哪个商品分类里面',
// type: 'dropdown', //下拉框
// name: 'goods_class_id',
// default: JSON.stringify(data),
// placeholder: '不调整(如需调整请选择)',
// },
{
label: '商品参数信息',
type: 'for_form',
name: 'paraminfo',
placeholder: '',
default:currentdata.paraminfo,
id: id, //加一个id,代表修改,没有代表新增
},
{
label: '排序',
type: 'number',
name: 'order',
placeholder: '请输入排序',
// default:50,
},
{
label: '可用状态',
type: 'btncheck', //按钮组选择
name: 'status',
default: JSON.stringify([
{ value: 1, name: '可用', checked: currentdata.status === 1 },
{ value: 0, name: '不可用', checked: currentdata.status === 0 },
]),
placeholder: '状态 0不可用 1可用 等等状态',
},
],
//修改内容默认值
data:currentdata,
},
//修改成功之后跳转到哪个页面
successUrl: '/shop/admin/goods-/'+currentdata.goods_id+'/indexGoodsParam',
});
}
//修改商品参数数据
async updateGoodsParam(){
const { ctx, app } = this;
//1.参数验证
this.ctx.validate({
id: {
type: 'int',
required: true,
desc: '商品参数id',
// defValue: 0,
range:{
min:1,
}
},
paraminfo: {
type: 'string',
required: true,
desc: '商品参数信息',
// defValue: 0,
range:{
max:5000
}
},
status: {
type: 'int',
required: false,
defValue: 1,
desc: '状态 0不可用 1可用 等等状态',
range:{
in:[0,1]
}
},
order: {
type: 'int',
required: false,
defValue: 50,
desc: '排序'
},
});
// 参数
const id = ctx.params.id;
// 先看一下是否存在
let data = await app.model.GoodsParam.findOne({ where: { id } });
if (!data) {
return ctx.apiFail('该商品参数不存在');
}
const { status, order, paraminfo } = ctx.request.body;
let goods_id = data.goods_id;
// 修改数据
data.paraminfo = paraminfo;
data.status = status;
data.goods_id = goods_id;
data.order = order;
await data.save();
// 给一个反馈
ctx.apiSuccess('修改商品参数成功');
}
# 五、商品的参数没有对应的skus信息,先添加skus信息
# 1. 控制器
app/controller/admin/goods.js
//新增商品参数界面
async createGoodsParam(){
const { ctx, app } = this;
//1.参数验证
this.ctx.validate({
goods_id: {
type: 'int',
required: true,
desc: '商品id',
// defValue: 0,
range:{
min:1,
}
},
});
// 参数
const goods_id = ctx.params.goods_id;
let data = await app.model.Goods.findOne({ where: { id:goods_id } });
if (!data) {
return ctx.apiFail('该商品不存在');
}
// 根据商品id获取商品分类,然后获取分类里面的skus值
let goodsClassId = data.goods_class_id;
// ctx.body = goodsClassId; return;
// 根据商品分类id读取商品分类及其子分类,并转成树形结构
let goodsClassTree = await this.datatree_byid(goodsClassId, 'GoodsClass');
// ctx.body = goodsClassTree; return;
/*
//渲染公共模版
await ctx.renderTemplate({
title: '创建商品参数',//现在网页title,面包屑导航title,页面标题
tempType: 'form', //模板类型:table表格模板 ,form表单模板
form: {
//提交地址
action: "",
// 字段
fields: [
{
label: '商品分类',
type: 'dropdown', //下拉框
name: 'pid',
default: JSON.stringify(goodsClassTree),
placeholder: '请选择一个商品分类',
},
],
},
//新增成功之后跳转到哪个页面
successUrl: '',
});
*/
//看一下当前商品分类在商品skus里面有没有创建的信息
let skusdata = await app.model.Skus.findAll({
where:{
goods_class_id:goodsClassId
}
});
// ctx.body = skusdata; return;
// 获取当前页面网址
let gourl = encodeURIComponent(ctx.url);
if(skusdata.length){
// ctx.body = '有对应的skus信息,可继续添加skus信息'
await ctx.renderTemplate({
title: '创建商品参数',//现在网页title,面包屑导航title,页面标题
tempType: 'form', //模板类型:table表格模板 ,form表单模板,for_form循环模版
form: {
//提交地址
action: "/shop/admin/goods-/"+goods_id+"/saveGoodsParam",
// 字段
fields: [
// {
// label: '商品分类',
// type: 'dropdown', //下拉框
// name: 'pid',
// default: JSON.stringify(goodsClassTree),
// placeholder: '请选择一个商品分类',
// },
{
label: '商品参数',
type: 'for_form', //循环遍历数组数据到表单
name: 'paraminfo',
default: JSON.stringify(skusdata),
placeholder: '',
},
{
label: '排序',
type: 'number',
name: 'order',
placeholder: '请输入排序',
default:50,
},
{
label: '可用状态',
type: 'btncheck', //按钮组选择
name: 'status',
default: JSON.stringify([
{ value: 1, name: '可用', checked: true },
{ value: 0, name: '不可用', },
]),
placeholder: '状态 0不可用 1可用 等等状态',
},
],
},
//新增成功之后跳转到哪个页面
successUrl: '/shop/admin/goods-/'+goods_id+'/indexGoodsParam',
});
}else{
// ctx.body = '没有对应的skus信息,先添加skus信息'
await ctx.renderTemplate({
title: '创建商品参数',//现在网页title,面包屑导航title,页面标题
tempType: 'form', //模板类型:table表格模板 ,form表单模板,for_form循环模版
form: {
//提交地址
action: "/shop/admin/goods-/"+goods_id+"/saveGoodsParam",
// 字段
fields: [
// {
// label: '商品分类',
// type: 'dropdown', //下拉框
// name: 'pid',
// default: JSON.stringify(goodsClassTree),
// placeholder: '请选择一个商品分类',
// },
{
label: '商品参数',
type: 'for_form', //循环遍历数组数据到表单
name: 'paraminfo',
default: JSON.stringify(skusdata),//没有值的情况
placeholder: '',
//自定义一些数据
ortherdata:JSON.stringify({
html:`
<a href="/shop/admin/skus/create?gourl=${gourl}&goods_class_id=${goodsClassId}">
请先创建规格数据,再来创建商品参数
</a>
`,
}),
},
{
label: '排序',
type: 'number',
name: 'order',
placeholder: '请输入排序',
default:50,
},
{
label: '可用状态',
type: 'btncheck', //按钮组选择
name: 'status',
default: JSON.stringify([
{ value: 1, name: '可用', checked: true },
{ value: 0, name: '不可用', },
]),
placeholder: '状态 0不可用 1可用 等等状态',
},
],
},
//新增成功之后跳转到哪个页面
successUrl: '/shop/admin/goods-/'+goods_id+'/indexGoodsParam',
});
}
}
//新增商品参数数据
async saveGoodsParam(){
const { ctx, app } = this;
//1.参数验证
this.ctx.validate({
goods_id: {
type: 'int',
required: true,
desc: '商品id',
// defValue: 0,
range:{
min:1,
}
},
paraminfo: {
type: 'string',
required: true,
desc: '商品参数信息',
// defValue: 0,
range:{
max:5000
}
},
status: {
type: 'int',
required: false,
defValue: 1,
desc: '状态 0不可用 1可用 等等状态',
range:{
in:[0,1]
}
},
order: {
type: 'int',
required: false,
defValue: 50,
desc: '排序'
},
});
// 参数
const goods_id = ctx.params.goods_id;
let data = await app.model.Goods.findOne({ where: { id:goods_id } });
if (!data) {
return ctx.apiFail('该商品不存在');
}
let {paraminfo,status,order} = ctx.request.body;
if(paraminfo == '[]'){
return ctx.apiFail('请先创建规格数据,再来创建商品参数');
}
const res = await app.model.GoodsParam.create({
paraminfo,status,order,
goods_id
});
this.ctx.apiSuccess('添加商品参数成功');
}
# 2. skus的控制器
app/controller/admin/skus.js
// 创建商品规格界面
async create() {
const { ctx, app } = this;
// 渲染模版前先拿到所有分类
let data = await ctx.service.goodsClass.dropdown_goodsclass_list();
data.shift();
// console.log('分类数据', data);
if(data.length == 0){
//提示
ctx.toast('请先创建商品分类,在创建商品规格', 'danger');
//跳转
return ctx.redirect('/shop/admin/goodsclass/create');
}
// ctx.body = data;return;
//看一下网址上是否有商品分类id goods_class_id
let _fields = [
{
label: '放在哪个商品分类里面',
type: 'dropdown', //下拉框
name: 'goods_class_id',
default: JSON.stringify(data),
placeholder: '请选择一个商品分类',
}
];
// ctx.body = _fields;return;
if(ctx.query.goods_class_id){
let goodsClass = await app.model.GoodsClass.findByPk(parseInt(ctx.query.goods_class_id));
if(goodsClass){
data = [];
data.push(goodsClass);
_fields[0].default = goodsClass.id;
_fields[0].placeholder = '';
_fields[0].type = 'number';
_fields[0].hidekeyData = true; //隐藏数据
}
// ctx.body = _fields;return;
}
//渲染公共模版
await ctx.renderTemplate({
title: '创建商品规格',//现在网页title,面包屑导航title,页面标题
tempType: 'form', //模板类型:table表格模板 ,form表单模板
form: {
//提交地址
action: "/shop/admin/skus",
// 字段
fields: [
..._fields,
{
label: '商品规格名称',
type: 'text',
name: 'name',
placeholder: '请输入商品规格名称',
// default:'默认值测试', //新增时候默认值,可选
},
{
label: '商品规格值',
type: 'textarea',
name: 'default',
placeholder: '请输入商品规格值,多个值用逗号隔开',
},
{
label: '规格类型',
type: 'number',
name: 'type',
placeholder: '请输入商品规格类型:0无限制,1颜色,2图片,3尺寸等等,选填,默认0',
default:0,
},
{
label: '排序',
type: 'number',
name: 'order',
placeholder: '请输入排序',
default:50,
},
{
label: '可用状态',
type: 'btncheck', //按钮组选择
name: 'status',
default: JSON.stringify([
{ value: 1, name: '可用', checked: true },
{ value: 0, name: '不可用' },
]),
placeholder: '状态 0不可用 1可用 等等状态',
},
],
},
//新增成功之后跳转到哪个页面
successUrl: '/shop/admin/skus',
});
}
# 六、商品多条参数信息的创建
# 1. 控制器
app/controller/admin/goods.js
//商品参数部分
//新增商品参数界面
async createGoodsParam(){
const { ctx, app } = this;
//1.参数验证
this.ctx.validate({
goods_id: {
type: 'int',
required: true,
desc: '商品id',
// defValue: 0,
range:{
min:1,
}
},
});
// 参数
const goods_id = ctx.params.goods_id;
let data = await app.model.Goods.findOne({ where: { id:goods_id } });
if (!data) {
return ctx.apiFail('该商品不存在');
}
// 根据商品id获取商品分类,然后获取分类里面的skus值
let goodsClassId = data.goods_class_id;
// ctx.body = goodsClassId; return;
// 根据商品分类id读取商品分类及其子分类,并转成树形结构
let goodsClassTree = await this.datatree_byid(goodsClassId, 'GoodsClass');
// ctx.body = goodsClassTree; return;
/*
//渲染公共模版
await ctx.renderTemplate({
title: '创建商品参数',//现在网页title,面包屑导航title,页面标题
tempType: 'form', //模板类型:table表格模板 ,form表单模板
form: {
//提交地址
action: "",
// 字段
fields: [
{
label: '商品分类',
type: 'dropdown', //下拉框
name: 'pid',
default: JSON.stringify(goodsClassTree),
placeholder: '请选择一个商品分类',
},
],
},
//新增成功之后跳转到哪个页面
successUrl: '',
});
*/
//看一下当前商品分类在商品skus里面有没有创建的信息
let skusdata = await app.model.Skus.findAll({
where:{
goods_class_id:goodsClassId
}
});
// ctx.body = skusdata; return;
//获取当前页面的网站
let gourl = encodeURIComponent(ctx.url);
if(skusdata.length){
// ctx.body = '有对应的skus信息,可继续添加skus信息'
await ctx.renderTemplate({
title: '创建商品参数',//现在网页title,面包屑导航title,页面标题
tempType: 'form', //模板类型:table表格模板 ,form表单模板,for_form循环模版
form: {
//提交地址
action: "/shop/admin/goods-/"+goods_id+"/saveGoodsParam",
// 字段
fields: [
// {
// label: '商品分类',
// type: 'dropdown', //下拉框
// name: 'pid',
// default: JSON.stringify(goodsClassTree),
// placeholder: '请选择一个商品分类',
// },
{
label: '商品参数',
type: 'for_form', //循环遍历数组数据到表单
name: 'paraminfo',
default: JSON.stringify(skusdata),
placeholder: '',
},
{
label: '排序',
type: 'number',
name: 'order',
placeholder: '请输入排序',
default:50,
},
{
label: '可用状态',
type: 'btncheck', //按钮组选择
name: 'status',
default: JSON.stringify([
{ value: 1, name: '可用', checked: true },
{ value: 0, name: '不可用', },
]),
placeholder: '状态 0不可用 1可用 等等状态',
},
],
},
//新增成功之后跳转到哪个页面
successUrl: '/shop/admin/goods-/'+goods_id+'/indexGoodsParam',
});
}else{
// ctx.body = '没有对应的skus信息,先添加skus信息'
await ctx.renderTemplate({
title: '创建商品参数',//现在网页title,面包屑导航title,页面标题
tempType: 'form', //模板类型:table表格模板 ,form表单模板,for_form循环模版
form: {
//提交地址
action: "/shop/admin/goods-/"+goods_id+"/saveGoodsParam",
// 字段
fields: [
// {
// label: '商品分类',
// type: 'dropdown', //下拉框
// name: 'pid',
// default: JSON.stringify(goodsClassTree),
// placeholder: '请选择一个商品分类',
// },
{
label: '商品参数',
type: 'for_form', //循环遍历数组数据到表单
name: 'paraminfo',
default: JSON.stringify(skusdata),//没有值的情况
placeholder: '',
// 自定义一些数据
ortherdata:JSON.stringify({
html:`
<a href="/shop/admin/skus/create?gourl=${gourl}&goods_class_id=${goodsClassId}">
请先创建规格数据,再来创建商品参数
</a>
`,
}),
},
{
label: '排序',
type: 'number',
name: 'order',
placeholder: '请输入排序',
default:50,
},
{
label: '可用状态',
type: 'btncheck', //按钮组选择
name: 'status',
default: JSON.stringify([
{ value: 1, name: '可用', checked: true },
{ value: 0, name: '不可用', },
]),
placeholder: '状态 0不可用 1可用 等等状态',
},
],
},
//新增成功之后跳转到哪个页面
successUrl: '/shop/admin/goods-/'+goods_id+'/indexGoodsParam',
});
}
}
//新增商品参数数据
async saveGoodsParam(){
const { ctx, app } = this;
//1.参数验证
this.ctx.validate({
goods_id: {
type: 'int',
required: true,
desc: '商品id',
// defValue: 0,
range:{
min:1,
}
},
paraminfo: {
type: 'string',
required: true,
desc: '商品参数信息',
// defValue: 0,
range:{
max:5000
}
},
status: {
type: 'int',
required: false,
defValue: 1,
desc: '状态 0不可用 1可用 等等状态',
range:{
in:[0,1]
}
},
order: {
type: 'int',
required: false,
defValue: 50,
desc: '排序'
},
});
// 参数
const goods_id = ctx.params.goods_id;
let data = await app.model.Goods.findOne({ where: { id:goods_id } });
if (!data) {
return ctx.apiFail('该商品不存在');
}
let {paraminfo,status,order} = ctx.request.body;
const res = await app.model.GoodsParam.create({
paraminfo,status,order,
goods_id
});
this.ctx.apiSuccess('添加商品参数成功');
}
//选择一个商品分类下的skus添加到商品参数中
async selectGoodsParamBytree(){
const { ctx, app } = this;
//1.参数验证
this.ctx.validate({
goods_id: {
type: 'int',
required: true,
desc: '商品id',
// defValue: 0,
range:{
min:1,
}
},
});
// 参数
const goods_id = ctx.params.goods_id;
let data = await app.model.Goods.findOne({ where: { id:goods_id } });
if (!data) {
return ctx.apiFail('该商品不存在');
}
// 选择一个商品分类(读取有skus信息的商品分类)
let getskus = await app.model.Skus.findAll({
attributes:['goods_class_id'],
});
// ctx.body = getskus; return;
let goodsclass_ids = [];
// 如果goodsclass_ids没有item.goods_class_id的值,就添加进去
getskus.forEach(item=>{
if(goodsclass_ids.indexOf(item.goods_class_id) == -1){
goodsclass_ids.push(item.goods_class_id);
}
});
// ctx.body = goodsclass_ids; return;
// 根据goodsclass_ids查商品分类信息
let goodsClassTree = await app.model.GoodsClass.findAll({
where:{
id:{
[this.app.Sequelize.Op.in]:goodsclass_ids
}
},
attributes:['id','name','pid'],
include: [
{
model: app.model.Skus,
attributes:['id','name','goods_class_id','default'],
// as: 'children'
},
],
});
goodsClassTree = JSON.parse(JSON.stringify(goodsClassTree));
// ctx.body = goodsClassTree; return;
// ctx.body = ctx.treeify(goodsClassTree); return;
let treedata = ctx.treeify(goodsClassTree);
await ctx.renderTemplate({
title: '创建商品多条参数前,请先选择一个商品分类,将该分类下的规格数据添加到商品参数中',//现在网页title,面包屑导航title,页面标题
tempType: 'form', //模板类型:table表格模板 ,form表单模板,for_form循环模版
form: {
//提交地址
action: "/shop/admin/goods-/"+goods_id+"/saveGoodsParam",
// 字段
fields: [
{
label: '请选择一个商品分类得到分类下的规格数据',
type: 'dropdown', //下拉框
name: 'paraminfo',
default: JSON.stringify(treedata),
placeholder: '请选择一个商品分类',
//其他数据
ortherdata:JSON.stringify(goodsClassTree),
},
// {
// label: '商品参数',
// type: 'for_form', //循环遍历数组数据到表单
// name: 'paraminfo',
// default: JSON.stringify(skusdata),//没有值的情况
// placeholder: '',
// // 自定义一些数据
// ortherdata:JSON.stringify({
// html:`
// <a href="/shop/admin/skus/create?gourl=${gourl}&goods_class_id=${goodsClassId}">
// 请先创建规格数据,再来创建商品参数
// </a>
// `,
// }),
// },
],
},
//新增成功之后跳转到哪个页面
successUrl: '/shop/admin/goods-/'+goods_id+'/indexGoodsParam',
});
}
//商品参数列表
async indexGoodsParam(){
const { ctx, app } = this;
//1.参数验证
this.ctx.validate({
goods_id: {
type: 'int',
required: true,
desc: '商品id',
// defValue: 0,
range:{
min:1,
}
},
});
// 参数
const goods_id = ctx.params.goods_id;
let Goodsdata = await app.model.Goods.findOne({ where: { id:goods_id } });
if (!Goodsdata) {
return ctx.apiFail('该商品不存在');
}
let GoodsClassdata = await app.model.GoodsClass.findOne({
where:{
id:Goodsdata.goods_class_id
},
include: [
{
model: app.model.Skus,
// as: 'children'
},
],
});
// ctx.body = GoodsClassdata; return;
//分页:可以提炼成一个公共方法page(模型名称,where条件,其他参数options)
let data = await ctx.page('GoodsParam',{
goods_id:goods_id
});
// let data = await ctx.service.goodsClass.datalist({ limit: 10000 });
// console.log('分类数据', data);
// ctx.body = data;
// return;
// data = data.rules;
//渲染公共模版
await ctx.renderTemplate({
title: '商品:' + Goodsdata.name + ' 参数列表',//现在网页title,面包屑导航title,页面标题
data,
tempType: 'table', //模板类型:table表格模板 ,form表单模板
table: {
//表格上方按钮,没有不要填buttons
buttons: [
{
url: '/shop/admin/goods-/'+goods_id+'/createGoodsParam',//新增路径
desc: '创建商品(单条)参数',//新增 //按钮名称
// icon: 'fa fa-plus fa-lg',//按钮图标
},
{
url: '/shop/admin/goods-/'+goods_id+'/selectGoodsParamBytree',//新增路径
desc: '创建商品(多条)参数',//新增 //按钮名称
// icon: 'fa fa-plus fa-lg',//按钮图标
},
// {
// url: '/shop/admin/goods/create',//新增路径
// desc: '上传商品',//新增 //按钮名称
// // icon: 'fa fa-plus fa-lg',//按钮图标
// }
],
//表头
columns: [
{
title: '商品参数信息',
// key: 'paraminfo',
class: 'text-left',//可选
render(item) { //树形数据
// console.log('每个item',item);
// if (item.level) {
// let w = item.level * 40;
// return `<span style="display:inline-block;width:${w}px"></span>`;
// }
let str =``;
let data = JSON.parse(item.paraminfo);
for(let i=0; i<data.length; i++){
let _str = `<p style="width:100%;margin-top:15px;">${data[i].title}</p>`;
let valuearr = data[i].data;
let _str_ = ``;
for(let j=0; j<valuearr.length; j++){
_str_ += `
<p style="font-size:12px;border:1px solid #ccc;padding:5px;margin-right:10px;">
<span>${valuearr[j].name}</span>:
<span style="color:#ff0000">${valuearr[j].value}</span>
</p>
`;
}
str += `<div style="display:flex;flex-wrap:wrap;">${_str}${_str_}</div>`;
}
return `<div>${str}</div>`;
}
},
// {
// title: '分类下的商品',
// // key: 'name',
// class: 'text-left',//可选
// render(item) { //树形数据
// // console.log('每个item',item);
// // if (item.level) {
// // let w = item.level * 40;
// // return `<span style="display:inline-block;width:${w}px"></span>`;
// // }
// return `<a href="/shop/admin/imageclass/${item.id}/imgList">${item.images.length}张</a>`;
// }
// },
// {
// title: '是否是导航栏栏目',
// key: 'isnav',
// width: 200,//可选
// class: 'text-center',//可选
// hidekeyData: true,//是否隐藏key对应的数据
// render(item) {
// console.log('可用状态里面每个item', item);
// let arr = [
// { value: 1, name: '是' },
// { value: 0, name: '否' },
// ];
// let str = `<div class="btn-group btn-group-${item.id}">`;
// for (let i = 0; i < arr.length; i++) {
// str += `<button type="button" class="btn btn-light" data="${item.isnav}"
// value="${arr[i].value}"
// @click="changeBtnStatus('isnav','btn-group-${item.id}',${arr[i].value},${i},${item.id},'category','Category')">${arr[i].name}</button>`;
// }
// str += `</div>`;
// return str;
// }
// },
{
title: '排序',
key: 'order',
class: 'text-center',//可选
},
{
title: '可用状态',
key: 'status',
width: 200,//可选
class: 'text-center',//可选
hidekeyData: true,//是否隐藏key对应的数据
render(item) {
// console.log('可用状态里面每个item', item);
let arr = [
{ value: 1, name: '可用' },
{ value: 0, name: '不可用' },
];
let str = `<div class="btn-group btn-group-${item.id}">`;
for (let i = 0; i < arr.length; i++) {
str += `<button type="button" class="btn btn-light" data="${item.status}"
value="${arr[i].value}"
@click="changeBtnStatus('status','btn-group-${item.id}',${arr[i].value},${i},${item.id},'/shop/admin/goods-/${goods_id}/indexGoodsParam','GoodsParam')">${arr[i].name}</button>`;
}
str += `</div>`;
return str;
}
},
{
title: '操作',
class: 'text-right',//可选
action: {
//修改
edit: function (id) {
return `/shop/admin/goods-/${id}/editGoodsParam`;
},
//删除
delete: function (id) {
return `/shop/admin/goods-/${id}/deleteGoodsParam`;
}
}
},
],
},
});
}
# 2. 路由
app/router/admin/shop.js
...
//商品参数goods_param处理
//选择一个商品分类下的skus添加到商品参数中
router.get('/shop/admin/goods-/:goods_id/selectGoodsParamBytree', controller.admin.goods.selectGoodsParamBytree);
router.get('/shop/admin/goods-/:goods_id/createGoodsParam', controller.admin.goods.createGoodsParam);
router.post('/shop/admin/goods-/:goods_id/saveGoodsParam', controller.admin.goods.saveGoodsParam);
router.get('/shop/admin/goods-/:goods_id/indexGoodsParam', controller.admin.goods.indexGoodsParam);
...
# 七、商品选购信息管理
具体查看,eggjs.创建商品sku选购信息