您现在的位置是:首页 > cms教程 > shopxo教程shopxo教程
shopxo二次开发小白进阶教程
阿兰2025-07-28shopxo教程已有人查阅
导读shopxo基于ThinkPHP5 架构,所以先去研读ThinkPHP5底层在service下,然后controller中的对象继承parent::__construct();所以,跨模块的底层调用可放在service下
shopxo基于ThinkPHP5 架构,所以先去研读ThinkPHP5
底层在service下,然后controller中的对象继承parent::__construct();
所以,跨模块的底层调用可放在service下
登录校验(用户不登录,看不到商城内容)
首页,增加
application/index/controller/goods.php文件第50行,增加
同理:
搜索页,application/index/controller/goods.php文件
文章页,application/index/controller/article.php文件
2. 用户登录流程
控制用户登录的2个关键文件
application/index/controller/User.php
application/service/UserService.php
登录模版view是:index/view/default/user/login_info.html和login_content.html
代码在index/controller/user.php中有对应的
可从dzz/system/template/app_ajax.htm 把登录信息抛到 shopxo/public/index.php/index/user/login_dam , 表示路由到application下的controller下的user.php中的
shopxo的操作分2层,以用户登录为例,第一层application/index/controller/user.php下的login()并不直接查询数据库,如果在这层查询数据库db:name('User')会报错
致命错误: Class 'app\index\controller\Db' not found
因为user.php头部并没有use think\Db; 而在第二层application/service/UserService.php下
所以还要到UserService.php下写实际的数据库操作命令:
shopxo的public/static/js/comom.js已经封装了ajax提交,在460行有Ajax请求相关js代码,所以不必再自己开发。
同时在前端html页面尾部增加submitback()js,可以根据返回的e对象,进行进一步操作,比如:
在view/default/buy/index.html中
查询thinkphp5.1的开发手册中关于数据库查询,以及文件application/service/userService.php第732行
最后对此用户进行登录处理
(2)可以在第三方登录后,同步执行shopxo的登录动作。
6. 打开调试模式
文件位置 config/app.php
app_debug 的值 false改为 true
打开调试后、如果是请求接口形式出现的错误、可以参考下面是方式查看错误
1.打开chrome浏览器
2.在键盘上按F12快捷键
3.network 下勾选 XHR
4.页面执行操作,name下找到请求链接、点击一下,右侧的headers、preview、response(response信息复制出来,看看错误)
7. 目录图标,在templete中的写法
在thinkphp/tpl/think_exception.tpl中
shopxo和PHP,Thinkphp5不同之处
把所有文件中(大致有21处)的('is_load_baidumap_api', 1), 改成('is_load_baidumap_api', 0)
9. 后台编辑用户信息
在applicaton/service/UserService.php中 function UserSave()
相关文件
11. 给商品增加规格值,或其他价格
在GoodsService.php中,改造function GoodsSave()
需注意,容易吃药的是在GoodsService.php中,对规格值specification的值,竟然用index来查找!
罪魁祸首如下:比如在规则中增加一个下拉框select,而不是text框
ajax序列化后,同样都是specifications_开头的规格值,但select框就是排列在了text框后面,从而造成保存数据偏移了!这个问题打开chrome的XHR调试可以一步步dump跟踪到。
修改service/BuyService.php中 function OrderInsert()
13. 修改用户数据表
application/index/controller/user.php
14. 导出excel
在extend/base/excel.php中有excel文件导入和导出,负责导出的function Export()厂商已经做的完善了,所以只要改动下面的代码:
在application/admin/lang/zh-ch.php下有控制导出用户表excel的标题列
15. 导入excel
负责导入的function Import() //这个貌似还没有开发完全,要自己完善
1)首先增加一个按钮,在application/admin/view/default/user/index.html增加一个按钮,导入excel表,指向{{:MyUrl('admin/excel/index')}},
2)前端页面:在admin/view/default/下新建Excel目录,新建文件Import.html,主要代码如下。(如果要用JS查看excel,则在public/static/common/js/js-xlsx/目录下传人xlsx.core.min.js,但js-xlsx在IE11下兼容性不行)
PHPExcel插件我放在了extend/excel2目录下,然后有个class文件
PHPExcel原开发者已经停止维护了,找一个好的网上版本
PHPExcel对读取的excel格式有一定要求,所以让用户先下载你标准格式,然后做一些信息提示是非常有必要的
PHP各个版本程序语法上有不同,比如5.4后的数组声明就不一样,所以做好测试。我以上代码在PHP7.4下有测试通过
thinkPHP5 上传文件简单好用,拼接完整的文件地址给PHPExcel即可。可参考官方教程:上传 · ThinkPHP5.0完全开发手册 · 看云
16. 一些常量
模版中可用:
在s_power数据库中,不想显示哪个菜单,就把is_show设为0
18. 增加物流信息跟踪
目前有几个快递API平台可选择,有区别(收费版快宝最便宜,免费版可用快递100和快递鸟,如果非要免费版顺丰则只能快递100):
快递100, 快递100 , (要注册企业版才可以查顺丰,100单免费测试) ***6cyq,价格0.09元/次
快递鸟,注册后6个月免费(免费版不支持顺丰)。快递鸟网址,价格0.06元/次
2021年快递鸟免费版仅支持申通、圆通、中通三家快递,收费版 价格昂贵 最低1280一年
快宝,免费5条/天,10元 800次。快宝开放平台官网,快宝暂不支持顺丰, 价格:0.012元/次
快递100
API接口介绍:快递100API开放平台
集成到shopxo中去
1)前台 view/order/detail.html 340行左右,增加一个button,用Ajax 丢到{{:MyUrl(('index/order/package') }}
18. 积分有效期的开发
需求背景:客户定期给会员充积分,充入积分时有积分有效期一列;客户需要让会员知道积分充入的时间、积分过期、积分有效这些信息。
而原有shopxo系统,存在积分明细表s_user_integral_log和用户表s_user中的积分项(当前积分,冻结积分),达不到客户上述要求,因此需要二次开发。
参考这篇文章思路:积分有效期的设计处理方案_DannyIsCoder的博客
解决方案
新添加了一张表“可用积分明细表”。该表的关键字段如下:
原有积分明细表
文章的思路是在某过期日统计该用户的可用积分时,发现有一条记录id为103的记录已过期,那么统计完之后,各表变化如下:
可用积分表及时删除过期记录:
何时触发统计积分过期
My PLAN:在(在点击个人中心下的积分中心时,以及在积分购物时),2次都触发有效积分统计。
问题:如果不新建可用积分明细表,可否
其实也可。只要在用户登录时(同时在积分购物时,点击个人中心时),3次都触发有效积分统计,对于过期的积分到积分明细表中做扣除,然后把统计total值更新到用户表中的总积分下,即可。
不必新建“可用积分明细表”
客户想法再次明确!
因客户希望的积分政策不存在交叉情况,所以就比较简单了!
举例:
比如:某user原有1000分,有效期2021.1.1-2021.6.30。到了7.1这天不管里面剩余多少,都作废。2021.7.1之前,会充入衔接的下一批积分。不累计不累加。
对应的表就是
会员id , 类型1积分, 添加时间, 过期时间, 是否已过期
100, 1000, 2021.1.1.0:00 2021.6.30.23:59 已过期
图1: 客户希望这种类型的积分记录
要出现上图的view(可称为账号余额),
只需查询表s_user_integral_log 中所有充值记录行(type=1,add_type=1 表示批量积分充值)。
图2: 客户希望这种消费记录
要出现图2,只需查询表s_user_integral_log 中所有type=0, add_type为0的记录
更新可用余额和更新有效期的具体算法:
(1)在表s_user_integral_log ,增加expire_time, expire_rd , add_type 3个字段,分布表示积分有效期和是否已过期标志。
其中,expire_time,仅在excel批量上传充值时,才设置
expire_rd,仅对充值那行记录(即expire_time非空的那行记录)才设置,1表示过期。
add_type,1表示充值
(2)原表中operation_integral 表示充值的金额(积分充值、彩页充值则新增2行记录)
(3)原表中new_integral ,可用来表示最新可用的积分余额,对应user表中credit1 同步
(4)在IntegralService.php中,增加IntegralExpire()函数,在积分购物前和显示积分充值记录时,2次触发,更新有效期状态
(5)在每次购物扣除积分后,直接将最新可用的积分credit1,credit2 更新到此用户的有效期内充值那条的new_integral
负责积分列表的底层操作在service/IntegralService.php中100行 IntegralLogList()
TP5 中的时间比较:时间查询 · ThinkPHP5.0完全开发手册 · 看云
几个常用的日期函数
gmmktime() 函数,返回 GMT 日期的 UNIX 时间戳
mktime()函数,返回一个日期的 UNIX 时间戳,和gmmktime()类似
date() 函数, 把时间戳格式化为可读性更好的日期和时间
time()函数,服务器当前时间的时间戳
strtotime()函数:将任何英文文本的日期时间描述解析为时间戳。
举例:
Jan-05-2002
Feb-01-2002
Jan-01-2001
Jan-01-1999
19. 前台用户中心的左侧菜单
application/service/NavigationService.php, 858行
20. 给订单管理,增加批量订单打印功能
要做5步,没办法框架就是得这么麻烦(原生的php一步就能达成)
(1) 先到form/order.php下增加全选功能
21. 增加备货,在后台右侧增加操作按钮
这里只讲方法,就不贴具体代码了
方法:
1. 到operate.html中增加新按钮,button的class,submit,data-url,data-id, 都要改
2. 到html中增加一个弹窗,onclick
3. 到public\static\js\order.js 中增加对这个新按钮和form提交数据对前期数据校验,确保id,userid非空
4. 到order中增加新function
5. 到orderService下增加新function
6. 到数据库中修改
7. 到涉及到的相关代码中,修改。因为增加了“备货”环节,所以status相关都要改
22. 仓库管理下面增加“商品进/出明细”
这里只讲方法,就不贴具体代码了
方法:
1. 在“权限控制”-“权限分配”-“仓库管理”下,新增一个“商品操作记录”,控制器写WarehouseGIO
2. 给管理员分配“商品操作记录”的权限
3. 到application\admin\form下\新建Warehousegio.php, gio要小写
4. 到application\admin\controller下\新建Warehousegio.php, gio要小写
5. 把上面2个新文件的Class名称,根据错误提示改错误,先跑通
6. 在view/defalut/下新建warehousegio目录,放模板文件进去
23. 前台不显示库存为0的商品
方法:
1. index\service\SearchService.php第99行,GoodsList函数里增加
待处理:
首页打开慢
产品图片上传时,要增加压缩图片功能
底层在service下,然后controller中的对象继承parent::__construct();
所以,跨模块的底层调用可放在service下
登录校验(用户不登录,看不到商城内容)
首页,增加
application/index/controller/index.php文件第50行,增加
$this->IsLogin();
//如果没登录,将跳转到登录页
商品页,增加登录校验application/index/controller/goods.php文件第50行,增加
同理:
搜索页,application/index/controller/goods.php文件
文章页,application/index/controller/article.php文件
2. 用户登录流程
控制用户登录的2个关键文件
application/index/controller/User.php
application/service/UserService.php
登录模版view是:index/view/default/user/login_info.html和login_content.html
代码在index/controller/user.php中有对应的
loginInfo(), Login()
public function Login()
{
// 是否ajax请求
if(!IS_AJAX)
{
return $this->error('非法访问');
}
// 调用服务层
return UserService::Login($this->data_post);
}
然后login动作抛到上层的application/service/UserService.php的610行Login()可从dzz/system/template/app_ajax.htm 把登录信息抛到 shopxo/public/index.php/index/user/login_dam , 表示路由到application下的controller下的user.php中的
function LoginDam()
//注意:以下代码并不完整,只是写个思路,起到关键线索提示作用
//UserService.php
function LoginDam(){
//做一些查询user数据库,比对密码的事
$user=Db::name('user')->field('id,pwd,salt,status')->where($where)->find();
。。。。。
//到UserLoginHandle中处理登录后的具体事情
return self::UserLoginHandle($user['id'],$params);
}
function UserLoginHandle(){
//在返回时,增加自己想要返回的内容,比如返回groupid
$return=['body_html'=>'', 'groupid'=>$user['groupid'], ];
}
//在loginDam()中,不需要ajax,直接判断登录,然后跳转到商城首页(商城首页会再次检查登录状态)
//这样在上层的user.php中对返回值进行判断,比如:
function LoginDam(){
$r=UserService::LoginDam($this->data_post);
if($r['msg']=="登录成功"){
$this->redirect('index/search/index'); //跳转到首页
}else{
//跳转到对应产品类别
$this->redirect('index/search/index', 'category_id'.$r['data']['groupid']);
}
}
3. 数据库操作shopxo的操作分2层,以用户登录为例,第一层application/index/controller/user.php下的login()并不直接查询数据库,如果在这层查询数据库db:name('User')会报错
致命错误: Class 'app\index\controller\Db' not found
因为user.php头部并没有use think\Db; 而在第二层application/service/UserService.php下
所以还要到UserService.php下写实际的数据库操作命令:
//数据库操作要写在UserService.php下,因为它头部有use think\Db;
// 获取用户账户信息
$where = [$ac['data'] => $params['accounts'], 'is_delete_time'=>0];
$user = Db::name('User')->field('id,pwd,salt,status')->where($where)->find();
4. Ajax机制shopxo的public/static/js/comom.js已经封装了ajax提交,在460行有Ajax请求相关js代码,所以不必再自己开发。
同时在前端html页面尾部增加submitback()js,可以根据返回的e对象,进行进一步操作,比如:
//前端html页面尾部js
<script type="text/javascript">
// 提交订单回调
function BuySubmitBack(e)
{
if(e.code == 0)
{
$('#buy-order-submit-modal').modal({
closeViaDimmer: false,
width: 280,
height: 140
});
$.AMUI.progress.done();
Prompt(e.msg, 'success');
setTimeout(function()
{
window.location.href = e.data.jump_url;
}, 1500);
} else {
$('form.form-validation').find('button[type="submit"]').button('reset');
$.AMUI.progress.done();
Prompt(e.msg);
}
}
<public/static/js/comom.js/script>
//public/static/js/comom.js 368行
var request_value = $form.attr('request-value') || null;
// 以 ajax 开头的都会先请求再处理
// ajax-reload 请求完成后刷新页面
// ajax-url 请求完成后调整到指定的请求值
// ajax-fun 请求完成后调用指定方法
// ajax-view 请求完成后仅提示文本信息
// sync 不发起请求、直接同步调用指定的方法
// jump 不发起请求、拼接数据参数跳转到指定 url 地址
var request_handle = ['ajax-reload', 'ajax-url', 'ajax-fun', 'ajax-view', 'sync', 'jump', 'form'];
//在460行,有Ajax请求相关js代码,所以不必再自己开发
$.ajax({
url: action,
type: method,
dataType: "json",
timeout: $form.attr('timeout') || 60000,
data: GetFormVal(form_name),
processData: false,
contentType: false,
success: function(result)
{
$.AMUI.progress.done();
来一段例子,以订单为例在view/default/buy/index.html中
<form class="am-form form-validation nav-buy" action="{{:MyUrl('index/buy/add')}}" method="post" class="nav-buy" request-type="ajax-fun" request-value="BuySubmitBack" data-site-type="{{$common_site_type}}" data-is-booking="{{$common_order_is_booking}}">
<input type="hidden" name="goods_id" value="{{if isset($params['goods_id'])}}{{$params.goods_id}}{{else /}}0{{/if}}" />
<input type="hidden" name="buy_type" value="{{if isset($params['buy_type'])}}{{$params.buy_type}}{{else /}}goods{{/if}}" />
<input type="hidden" name="stock" value="{{if isset($params['stock'])}}{{$params.stock}}{{else /}}1{{/if}}" />
<input type="hidden" name="spec" value='{{if isset($params['spec'])}}{{$params.spec}}{{/if}}' />
<input type="hidden" name="ids" value="{{if isset($params['ids'])}}{{$params.ids}}{{/if}}" />
<input type="hidden" name="address_id" value="{{if isset($base['address']) and isset($base['address']['id'])}}{{$base.address.id}}{{else /}}{{if isset($params['address_id'])}}{{$params.address_id}}{{else /}}-1{{/if}}{{/if}}" />
<input type="hidden" name="payment_id" value="{{if isset($params['payment_id'])}}{{$params.payment_id}}{{else /}}0{{/if}}" />
<input type="hidden" name="user_note" value="" />
<input type="hidden" name="site_model" value="{{if isset($params['site_model'])}}{{$params.site_model}}{{else /}}0{{/if}}" />
<!-- 订单确认页面提交订单表单内部钩子-开始 -->
{{if isset($shopxo_is_develop) and $shopxo_is_develop eq true and (!isset($is_footer) or $is_footer eq 1)}}
<div class="plugins-tag">
<span>plugins_view_buy_form_inside</span>
</div>
{{/if}}
{{if !empty($plugins_view_buy_form_inside_data) and is_array($plugins_view_buy_form_inside_data)}}
{{foreach $plugins_view_buy_form_inside_data as $hook}}
{{if is_string($hook) or is_int($hook)}}
{{$hook|raw}}
{{/if}}
{{/foreach}}
{{/if}}
<!-- 订单确认页面提交订单表单内部钩子-结束 -->
<div class="go-btn-wrap">
<button type="submit" class="btn-go btn-loading-example" title="点击此按钮,提交订单" data-am-loading="{loadingText:'处理中...'}">提交订单</button>
</div>
</form>
<!-- 订单提交后提示弹层 -->
<div class="am-modal am-modal-no-btn" tabindex="-1" id="buy-order-submit-modal">
<div class="am-modal-dialog">
<div class="am-modal-bd">
<div class="content am-vertical-align-middle am-padding-vertical-sm">
<p class="am-text-success">支付跳转中、请勿关闭页面</p>
<p class="am-text-warning am-margin-top-lg">支付失败或长时间未响应</p>
<p class="am-text-warning am-margin-xs">
<span>进入</span>
<a href="{{:MyUrl('index/order/index')}}" class="am-text-secondary">我的订单</a>
<span>后可以重新发起支付</span>
</p>
</div>
</div>
</div>
</div>
<script type="text/javascript">
// 提交订单回调
function BuySubmitBack(e)
{
if(e.code == 0)
{
//弹出提示窗口
$('#buy-order-submit-modal').modal({
closeViaDimmer: false,
width: 280,
height: 140
});
$.AMUI.progress.done();
Prompt(e.msg, 'success');
setTimeout(function()
{
window.location.href = e.data.jump_url;
}, 1500);
} else {
$('form.form-validation').find('button[type="submit"]').button('reset');
$.AMUI.progress.done();
Prompt(e.msg);
}
}
</script>
以用户登录为例
//在index/view/default/user/login_content.html中
<form class="am-form form-validation-username" method="post"
action="{{:MyUrl('index/user/login')}}"
request-type="ajax-fun"
request-value="LoginBackHandle">
......
</form>
根据public/static/common/js/common.js第226行
/*
* @param {[string] [form_name] [标题class或id]}
* @param {[string] [action] [请求地址]}
* @param {[string] [method] [请求类型 POST, GET]}
* @param {[string] [request-type] [回调类型 ajax-url, ajax-fun, ajax-reload]}
* @param {[string] [request-value] [回调值 ajax-url地址 或 ajax-fun方法]}
*/
5. 根据用户类别,显示不同类别分类的产品,包括logo也换掉查询thinkphp5.1的开发手册中关于数据库查询,以及文件application/service/userService.php第732行
DB::name('User')->field('id,pwd,salt,status')->where($where)->find();
.... 一系列比对操作最后对此用户进行登录处理
userLoginHandle($user['id'],$params);
...
801行 function UserLoginHandle($user_id, $params=[]){
(1)可以插入跳转语句,根据用户所属类别,跳转到相应类别到产品页(2)可以在第三方登录后,同步执行shopxo的登录动作。
6. 打开调试模式
文件位置 config/app.php
app_debug 的值 false改为 true
打开调试后、如果是请求接口形式出现的错误、可以参考下面是方式查看错误
1.打开chrome浏览器
2.在键盘上按F12快捷键
3.network 下勾选 XHR
4.页面执行操作,name下找到请求链接、点击一下,右侧的headers、preview、response(response信息复制出来,看看错误)
7. 目录图标,在templete中的写法
<img src="{{ $goods_category_list[0]['icon'] }}" />
//用{{ }} 表示 <?php echo ?>
debug尾部的shopxo在thinkphp/tpl/think_exception.tpl中
shopxo和PHP,Thinkphp5不同之处
//1. shopxo中没有驼峰,url中的login_dam直接对应操作function login_dam()
//2. 接受from表单传值,用$username = $this->request->post('username');
不用$_GET(),会报错
//3. {{if !empty($v['icon'])}} 而不是 {if !empty($v['icon'])},要用2个{}
8. 关闭烦人的百度map报错把所有文件中(大致有21处)的('is_load_baidumap_api', 1), 改成('is_load_baidumap_api', 0)
9. 后台编辑用户信息
在applicaton/service/UserService.php中 function UserSave()
// 更新数据
$data = [
'username' => isset($params['username']) ? $params['username'] : '',
'nickname' => isset($params['nickname']) ? $params['nickname'] : '',
'mobile' => isset($params['mobile']) ? $params['mobile'] : '',
'email' => isset($params['email']) ? $params['email'] : '',
'address' => isset($params['address']) ? $params['address'] : '',
'gender' => intval($params['gender']),
'integral' => intval($params['integral']),
'status' => intval($params['status']),
'alipay_openid' => isset($params['alipay_openid']) ? $params['alipay_openid'] : '',
'baidu_openid' => isset($params['baidu_openid']) ? $params['baidu_openid'] : '',
'toutiao_openid' => isset($params['toutiao_openid']) ? $params['toutiao_openid'] : '',
'qq_openid' => isset($params['qq_openid']) ? $params['qq_openid'] : '',
'qq_unionid' => isset($params['qq_unionid']) ? $params['qq_unionid'] : '',
'weixin_openid' => isset($params['weixin_openid']) ? $params['weixin_openid'] : '',
'weixin_unionid' => isset($params['weixin_unionid']) ? $params['weixin_unionid'] : '',
'weixin_web_openid' => isset($params['weixin_web_openid']) ? $params['weixin_web_openid'] : '',
'birthday' => empty($params['birthday']) ? 0 : strtotime($params['birthday']),
'referrer' => empty($params['referrer']) ? 0 : intval($params['referrer']),
'upd_time' => time(),
];
10. 积分支付相关文件
//在public/static/index/default/js/buy.js中,
//支付方式 payment_id
更新用户表s_user中的credit,integral,以及积分明细表s_user_integral_log11. 给商品增加规格值,或其他价格
在GoodsService.php中,改造function GoodsSave()
// 基础数据
$data = [
'title' => $params['title'],
'title_color' => empty($params['title_color']) ? '' : $params['title_color'],
'simple_desc' => $params['simple_desc'],
'model' => $params['model'],
'place_origin' => isset($params['place_origin']) ? intval($params['place_origin']) : 0,
'inventory_unit' => $params['inventory_unit'],
'give_integral' => $give_integral,
'buy_min_number' => max(1, isset($params['buy_min_number']) ? intval($params['buy_min_number']) : 1),
'buy_max_number' => isset($params['buy_max_number']) ? intval($params['buy_max_number']) : 0,
'is_deduction_inventory' => isset($params['is_deduction_inventory']) ? intval($params['is_deduction_inventory']) : 0,
'is_shelves' => isset($params['is_shelves']) ? intval($params['is_shelves']) : 0,
'content_web' => $content_web,
'photo_count' => count($photo['data']),
'images' => $images,
'brand_id' => isset($params['brand_id']) ? intval($params['brand_id']) : 0,
'video' => $attachment['data']['video'],
'seo_title' => empty($params['seo_title']) ? '' : $params['seo_title'],
'seo_keywords' => empty($params['seo_keywords']) ? '' : $params['seo_keywords'],
'seo_desc' => empty($params['seo_desc']) ? '' : $params['seo_desc'],
'is_exist_many_spec' => empty($specifications['data']['title']) ? 0 : 1,
'spec_base' => empty($specifications_base['data']) ? '' : json_encode($specifications_base['data'], JSON_UNESCAPED_UNICODE),
'fictitious_goods_value' => $fictitious_goods_value,
'site_type' => isset($params['site_type']) ? $params['site_type'] : -1,
];
如果要在goods_specbase中增加规格值,比如price_type(积分类型)。需注意,容易吃药的是在GoodsService.php中,对规格值specification的值,竟然用index来查找!
罪魁祸首如下:比如在规则中增加一个下拉框select,而不是text框
ajax序列化后,同样都是specifications_开头的规格值,但select框就是排列在了text框后面,从而造成保存数据偏移了!这个问题打开chrome的XHR调试可以一步步dump跟踪到。
public static function GetFormGoodsSpecificationsParams($params = [])
{
$data = [];
$title = [];
$images = [];
// 基础字段数据字段长度
$base_count = 6;
// 规格值
foreach($params as $k=>$v)
{
if(substr($k, 0, 15) == 'specifications_')
{
$keys = explode('_', $k);
if(count($keys) > 1)
{
if($keys[1] != 'name')
{
foreach($v as $ks=>$vs)
{
if($keys[1] == 'extends')
{
$data[$ks][] = empty($vs) ? null : htmlspecialchars_decode($vs);
} else {
$data[$ks][] = trim($vs);
}
}
}
}
}
}
// 要去修正$temp_key 中的顺序才行
if(!empty($data['data']))
{
// 基础字段
$count = count($data['data'][0]);
$temp_key = ['price', 'weight', 'coding', 'barcode', 'original_price', 'extends'];
$key_count = count($temp_key);
// 等于key总数则只有一列基础规格
if($count == $key_count)
{
12. 订单处理修改service/BuyService.php中 function OrderInsert()
13. 修改用户数据表
application/index/controller/user.php
14. 导出excel
在extend/base/excel.php中有excel文件导入和导出,负责导出的function Export()厂商已经做的完善了,所以只要改动下面的代码:
在application/admin/lang/zh-ch.php下有控制导出用户表excel的标题列
// 用户excel导出标题列表
'excel_user_title_list' => [
'username' => [
'name' => '用户名',
'type' => 'string',
],
'nickname' => [
'name' => '昵称',
'type' => 'int',
],
'gender_text' => [
'name' => '性别',
'type' => 'string',
],
'birthday_text'=> [
'name' => '生日',
'type' => 'string',
],
'status_text'=> [
'name' => '状态',
'type' => 'string',
],
注意:在数据表s_power中增加ExcelImport,或者在后台的权限管理里面,增加一个接口(参照其他接口,选不显示) ,否则会提示无权限15. 导入excel
负责导入的function Import() //这个貌似还没有开发完全,要自己完善
1)首先增加一个按钮,在application/admin/view/default/user/index.html增加一个按钮,导入excel表,指向{{:MyUrl('admin/excel/index')}},
2)前端页面:在admin/view/default/下新建Excel目录,新建文件Import.html,主要代码如下。(如果要用JS查看excel,则在public/static/common/js/js-xlsx/目录下传人xlsx.core.min.js,但js-xlsx在IE11下兼容性不行)
{{include file="public/header" /}}
<!-- right content start -->
<div class="content-right" style="background:#fff;">
<div class="content">
<h1>用户积分导入</h1>
<div style="padding:25px;line-height:2">
<p style="line-height:3"><strong>步骤 1</strong>
<a class="btn btn-simple" href="" target="_blank">下载标准excel模板</a>
<span class="help-inline ml10">excel格式文件</span>
</p>
<p style="line-height:4"><strong>步骤 2</strong>:分配用户积分</p>
<div style="padding:10px;">
<form name="importfile" class="form-horizontal form-horizontal-left" method="post" action="{{:MyUrl('admin/excel/import')}}" id="importfile" enctype="multipart/form-data" request-type="ajax-fun" request-value="importexcel">
<input type="hidden" name="importfilesubmit" value="true">
<div class="form-group">
<label class="control-label " style="width:140px;text-align:left;padding-left:0">步骤3:导入</label>
<div class="controls">
<input id="file" type="file" name="importfile" accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" class="form-control" style="padding:6px 12px;" onchange="selectFile()" />
</div>
<span class="help-inline">支持xlsx格式文件</span>
</div>
<div class="form-group">
<label class="control-label"></label>
<div class="controls ml20">
<input type="submit" name="submit" class="btn btn-simple" style="padding:6px 25px" value="提交" />
</div>
</div>
</form>
</div>
<div class="alert alert-warning " style="padding:10px;color:#333;text-shadow:1px 1px 1px #FFF">
<p><strong>注意事项</strong></p>
<ul class="list-unstyled" style="padding-left:25px;list-style-type:decimal;">
<li>下载标准excel模板,不要随意改动表格格式,不要合并单元格</li>
<li>表格第一行必须是字段名</li>
<li>积分用正整数,普通数值类型格式,不要有千分符</li>
<li>仅支持xlsx后缀的OFFICE EXCEL 2007或以上版本文件</li>
</ul>
</div>
</div>
</div>
<div class="container">
<p>表格预览:</p>
<div id="result" contenteditable=""><br></div>
</div>
</div>
<!-- right content end -->
<!-- footer start -->
{{include file="public/footer" /}}
<!-- footer end -->
<script src="{{$attachment_host}}/static/common/js/js-xlsx/xlsx.core.min.js" type="text/javascript"></script>
<script type="text/javascript">
function selectFile() {
document.getElementById('file').click();
}
// 读取本地excel文件
function readWorkbookFromLocalFile(file, callback) {
var reader = new FileReader();
reader.onload = function(e) {
var data = e.target.result;
var workbook = XLSX.read(data, {type: 'binary'});
if(callback) callback(workbook);
};
reader.readAsBinaryString(file);
}
// 从网络上读取某个excel文件,url必须同域,否则报错
function readWorkbookFromRemoteFile(url, callback) {
var xhr = new XMLHttpRequest();
xhr.open('get', url, true);
xhr.responseType = 'arraybuffer';
xhr.onload = function(e) {
if(xhr.status == 200) {
var data = new Uint8Array(xhr.response)
var workbook = XLSX.read(data, {type: 'array'});
if(callback) callback(workbook);
}
};
xhr.send();
}
// 读取 excel文件
function outputWorkbook(workbook) {
var sheetNames = workbook.SheetNames; // 工作表名称集合
sheetNames.forEach(name => {
var worksheet = workbook.Sheets[name]; // 只能通过工作表名称来获取指定工作表
for(var key in worksheet) {
// v是读取单元格的原始值
console.log(key, key[0] === '!' ? worksheet[key] : worksheet[key].v);
}
});
}
function readWorkbook(workbook) {
var sheetNames = workbook.SheetNames; // 工作表名称集合
var worksheet = workbook.Sheets[sheetNames[0]]; // 这里我们只读取第一张sheet
var csv = XLSX.utils.sheet_to_csv(worksheet);
document.getElementById('result').innerHTML = csv2table(csv);
}
// 将csv转换成表格
function csv2table(csv)
{
var html = '<table>';
var rows = csv.split('\n');
rows.pop(); // 最后一行没用的
rows.forEach(function(row, idx) {
var columns = row.split(',');
columns.unshift(idx+1); // 添加行索引
if(idx == 0) { // 添加列索引
html += '<tr>';
for(var i=0; i<columns.length; i++) {
html += '<th>' + (i==0?'':String.fromCharCode(65+i-1)) + '</th>';
}
html += '</tr>';
}
html += '<tr>';
columns.forEach(function(column) {
html += '<td>'+column+'</td>';
});
html += '</tr>';
});
html += '</table>';
return html;
}
function table2csv(table) {
var csv = [];
$(table).find('tr').each(function() {
var temp = [];
$(this).find('td').each(function() {
temp.push($(this).html());
})
temp.shift(); // 移除第一个
csv.push(temp.join(','));
});
csv.shift();
return csv.join('\n');
}
// csv转sheet对象
function csv2sheet(csv) {
var sheet = {}; // 将要生成的sheet
csv = csv.split('\n');
csv.forEach(function(row, i) {
row = row.split(',');
if(i == 0) sheet['!ref'] = 'A1:'+String.fromCharCode(65+row.length-1)+(csv.length-1);
row.forEach(function(col, j) {
sheet[String.fromCharCode(65+j)+(i+1)] = {v: col};
});
});
return sheet;
}
// 将一个sheet转成最终的excel文件的blob对象,然后利用URL.createObjectURL下载
function sheet2blob(sheet, sheetName) {
sheetName = sheetName || 'sheet1';
var workbook = {
SheetNames: [sheetName],
Sheets: {}
};
workbook.Sheets[sheetName] = sheet;
// 生成excel的配置项
var wopts = {
bookType: 'xlsx', // 要生成的文件类型
bookSST: false, // 是否生成Shared String Table,官方解释是,如果开启生成速度会下降,但在低版本IOS设备上有更好的兼容性
type: 'binary'
};
var wbout = XLSX.write(workbook, wopts);
var blob = new Blob([s2ab(wbout)], {type:"application/octet-stream"});
// 字符串转ArrayBuffer
function s2ab(s) {
var buf = new ArrayBuffer(s.length);
var view = new Uint8Array(buf);
for (var i=0; i!=s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
return buf;
}
return blob;
}
/**
* 通用的打开下载对话框方法,没有测试过具体兼容性
* @param url 下载地址,也可以是一个blob对象,必选
* @param saveName 保存文件名,可选
*/
function openDownloadDialog(url, saveName)
{
if(typeof url == 'object' && url instanceof Blob)
{
url = URL.createObjectURL(url); // 创建blob地址
}
var aLink = document.createElement('a');
aLink.href = url;
aLink.download = saveName || ''; // HTML5新增的属性,指定保存文件名,可以不要后缀,注意,file:///模式下不会生效
var event;
if(window.MouseEvent) event = new MouseEvent('click');
else
{
event = document.createEvent('MouseEvents');
event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
}
aLink.dispatchEvent(event);
}
$(function() {
document.getElementById('file').addEventListener('change', function(e) {
var files = e.target.files;
if(files.length == 0) return;
var f = files[0];
if(!/\.xlsx$/g.test(f.name)) {
alert('仅支持读取xlsx格式的Excel文件!');
return;
}
readWorkbookFromLocalFile(f, function(workbook) {
readWorkbook(workbook);
});
});
//loadRemoteFile('./sample/test.xlsx');
});
function loadRemoteFile(url) {
readWorkbookFromRemoteFile(url, function(workbook) {
readWorkbook(workbook);
});
}
function exportExcel() {
var csv = table2csv($('#result table')[0]);
var sheet = csv2sheet(csv);
var blob = sheet2blob(sheet);
openDownloadDialog(blob, '导出.xlsx');
}
</script>
3)后端代码:在admin/controller/下新建excel.php, 整合后的完整代码如下:PHPExcel插件我放在了extend/excel2目录下,然后有个class文件
<?php
namespace app\admin\controller;
use think\facade\Hook;
use app\service\IntegralService;
use app\service\UserService;
use think\Db;
use PHPExcel;
use PHPExcel_IOFactory;
use PHPExcel_Cell;
class Excel extends Common
{
/**
* [ExcelExport excel文件导入]
* @author bruce
* @blog
* @version 0.0.1
* @datetime 2021-04-30
*/
public function Index()
{
if(!empty($this->data_request['importfilesubmit'])
&& $this->data_request['importfilesubmit']=='true'){
self::Import();
}
return $this->fetch();
}
public function Import(){
//upload文件
// 获取表单上传文件 前端file name=importfile
$file = request()->file('importfile');
// 移动到框架应用根目录/public/uploads/ 目录下
if($file){
$info = $file->move(ROOT_PATH . 'public' . DS . 'uploads');
if($info){
// 成功上传后 获取上传信息
// 输出 xlsx
//echo $info->getExtension();
// 输出格式 目录名/文件名.xlsx
//echo $info->getSaveName();
// 输出格式 文件名.xlsx
//echo $info->getFilename();
}else{
// 上传失败获取错误信息
return $file->getError();
}
}
//引入PHPExcel库
require_once ROOT.'/extend/excel2/class_PHPExcel.php';
$inputFileName= ROOT.'/public/public/uploads/'.$info->getSaveName();
//$inputFileName= ROOT.'/extend/excel2/1.xlsx';
$inputFileType = PHPExcel_IOFactory::identify($inputFileName);
$objReader = PHPExcel_IOFactory::createReader($inputFileType);
$objPHPExcel = $objReader->load($inputFileName);
$sheetData = $objPHPExcel->getActiveSheet()->toArray(null,true,true,true);
//获取导入数据的字段
$h0=[
'username' =>'账户简称',
'credit1' =>'积分1',
'credit2' =>'积分2',
];
//建立数据表(数组)的表头 hearer
//当sheet中实际存在和h0中一样的title时才在h中建立表头(表头索引为A,B这类)
//上传的表中多余的列,不计入h表头,sheet中大标题和其他无关内容也可排除掉
//一定要用推荐下载的excel文件格式,否在phpexcel读取会有问题
//还要考虑同一个表中是否有相同的用户名,如果用username做标识的话
//表格第一行必须是字段名,因为用了$sheetData[1]
$h=array();
foreach($sheetData[1] as $key =>$value){
$value=trim($value);
foreach($h0 as $fieldid=>$title){
if($title==$value){
$h[$key]=$fieldid;
break;
}
}
}
//dump($h);
if(!in_array('username',$h) || !in_array('credit1',$h) || !in_array('credit2',$h))
{
return '请上传符合格式要求的excel表';
}
//建立数据表(数组)的值 List
//每次取一行,然后插入到table
$list=array();
foreach($sheetData as $key=> $value){
if($key<=1) continue; //跳过第一行表头
//不断取出表中一行,并赋值到$temp()
foreach($value as $col =>$val){
if(trim($val)=='') continue;
//如果h中存在此列,则记入值,$h[$col]代表h["A"]=username
//如果时credit1或credit2,则换成整数,表格中不能用千分符
if(!empty($h[$col])){
if($h[$col]=='credit1' || $h[$col]=='credit2' ){
$list[$h[$col]]=(int)$val;
}else{
$list[$h[$col]]=$val;
}
}
//dump($col);
}
//dump($list); die;
//更新user表
//1. 先查询用户信息
$error_msg='';
$user = Db::name('User')->where('username',$list['username'])->find();
//dump($user);die;
if(empty($user)){
$error_msg=$error_msg.'用户['.$list['username'].']不存在, 请先为此用户建立档案 ';
//return DataReturn($error_msg, -102);
return $error_msg;
}else{
Db::name('User')->where('username',$list['username'])->update($list);
}
}
//dump($list);
//return DataReturn('操作成功', 0);
return '操作成功';
}//end function Import
}
?>
PHPExcel导入文件的一些注意点PHPExcel原开发者已经停止维护了,找一个好的网上版本
PHPExcel对读取的excel格式有一定要求,所以让用户先下载你标准格式,然后做一些信息提示是非常有必要的
PHP各个版本程序语法上有不同,比如5.4后的数组声明就不一样,所以做好测试。我以上代码在PHP7.4下有测试通过
thinkPHP5 上传文件简单好用,拼接完整的文件地址给PHPExcel即可。可参考官方教程:上传 · ThinkPHP5.0完全开发手册 · 看云
16. 一些常量
模版中可用:
{{$attachment_host}} //表示shopxo目录下public/
程序中可用:
GetDocumentRoot() //可获得程序根目录
__DIR__ //表示当前程序路径
__MY_HOST__ //项目HOST
__MY_URL__ //项目URL地址
__MY_PUBLIC_URL__ //项目public目录URL地址
ROOT //系统根目录 去除public
APP_PATH //定义应用目录=ROOT.'application'
//比如:
require_once ROOT.'/extend/excel2/class_PHPExcel.php';
17. admin后台的left_menu在s_power数据库中,不想显示哪个菜单,就把is_show设为0
18. 增加物流信息跟踪
目前有几个快递API平台可选择,有区别(收费版快宝最便宜,免费版可用快递100和快递鸟,如果非要免费版顺丰则只能快递100):
快递100, 快递100 , (要注册企业版才可以查顺丰,100单免费测试) ***6cyq,价格0.09元/次
快递鸟,注册后6个月免费(免费版不支持顺丰)。快递鸟网址,价格0.06元/次
2021年快递鸟免费版仅支持申通、圆通、中通三家快递,收费版 价格昂贵 最低1280一年
快宝,免费5条/天,10元 800次。快宝开放平台官网,快宝暂不支持顺丰, 价格:0.012元/次
快递100
API接口介绍:快递100API开放平台
集成到shopxo中去
1)前台 view/order/detail.html 340行左右,增加一个button,用Ajax 丢到{{:MyUrl(('index/order/package') }}
<form class="am-form form-validation nav-buy" action="{{:MyUrl('index/order/package')}}"
method="post" class="nav-buy" request-type="ajax-fun" request-value="BuySubmitBack" data-site-type="{{$common_site_type}}" data-is-booking="{{$common_order_is_booking}}">
<input type="hidden" name="package_company" value="$data.express_name" />
<input type="hidden" name="package_code" value="{{$data.express_number}}" />
<input type="hidden" name="package_phone" value="{{$data.address_data.tel}}" />
<button type="submit" class="btn-go btn-loading-example" title="查询详细快递状态"
data-am-loading="{loadingText:'处理中...'}">查询详细快递状态</button>
</form>
在文件尾部增加
<!-- 查询物流信息,提交后提示弹层 -->
<div class="am-modal am-modal-no-btn" tabindex="-1" id="buy-order-submit-modal">
<div class="am-modal-dialog">
<div class="am-modal-bd">
<div class="content am-vertical-align-middle am-padding-vertical-sm">
<p class="am-text-success">查询中、请勿关闭页面</p>
<p class="am-text-warning am-margin-top-lg">查询失败或长时间未响应</p>
</div>
</div>
</div>
</div>
<script type="text/javascript">
// 查询物流信息回调
function BuySubmitBack(e)
{
if(e.code == 0)
{
$('#buy-order-submit-modal').modal({
closeViaDimmer: false,
width: 280,
height: 140
});
$.AMUI.progress.done();
Prompt(e.msg, 'success');
setTimeout(function()
{
window.location.href = e.data.jump_url;
}, 1500);
} else {
$('form.form-validation').find('button[type="submit"]').button('reset');
$.AMUI.progress.done();
Prompt(e.msg);
}
}
</script>
2) 后端,在controller/order.php中增加function package()18. 积分有效期的开发
需求背景:客户定期给会员充积分,充入积分时有积分有效期一列;客户需要让会员知道积分充入的时间、积分过期、积分有效这些信息。
而原有shopxo系统,存在积分明细表s_user_integral_log和用户表s_user中的积分项(当前积分,冻结积分),达不到客户上述要求,因此需要二次开发。
参考这篇文章思路:积分有效期的设计处理方案_DannyIsCoder的博客
解决方案
新添加了一张表“可用积分明细表”。该表的关键字段如下:
原有积分明细表
文章的思路是在某过期日统计该用户的可用积分时,发现有一条记录id为103的记录已过期,那么统计完之后,各表变化如下:
可用积分表及时删除过期记录:
何时触发统计积分过期
My PLAN:在(在点击个人中心下的积分中心时,以及在积分购物时),2次都触发有效积分统计。
问题:如果不新建可用积分明细表,可否
其实也可。只要在用户登录时(同时在积分购物时,点击个人中心时),3次都触发有效积分统计,对于过期的积分到积分明细表中做扣除,然后把统计total值更新到用户表中的总积分下,即可。
不必新建“可用积分明细表”
客户想法再次明确!
因客户希望的积分政策不存在交叉情况,所以就比较简单了!
举例:
比如:某user原有1000分,有效期2021.1.1-2021.6.30。到了7.1这天不管里面剩余多少,都作废。2021.7.1之前,会充入衔接的下一批积分。不累计不累加。
对应的表就是
会员id , 类型1积分, 添加时间, 过期时间, 是否已过期
100, 1000, 2021.1.1.0:00 2021.6.30.23:59 已过期
图1: 客户希望这种类型的积分记录
要出现上图的view(可称为账号余额),
只需查询表s_user_integral_log 中所有充值记录行(type=1,add_type=1 表示批量积分充值)。
图2: 客户希望这种消费记录
要出现图2,只需查询表s_user_integral_log 中所有type=0, add_type为0的记录
更新可用余额和更新有效期的具体算法:
(1)在表s_user_integral_log ,增加expire_time, expire_rd , add_type 3个字段,分布表示积分有效期和是否已过期标志。
其中,expire_time,仅在excel批量上传充值时,才设置
expire_rd,仅对充值那行记录(即expire_time非空的那行记录)才设置,1表示过期。
add_type,1表示充值
(2)原表中operation_integral 表示充值的金额(积分充值、彩页充值则新增2行记录)
(3)原表中new_integral ,可用来表示最新可用的积分余额,对应user表中credit1 同步
(4)在IntegralService.php中,增加IntegralExpire()函数,在积分购物前和显示积分充值记录时,2次触发,更新有效期状态
(5)在每次购物扣除积分后,直接将最新可用的积分credit1,credit2 更新到此用户的有效期内充值那条的new_integral
负责积分列表的底层操作在service/IntegralService.php中100行 IntegralLogList()
TP5 中的时间比较:时间查询 · ThinkPHP5.0完全开发手册 · 看云
几个常用的日期函数
gmmktime() 函数,返回 GMT 日期的 UNIX 时间戳
mktime()函数,返回一个日期的 UNIX 时间戳,和gmmktime()类似
date() 函数, 把时间戳格式化为可读性更好的日期和时间
time()函数,服务器当前时间的时间戳
strtotime()函数:将任何英文文本的日期时间描述解析为时间戳。
举例:
echo date("M-d-Y",mktime(0,0,0,12,36,2001)) . "<br>";
echo date("M-d-Y",mktime(0,0,0,14,1,2001)) . "<br>";
echo date("M-d-Y",mktime(0,0,0,1,1,2001)) . "<br>";
echo date("M-d-Y",mktime(0,0,0,1,1,99)) . "<br>";
结果:Jan-05-2002
Feb-01-2002
Jan-01-2001
Jan-01-1999
19. 前台用户中心的左侧菜单
application/service/NavigationService.php, 858行
20. 给订单管理,增加批量订单打印功能
要做5步,没办法框架就是得这么麻烦(原生的php一步就能达成)
(1) 先到form/order.php下增加全选功能
'form'=>[
[
'view_type' => 'checkbox',
'is_checked' => 0,
'checked_text' => '反选',
'not_checked_text' => '全选',
'align' => 'center',
'width' => 80,
],
]
(2) 在view/default/order/index.html头增加“批量打印”按钮,底部增加javascript处理checkbox
<!-- 表单顶部操作栏 -->
{{block name="form_operate_top"}}
<button class="am-btn am-btn-secondary am-radius am-btn-xs am-icon-print"
onclick="submitFun('print');"> 批量打印订单
</button>
<!-- 父级内容 -->
{__block__}
{{/block}}
//底部增加js,(1)做校验(2)拼接URL 传值打开新窗口
<script type="text/javascript">
//2021-06-17 bruce add, 批量打印
//(1)校验
//(2)拼接url,提交
function submitFun(act){
if(act=='print'){
var ids=[];
$(".am-ucheck-checkbox.am-field-valid:checked").each(function(){
console.log($(this).val());
ids.push($(this).val());
});
//(1)校验
if(ids.length>0){
//(2)拼接url,提交
var ids2=ids.join("-"); //用-构造字符串
//console.log(ids);
//console.log(ids2);
var url="{{:MyUrl('admin/order/print')}}";
//去掉url尾部d.HTML, 重新构造url
var url2=url.replace(".html","/ids/");
url2=url2+ids2+".html";
//console.log(url2);
window.open(url2,"_blank");
}else{
//Prompt('请先勾选订单,再打印!','error');
alert('请先勾选订单,再打印!');
}
}
}
</script>
(3)到function/order.php下,增加function Print()
public function Print()
{
if(!empty($this->data_request['ids'])){
// 获取order列表
$where2=array();
$ids2=$this->data_request['ids'];
$ids=explode("-",$ids2);
foreach ($ids as $value){
$where2['id'][]=$value;
}
//从0行开始,每次最多查询25张订单
$data_params = [
'm' => 0,
'n' => 25,
'where' => $where2,
'order_by' => 'id desc',
'is_public' => 0,
'user_type' => 'admin',
];
$ret = OrderService::OrderList($data_params);
// 基础参数赋值
$this->assign('data_list', $ret['data']);
return $this->fetch();
}
}
(4)在view/order/下,新建一个print.html页面,把打印订单的格式table模版写进去,用foreach 循环给订单号,商品赋值
//代码片段
<?php foreach($data_list as $row){ ?>
<div style="text-align: center;margin-top:30px;">
<table width="95%" align="center" border="0" cellspacing="0" cellpadding="0">
<tbody><tr>
<td colspan="9" height="50" class="title">柯尼卡美能达办公系统(中国)有限公司发货单</td>
</tr>
<tr>
<td class="td1">订单号:</td>
<td colspan="2">{{$row['order_no']}}</td>
<td class="td1" colspan="2">发货日期:</td>
<td colspan="2"> </td>
<td class="td1">发货单号:</td>
<td> </td>
</tr>
<tr>
<td class="td1"><span style="color:blue;">客户名称:</span></td>
<td colspan="2"><span style="color:red;">{{$row['user']['username']}}</span> <br>[{{$row['user']['fullname']}}] </td>
<td class="td1" colspan="2">客户编码:</td>
<td colspan="2">{{$row['user']['sale_code']}} </td>
<td class="td1">邮政编码:</td>
<td> </td>
</tr>
<tr>
<td class="td1"><span style="color:blue;">收货地址:</span></td>
<td colspan="8" height="30">{{$row['address_data']['province_name']}} {{$row['address_data']['city_name']}} {{$row['address_data']['county_name']}} {{$row['address_data']['address']}} </td>
</tr>
<tr>
<td class="td1"><span style="color:blue;">联系人:</span></td>
<td colspan="2">{{$row['address_data']['name']}} </td>
<td class="td1" colspan="2"><span style="color:blue;">联系电话:</span></td>
<td colspan="2">{{$row['address_data']['tel']}} </td>
<td class="td1">传真:</td>
<td> </td>
</tr>
</table>
<?php }?>
(5)到权限管理中,增加个“订单批量打印”,否则会提示“无权限”。另外,Service/orderservice.php中也需要小修改一点点21. 增加备货,在后台右侧增加操作按钮
这里只讲方法,就不贴具体代码了
方法:
1. 到operate.html中增加新按钮,button的class,submit,data-url,data-id, 都要改
2. 到html中增加一个弹窗,onclick
3. 到public\static\js\order.js 中增加对这个新按钮和form提交数据对前期数据校验,确保id,userid非空
4. 到order中增加新function
5. 到orderService下增加新function
6. 到数据库中修改
7. 到涉及到的相关代码中,修改。因为增加了“备货”环节,所以status相关都要改
22. 仓库管理下面增加“商品进/出明细”
这里只讲方法,就不贴具体代码了
方法:
1. 在“权限控制”-“权限分配”-“仓库管理”下,新增一个“商品操作记录”,控制器写WarehouseGIO
2. 给管理员分配“商品操作记录”的权限
3. 到application\admin\form下\新建Warehousegio.php, gio要小写
4. 到application\admin\controller下\新建Warehousegio.php, gio要小写
5. 把上面2个新文件的Class名称,根据错误提示改错误,先跑通
6. 在view/defalut/下新建warehousegio目录,放模板文件进去
23. 前台不显示库存为0的商品
方法:
1. index\service\SearchService.php第99行,GoodsList函数里增加
->where('inventory','>',0)
2. 在100行也增加->where('inventory','>',0)待处理:
首页打开慢
产品图片上传时,要增加压缩图片功能
本文标签:
很赞哦! ()
相关教程
图文教程
shopxo的selenium简单登录操作
from selenium import webdriverdriver = webdriver.Chrome()#driver.get('http://192.168.1.1/shopxo/index.php')
shopxo使用HBuilderX打包方法
进入uniapp官网下载软件根据自己的操作系统选择正式版本下载HBuilderX安装后我们这里采用uniapp插件市场一键导入也可以通过其他平台下载压缩包或者uniapp市场下载压缩包
shopxo支付宝小程序注册步骤
开发者登录 支付宝开放平台,点击 注册 按钮,进入注册页面。如果已经有支付宝账号,点击 立即登录 ,使用支付宝账号登录开放平台。如果没有支付宝账号,可以使用手机号注册成为开发者。
shopxo的MySQL分组错误解决方法
MySQL5.7.5以上版本,实现了对功能依赖的检测。如果启用了only_full_group_by SQL模式(默认启用),那么MySQL就会拒绝执行 select list、HAVING condition或ORDER BY list引用
相关源码
-
(自适应响应式)双语LED照明灯饰灯具外贸网站pbootcms源码下载模板采用响应式设计,能自动适应手机、平板和电脑等多种设备屏幕,确保用户在不同设备上都能获得良好的浏览体验。同一后台管理,数据实时同步,操作简便高效。查看源码 -
(自适应响应式)HTML5甲醛环境检测网站模板带在线留言和资料下载本模板为甲醛检测与环保科技企业开发,采用PbootCMS内核构建。首页集成空气质量数据可视化模块,服务流程采用时间轴展示设计,检测报告板块支持PDF在线预览功能查看源码 -
(PC+WAP)餐饮奶茶美食小吃招商加盟pbootcms模板源码下载为茶饮烘焙、小吃快餐等餐饮品牌打造的招商加盟系统,助力品牌快速拓展市场;双端pc+wap设计呈现加盟政策对比表。支持后台实时更新菜品图片、加盟费用等关键信息。查看源码 -
帝国cms7.5大型游戏资讯门户网站源码免费下载本模板基于帝国CMS7.5内核开发,为大型游戏资讯门户网站设计。模板自带响应式手机版,适配多种终端设备。内容架构针对游戏行业特点优化,支持游戏资讯、评测、攻略等内容类型的发布与管理。查看源码 -
帝国cms自适应古诗词古籍名句网站整站带数据基于帝国CMS打造的专业古诗词文化网站模板,专注于古典文学内容的展示与传播。模板设计蕴含传统文化韵味,支持诗词鉴赏、名句赏析、古籍整理等特色功能,为诗词爱好者提供优质的在线阅读体验。查看源码 -
(自适应)pbootcms家政服务保洁保姆打扫卫生网站模板下载本模板基于PbootCMS内核开发,为家政服务企业量身定制。设计风格温馨亲切,突出家政行业的专业与贴心服务特性,多方位展示企业服务项目与优势。查看源码
| 分享笔记 (共有 篇笔记) |
