商品Address

This commit is contained in:
2025-01-16 11:34:04 +08:00
parent 98a5c4f73f
commit c59d865fd8
33 changed files with 3179 additions and 3 deletions

View File

@ -14,6 +14,9 @@
"php": ">7.1"
},
"autoload": {
"files": [
"./src/helper.php"
],
"psr-4": {
"plugin\\points_mall\\": "src"
}

View File

@ -2,7 +2,15 @@
namespace plugin\points_mall\controller;
use HttpResponseException;
use plugin\points_mall\model\PointsMallGoodsStock;
use plugin\points_mall\model\PointsMallGoods;
use plugin\points_mall\model\PointsMallGoodsCate;
use plugin\points_mall\model\PointsMallGoodsItem;
use plugin\points_mall\service\GoodsService;
use think\admin\Controller;
use think\admin\extend\CodeExtend;
use think\admin\helper\QueryHelper;
/**
* 商品管理
@ -10,13 +18,211 @@ use think\admin\Controller;
class Goods extends Controller
{
/**
* 商品管理
* 商品数据管理
* @auth true
* @menu true
* @return void
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function index()
{
$this->title = '商品管理';
$this->type = $this->request->get('type', 'index');
PointsMallGoods::mQuery($this->get)->layTable(function () {
$this->title = '商品数据管理';
$this->cates = PointsMallGoodsCate::items();
}, function (QueryHelper $query) {
$query->withoutField('specs,content')->like('code|name#name')->like('marks,cates', ',');
$query->equal('status,level_upgrade,delivery_code,rebate_type')->dateBetween('create_time');
$query->where(['status' => intval($this->type === 'index'), 'deleted' => 0]);
});
}
/**
* 商品选择器
* @login true
* @return void
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function select()
{
$this->get['status'] = 1;
$this->get['deleted'] = 0;
$this->index();
}
/**
* 添加商品数据
* @auth true
*/
public function add()
{
$this->mode = 'add';
$this->title = '添加商品数据';
PointsMallGoods::mForm('form', 'code');
}
/**
* 编辑商品数据
* @auth true
*/
public function edit()
{
$this->mode = 'edit';
$this->title = '编辑商品数据';
PointsMallGoods::mForm('form', 'code');
}
/**
* 复制编辑商品
* @auth true
*/
public function copy()
{
$this->mode = 'copy';
$this->title = '复制编辑商品';
PointsMallGoods::mForm('form', 'code');
}
/**
* 表单数据处理
* @param array $data
*/
protected function _copy_form_filter(array &$data)
{
if ($this->request->isPost()) {
$data['code'] = CodeExtend::uniqidNumber(16, 'G');
}
}
/**
* 表单数据处理
* @param array $data
* @throws \think\admin\Exception
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
protected function _form_filter(array &$data)
{
if (empty($data['code'])) {
$data['code'] = CodeExtend::uniqidNumber(16, 'G');
}
if ($this->request->isGet()) {
$this->cates = PointsMallGoodsCate::items(true);
$data['marks'] = $data['marks'] ?? [];
$data['cates'] = $data['cates'] ?? [];
$data['specs'] = json_encode($data['specs'] ?? [], 64 | 256);
$data['items'] = PointsMallGoodsItem::itemsJson($data['code']);
$data['slider'] = is_array($data['slider'] ?? []) ? join('|', $data['slider'] ?? []) : '';
$data['delivery_code'] = $data['delivery_code'] ?? 'FREE';
} elseif ($this->request->isPost()) try {
if (empty($data['cover'])) $this->error('商品图片不能为空!');
if (empty($data['slider'])) $this->error('轮播图片不能为空!');
// 商品规格保存
[$count, $items] = [0, json_decode($data['items'], true)];
$data['marks'] = arr2str($data['marks'] ?? []);
foreach ($items as $item) if ($item['status'] > 0) {
$count++;
$data['price_market'] = min($data['price_market'] ?? $item['market'], $item['market']);
$data['price_selling'] = min($data['price_selling'] ?? $item['selling'], $item['selling']);
$data['allow_balance'] = max($data['allow_balance'] ?? $item['allow_balance'], $item['allow_balance']);
$data['allow_integral'] = max($data['allow_integral'] ?? $item['allow_integral'], $item['allow_integral']);
}
if (empty($count)) $this->error('无效的的商品价格信息!');
$this->app->db->transaction(static function () use ($data, $items) {
// 标识所有规格无效
PointsMallGoodsItem::mk()->where(['gcode' => $data['code']])->update(['status' => 0]);
$model = PointsMallGoods::mk()->where(['code' => $data['code']])->findOrEmpty();
$model->{$model->isExists() ? 'onAdminUpdate' : 'onAdminInsert'}($data['code']);
$model->save($data);
// 更新或写入商品规格
foreach ($items as $item) PointsMallGoodsItem::mUpdate([
'gsku' => $item['gsku'],
'ghash' => $item['hash'],
'gcode' => $data['code'],
'gspec' => $item['spec'],
'gimage' => $item['image'],
'status' => $item['status'] ? 1 : 0,
'price_cost' => $item['cost'],
'price_market' => $item['market'],
'price_selling' => $item['selling'],
'allow_balance' => $item['allow_balance'],
'allow_integral' => $item['allow_integral'],
'number_virtual' => $item['virtual'],
'number_express' => $item['express'],
'reward_balance' => $item['balance'],
'reward_integral' => $item['integral'],
], 'ghash', ['gcode' => $data['code']]);
});
// 刷新产品库存
GoodsService::stock($data['code']);
$this->success('商品编辑成功!', 'javascript:history.back()');
} catch (HttpResponseException $exception) {
throw $exception;
} catch (\Exception $exception) {
throw $exception;
// $this->error($exception->getMessage());
}
}
/**
* 商品库存入库
* @auth true
* @return void
*/
public function stock()
{
$input = $this->_vali(['code.require' => '商品不能为空哦!']);
if ($this->request->isGet()) {
$this->vo = PointsMallGoods::mk()->where($input)->with('items')->findOrEmpty()->toArray();
empty($this->vo) ? $this->error('无效的商品!') : $this->fetch();
} else try {
[$data, $post, $batch] = [[], $this->request->post(), CodeExtend::uniqidDate(12, 'B')];
if (isset($post['gcode']) && is_array($post['gcode'])) {
foreach (array_keys($post['gcode']) as $key) if ($post['gstock'][$key] > 0) $data[] = [
'batch_no' => $batch,
'ghash' => $post['ghash'][$key],
'gcode' => $post['gcode'][$key],
'gspec' => $post['gspec'][$key],
'gstock' => $post['gstock'][$key],
];
empty($data) || PointsMallGoodsStock::mk()->saveAll($data);
}
GoodsService::stock($input['code']);
$this->success('库存更新成功!');
} catch (HttpResponseException $exception) {
throw $exception;
} catch (\Exception $e) {
throw $e;
// $this->error($e->getMessage());
}
}
/**
* 商品上下架
* @auth true
*/
public function state()
{
PointsMallGoods::mSave($this->_vali([
'status.in:0,1' => '状态值范围异常!',
'status.require' => '状态值不能为空!',
]), 'code');
}
/**
* 删除商品数据
* @auth true
*/
public function remove()
{
PointsMallGoods::mSave($this->_vali([
'code.require' => '编号不能为空!',
'deleted.value' => 1
]), 'code');
}
}

View File

@ -0,0 +1,114 @@
<?php
namespace plugin\points_mall\controller;
use plugin\points_mall\model\PointsMallGoodsCate;
use think\admin\Controller;
use think\admin\extend\DataExtend;
use think\admin\helper\QueryHelper;
class GoodsCate extends Controller
{
/**
* 最大级别
* @var integer
*/
protected $maxLevel = 3;
/**
* 商品分类管理
* @auth true
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function index()
{
PointsMallGoodsCate::mQuery($this->get)->layTable(function () {
$this->title = "商品分类管理";
}, static function (QueryHelper $query) {
$query->where(['deleted' => 0]);
$query->like('name')->equal('status')->dateBetween('create_time');
});
}
/**
* 列表数据处理
* @param array $data
*/
protected function _page_filter(array &$data)
{
$data = DataExtend::arr2table($data);
}
/**
* 添加商品分类
* @auth true
*/
public function add()
{
PointsMallGoodsCate::mForm('form');
}
/**
* 编辑商品分类
* @auth true
*/
public function edit()
{
PointsMallGoodsCate::mForm('form');
}
/**
* 表单数据处理
* @param array $data
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
protected function _form_filter(array &$data)
{
if ($this->request->isGet()) {
$data['pid'] = intval($data['pid'] ?? input('pid', '0'));
$this->cates = PointsMallGoodsCate::pdata($this->maxLevel, $data, [
'id' => '0', 'pid' => '-1', 'name' => '顶部分类',
]);
}
}
/**
* 修改商品分类状态
* @auth true
*/
public function state()
{
PointsMallGoodsCate::mSave($this->_vali([
'status.in:0,1' => '状态值范围异常!',
'status.require' => '状态值不能为空!',
]));
}
/**
* 删除商品分类
* @auth true
*/
public function remove()
{
PointsMallGoodsCate::mDelete();
}
/**
* 商品分类选择器
* @login true
* @return void
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function select()
{
$this->get['status'] = 1;
$this->get['deleted'] = 0;
$this->index();
}
}

View File

@ -0,0 +1,138 @@
<?php
namespace plugin\points_mall\controller;
use plugin\account\model\PluginAccountUser;
use plugin\points_mall\model\PointsMallOrder;
use plugin\points_mall\service\UserOrderService;
use think\admin\Controller;
use think\admin\extend\CodeExtend;
use think\admin\helper\QueryHelper;
use think\exception\HttpResponseException;
class Order extends Controller
{
/**
* 订单数据管理
* @auth true
* @menu true
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function index()
{
$this->type = trim($this->get['type'] ?? 'ta', 't');
PointsMallOrder::mQuery()->layTable(function (QueryHelper $query) {
$this->title = '订单数据管理';
$this->total = ['t0' => 0, 't1' => 0, 't2' => 0, 't3' => 0, 't4' => 0, 't5' => 0, 't6' => 0, 't7' => 0, 'ta' => 0];
$this->types = ['ta' => '全部订单', 't2' => '待支付', 't3' => '待审核', 't4' => '待发货', 't5' => '已发货', 't6' => '已收货', 't7' => '已评论', 't0' => '已取消'];
foreach ($query->db()->field('status,count(1) total')->group('status')->cursor() as $vo) {
[$this->total["t{$vo['status']}"] = $vo['total'], $this->total['ta'] += $vo['total']];
}
}, function (QueryHelper $query) {
$query->with(['user', 'from', 'items', 'address']);
$query->equal('status,refund_status')->like('order_no');
$query->dateBetween('create_time,payment_time,cancel_time,delivery_type');
// 用户搜索查询
$db = PluginAccountUser::mQuery()->like('phone|nickname#user_keys')->db();
if ($db->getOptions('where')) $query->whereRaw("unid in {$db->field('id')->buildSql()}");
// 列表选项卡
if (is_numeric($this->type)) {
$query->where(['status' => $this->type]);
}
// 分页排序处理
$query->where(['deleted_status' => 0]);
});
}
/**
* 单据凭证支付审核
* @auth true
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function audit()
{
if ($this->request->isGet()) {
PointsMallOrder::mForm('', 'order_no');
} else {
$data = $this->_vali([
'order_no.require' => '订单单号不能为空!',
'status.in:0,1' => '审核状态数值异常!',
'status.require' => '审核状态不能为空!',
'remark.default' => '',
]);
if (empty($data['status'])) {
$data['status'] = 0;
$data['cancel_status'] = 1;
$data['cancel_remark'] = $data['remark'] ?: '后台审核驳回并取消订单';
$data['cancel_time'] = date('Y-m-d H:i:s');
} else {
$data['status'] = 4;
$data['payment_code'] = CodeExtend::uniqidDate(16, 'T');
$data['payment_time'] = date('Y-m-d H:i:s');
$data['payment_status'] = 1;
$data['payment_remark'] = $data['remark'] ?: '后台审核支付凭证通过';
}
$order = PointsMallOrder::mk()->where(['order_no' => $data['order_no']])->findOrEmpty();
if ($order->isEmpty() || $order['status'] !== 3) $this->error('不允许操作审核!');
// 无需发货时的处理
if ($data['status'] === 4 && empty($order['delivery_type'])) $data['status'] = 6;
// 更新订单支付状态
$map = ['status' => 3, 'order_no' => $data['order_no']];
if (PointsMallOrder::mk()->strict(false)->where($map)->update($data) !== false) {
if (in_array($data['status'], [4, 5, 6])) {
$this->app->event->trigger('PluginPaymentSuccess', $data);
$this->success('订单审核通过成功!');
} else {
$this->app->event->trigger('PluginWemallOrderCancel', $order);
UserOrderService::stock($data['order_no']);
$this->success('审核驳回并取消成功!');
}
} else {
$this->error('订单审核失败!');
}
}
}
/**
* 取消未支付的订单
* @auth true
* @return void
*/
public function cancel()
{
$data = $this->_vali(['order_no.require' => '订单号不能为空!']);
$order = PointsMallOrder::mk()->where($data)->findOrEmpty();
if ($order->isEmpty()) $this->error('订单查询异常!');
try {
if (!in_array($order['status'], [1, 2, 3])) {
$this->error('订单不能取消!');
}
$result = $order->save([
'status' => 0,
'cancel_status' => 1,
'cancel_remark' => '后台取消未支付的订单',
'cancel_time' => date('Y-m-d H:i:s'),
]);
if ($result !== false) {
UserOrderService::stock($order['order_no']);
$this->app->event->trigger('PluginWemallOrderCancel', $order);
$this->success('取消未支付的订单成功!');
} else {
$this->error('取消支付的订单失败!');
}
} catch (HttpResponseException $exception) {
throw $exception;
} catch (\Exception $exception) {
$this->error($exception->getMessage());
}
}
}

View File

@ -7,6 +7,7 @@ use think\exception\HttpResponseException;
class Auth extends AuthController
{
protected $checkBind = false;
/**
* 控制器初始化
* @return void
@ -15,7 +16,7 @@ class Auth extends AuthController
{
try {
parent::initialize();
$this->checkUserStatus(false);
$this->checkUserStatus($this->checkBind);
} catch (HttpResponseException $exception) {
throw $exception;
} catch (\Exception $exception) {

View File

@ -0,0 +1,95 @@
<?php
namespace plugin\points_mall\controller\api;
use plugin\points_mall\model\PointsMallGoods;
use plugin\points_mall\model\PointsMallGoodsCate;
use plugin\points_mall\model\PointsMallGoodsItem;
use think\admin\Controller;
use think\admin\extend\CodeExtend;
use think\admin\helper\QueryHelper;
use think\db\Query;
/**
* 商品数据管理
* @class Goods
* @package plugin\points_mall\controller\api
*/
class Goods extends Controller
{
/**
* 获取商品列表或详情
* @return void
*/
public function get()
{
$this->cnames = null;
PointsMallGoods::mQuery(null, function (QueryHelper $query) {
// 显示分类显示
if (!empty($vCates = input('cates'))) {
$cates = array_filter(PointsMallGoodsCate::items(), function ($v) use ($vCates) {
return $v['id'] == $vCates;
});
$this->cnames = null;
if (count($cates) > 0) {
$cate = array_pop($cates);
$this->cnames = array_combine($cate['ids'], $cate['names']);
}
}
$query->equal('code')->like('name#keys')->like('marks,cates', ',');
if (!empty($code = input('code'))) {
// 查询单个商品详情
$query->with(['discount', 'items', 'comments' => function (Query $query) {
$query->limit(2)->where(['status' => 1, 'deleted' => 0]);
}])->withCount(['comments' => function (Query $query) {
$query->where(['status' => 1, 'deleted' => 0]);
}]);
PointsMallGoods::mk()->where(['code' => $code])->inc('num_read')->update([]);
} else {
$query->with('discount')->withoutField('content');
}
// 数据排序处理
$sort = intval(input('sort', 0));
$type = intval(input('order', 0)) ? 'asc' : 'desc';
if ($sort === 1) {
$query->order("num_read {$type},sort {$type},id {$type}");
} elseif ($sort === 2) {
$query->order("price_selling {$type},sort {$type},id {$type}");
} else {
$query->order("sort {$type},id {$type}");
}
$query->where(['status' => 1, 'deleted' => 0]);
// 查询数据分页
$page = intval(input('page', 1));
$limit = max(min(intval(input('limit', 20)), 60), 1);
$this->success('获取商品数据', $query->page($page, false, false, $limit));
});
}
/**
* 数据结果处理
* @param array $data
* @param array $result
* @return void
*/
protected function _get_page_filter(array &$data, array &$result)
{
$result['cnames'] = $this->cnames ?? null;
}
/**
* 获取商品分类及标签
* @return void
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function cate()
{
$this->success('获取分类成功', [
'cate' => PointsMallGoodsCate::dtree(),
]);
}
}

View File

@ -0,0 +1,67 @@
<?php
namespace plugin\points_mall\controller\api\auth;
use plugin\points_mall\controller\api\Auth;
use plugin\points_mall\model\PointsMallGoods;
use plugin\points_mall\model\PointsMallGoodsItem;
use plugin\points_mall\model\PointsMallOrderCart;
use plugin\points_mall\service\UserActionService;
use think\admin\helper\QueryHelper;
use think\db\Query;
class Cart extends Auth
{
/**
* 获取购买车数据
* @return void
*/
public function get()
{
PointsMallOrderCart::mQuery(null, function (QueryHelper $query) {
$query->equal('ghash')->where(['unid' => $this->unid])->with([
'goods' => static function (Query $query) {
$query->with('items');
},
'specs' => static function (Query $query) {
$query->withoutField('id,create_time,update_time');
},
]);
$this->success('获取购买车数据!', $query->order('id desc')->page(false, false));
});
}
/**
* 修改购买车数据
* @return void
* @throws \think\db\exception\DbException
*/
public function set()
{
$data = $this->_vali([
'unid.value' => $this->unid,
'ghash.require' => '商品不能为空!',
'number.require' => '数量不能为空!',
]);
// 清理数量0的记录
$map = ['unid' => $this->unid, 'ghash' => $data['ghash']];
if ($data['number'] < 1) {
PointsMallOrderCart::mk()->where($map)->delete();
UserActionService::recount($this->unid);
$this->success('移除成功!');
}
// 检查商品是否存在
$gspec = PointsMallGoodsItem::mk()->where(['ghash' => $data['ghash']])->findOrEmpty();
$goods = PointsMallGoods::mk()->where(['code' => $gspec->getAttr('gcode')])->findOrEmpty();
if ($goods->isEmpty() || $gspec->isEmpty()) $this->error('商品不存在!');
// 保存商品数据
$data += ['gcode' => $gspec['gcode'], 'gspec' => $gspec['gspec']];
if (($cart = PointsMallOrderCart::mk()->where($map)->findOrEmpty())->save($data)) {
UserActionService::recount($this->unid);
$this->success('保存成功!', $cart->refresh()->toArray());
} else {
$this->error('保存失败!');
}
}
}

View File

@ -0,0 +1,509 @@
<?php
namespace plugin\points_mall\controller\api\auth;
use plugin\points_mall\controller\api\Auth;
use plugin\points_mall\model\PointsMallAddress;
use plugin\points_mall\model\PointsMallOrder;
use plugin\points_mall\model\PointsMallOrderCart;
use plugin\points_mall\model\PointsMallOrderItem;
use plugin\points_mall\service\GoodsService;
use plugin\points_mall\service\UserActionService;
use plugin\points_mall\service\UserOrderService;
use plugin\points_mall\service\UserPointService;
use think\admin\extend\CodeExtend;
use think\admin\helper\QueryHelper;
use think\db\Query;
use think\exception\HttpResponseException;
class Order extends Auth
{
protected $checkBind = true;
/**
* 获取订单数据
* @return void
*/
public function get()
{
PointsMallOrder::mQuery(null, function (QueryHelper $query) {
if (empty(input('order_no'))) {
$query->with('items')->where(['refund_status' => 0]);
} else {
$query->with(['items', 'address', 'sender', 'payments' => function (Query $query) {
$query->where(static function (Query $query) {
// $query->whereOr(['channel_type' => Payment::VOUCHER, 'payment_status' => 1, 'audit_status' => 1]);
$query->whereOr(['payment_status' => 1, 'audit_status' => 1]);
});
}]);
}
$query->in('status')->equal('order_no');
$query->where(['unid' => $this->unid, 'deleted_status' => 0])->order('id desc');
$this->success('获取订单成功!', $query->page(intval(input('page')), false, false, 10));
});
}
/**
* 创建订单数据
* @return void
*/
public function add()
{
try {
// 请求参数检查
$input = $this->_vali([
'carts.default' => '',
'rules.default' => '',
'agent.default' => '0',
]);
if (empty($input['rules']) && empty($input['carts'])) $this->error('参数无效!');
// 生成统一编号
do $extra = ['order_no' => $order['order_no'] = CodeExtend::uniqidNumber(16, 'N')];
while (PointsMallOrder::mk()->master()->where($extra)->findOrEmpty()->isExists());
[$items, $deliveryType] = [[], 0];
// 组装订单数据
foreach (GoodsService::parse($this->unid, trim($input['rules'], ':;'), $input['carts']) as $item) {
if (empty($item['count'])) continue;
if (empty($item['goods']) || empty($item['specs'])) $this->error('商品无效!');
[$goods, $gspec, $count] = [$item['goods'], $item['specs'], intval($item['count'])];
// 订单物流类型
if (empty($deliveryType) && $goods['delivery_code'] !== 'NONE') $deliveryType = 1;
// 限制购买数量
if (isset($goods['limit_maxnum']) && $goods['limit_maxnum'] > 0) {
$join = [PointsMallOrderItem::mk()->getTable() => 'b'];
$where = [['a.unid', '=', $this->unid], ['a.status', '>', 1], ['b.gcode', '=', $goods['code']]];
$buyCount = PointsMallOrder::mk()->alias('a')->join($join, 'a.order_no=b.order_no')->where($where)->sum('b.stock_sales');
if ($buyCount + $count > $goods['limit_maxnum']) $this->error('商品限购!');
}
// 限制购买身份
// if ($goods['limit_lowvip'] > $this->levelCode) $this->error('等级不够!');
// 商品库存检查
if ($gspec['stock_sales'] + $count > $gspec['stock_total']) $this->error('库存不足!');
// 订单详情处理
$items[] = [
'unid' => $order['unid'],
'order_no' => $order['order_no'],
// 商品字段
'gsku' => $gspec['gsku'],
'gname' => $goods['name'],
'gcode' => $gspec['gcode'],
'ghash' => $gspec['ghash'],
'gspec' => $gspec['gspec'],
'gunit' => $gspec['gunit'],
'gcover' => empty($gspec['gimage']) ? $goods['cover'] : $gspec['gimage'],
// 库存数量处理
'stock_sales' => $count,
// 快递发货数据
'delivery_code' => $goods['delivery_code'],
'delivery_count' => $goods['rebate_type'] > 0 ? $gspec['number_express'] * $count : 0,
// 商品费用字段
'price_cost' => $gspec['price_cost'],
'price_market' => $gspec['price_market'],
'price_selling' => $gspec['price_selling'],
// 商品费用统计
'total_price_cost' => $gspec['price_cost'] * $count,
'total_price_market' => $gspec['price_market'] * $count,
'total_price_selling' => $gspec['price_selling'] * $count,
'total_allow_balance' => $gspec['allow_balance'] * $count,
'total_allow_integral' => $gspec['allow_integral'] * $count,
'total_reward_balance' => $gspec['reward_balance'] * $count,
'total_reward_integral' => $gspec['reward_integral'] * $count,
];
}
// 默认使用销售销售
$order['rebate_amount'] = array_sum(array_column($items, 'rebate_amount'));
$order['allow_balance'] = array_sum(array_column($items, 'total_allow_balance'));
$order['allow_integral'] = array_sum(array_column($items, 'total_allow_integral'));
$order['reward_balance'] = array_sum(array_column($items, 'total_reward_balance'));
$order['reward_integral'] = array_sum(array_column($items, 'total_reward_integral'));
// 会员及代理升级
$order['level_agent'] = intval(max(array_column($items, 'level_agent')));
$order['level_member'] = intval(max(array_column($items, 'level_upgrade')));
// 订单发货类型
$order['status'] = $deliveryType ? 1 : 2;
$order['delivery_type'] = $deliveryType;
$order['ratio_integral'] = 100;
// 统计商品数量
$order['number_goods'] = array_sum(array_column($items, 'stock_sales'));
$order['number_express'] = array_sum(array_column($items, 'delivery_count'));
// 统计商品金额
$order['amount_cost'] = array_sum(array_column($items, 'total_price_cost'));
$order['amount_goods'] = array_sum(array_column($items, 'total_price_selling'));
// 折扣后的金额
$order['amount_discount'] = array_sum(array_column($items, 'discount_amount'));
$order['amount_reduct'] = $order['amount_goods'];
// 统计订单金额
$order['amount_real'] = round($order['amount_discount'] - $order['amount_reduct'], 2);
$order['amount_total'] = $order['amount_goods'];
$order['amount_profit'] = round($order['amount_real'] - $order['amount_cost']);
// 写入商品数据
$model = PointsMallOrder::mk();
$this->app->db->transaction(function () use ($order, $items, &$model) {
$model->save($order) && PointsMallOrderItem::mk()->saveAll($items);
// 设置收货地址
if ($order['delivery_type']) {
$where = ['unid' => $this->unid, 'deleted' => 0];
$address = PointsMallAddress::mk()->where($where)->order('type desc,id desc')->findOrEmpty();
$address->isExists() && UserOrderService::perfect($model->refresh(), $address);
}
});
// 同步库存销量
foreach (array_unique(array_column($items, 'gcode')) as $gcode) {
GoodsService::stock($gcode);
}
// 清理购物车数据
if (count($carts = str2arr($input['carts'])) > 0) {
PointsMallOrderCart::mk()->whereIn('id', $carts)->delete();
UserActionService::recount($this->unid);
}
// 触发订单创建事件
$this->app->event->trigger('PluginWemallOrderCreate', $order);
// 无需发货且无需支付,直接完成支付流程
if ($order['status'] === 2 && empty($order['amount_real'])) {
$this->success('下单成功!', $model->toArray());
}
// 返回处理成功数据
$this->success('下单成功!', array_merge($order, ['items' => $items]));
} catch (HttpResponseException $exception) {
throw $exception;
} catch (\Exception $exception) {
trace_file($exception);
$this->error("下单失败,{$exception->getMessage()}");
}
}
/**
* 模拟计算运费
* @return void
* @throws \think\admin\Exception
*/
// public function express()
// {
// $data = $this->_vali([
// 'unid.value' => $this->unid,
// 'order_no.require' => '单号不能为空!',
// 'address_id.require' => '地址不能为空!',
// ]);
//
// // 用户收货地址
// $map = ['unid' => $this->unid, 'id' => $data['address_id']];
// $addr = PointsMallAddress::mk()->where($map)->findOrEmpty();
// if ($addr->isEmpty()) $this->error('地址异常!');
//
// // 订单状态检查
// $map = ['unid' => $this->unid, 'order_no' => $data['order_no']];
// $tCount = intval(PointsMallOrderItem::mk()->where($map)->sum('delivery_count'));
//
// // 根据地址计算运费
// $map = ['status' => 1, 'deleted' => 0, 'order_no' => $data['order_no']];
// $tCode = PointsMallOrderItem::mk()->where($map)->column('delivery_code');
// [$amount, , , $remark] = ExpressService::amount($tCode, $addr['region_prov'], $addr['region_city'], $tCount);
// $this->success('计算运费!', ['amount' => $amount, 'remark' => $remark]);
// }
/**
* 确认收货地址
* @return void
* @throws \think\admin\Exception
*/
public function perfect()
{
$data = $this->_vali([
'unid.value' => $this->unid,
'order_no.require' => '单号不能为空',
'address_id.require' => '地址不能为空',
]);
// 用户收货地址
$where = ['id' => $data['address_id'], 'unid' => $this->unid, 'deleted' => 0];
$address = PointsMallAddress::mk()->where($where)->findOrEmpty();
if ($address->isEmpty()) $this->error('地址异常!');
// 订单状态检查
$where = ['unid' => $this->unid, 'order_no' => $data['order_no'], 'delivery_type' => 1];
$order = PointsMallOrder::mk()->where($where)->whereIn('status', [1, 2])->findOrEmpty();
if ($order->isEmpty()) $this->error('不能修改!');
// 更新订单收货地址
if (UserOrderService::perfect($order, $address)) {
$this->success('确认成功!', $order->refresh()->toArray());
} else {
$this->error('确认失败!');
}
}
/**
* 获取支付通道
* @return void
* @throws \think\admin\Exception
*/
// public function channel()
// {
// $this->success('获取支付通道!', [
// 'toratio' => Integral::ratio(),
// 'channels' => Payment::typesByAccess($this->type, true),
// ]);
// }
/**
* 获取支付参数
* @return void
*/
public function payment()
{
$data = $this->_vali([
'unid.value' => $this->unid,
'balance.default' => '0.00',
'integral.default' => '0',
'order_no.require' => '单号不能为空',
'order_ps.default' => '',
'coupon_code.default' => '', # 优惠券编号
'channel_code.require' => '支付不能为空',
'payment_back.default' => '', # 支付回跳地址
'payment_image.default' => '', # 支付凭证图片
]);
try {
$order = $this->getOrderModel();
$status = intval($order->getAttr('status'));
if ($status > 3) $this->success('已完成支付!');
if ($status === 3) $this->error('凭证待审核!');
if ($status !== 2) $this->error('不能发起支付!');
// 订单备注内容更新
empty($data['order_ps']) || $order->save(['order_ps' => $data['order_ps']]);
// 无需支付,直接完成订单
if (floatval($orderAmount = $order->getAttr('amount_real')) <= 0) {
$order->save(['status' => 4]);
$this->success('已支付成功!', []);
}
$leaveAmount = $orderAmount;
// 剩余支付金额
// if (($leaveAmount = Payment::leaveAmount($data['order_no'], $orderAmount)) <= 0) {
// $this->success('已完成支付!', PaymentResponse::mk(true, '已完成支付!')->toArray());
// }
// 扣除优惠券
// if (!empty($data['coupon_code']) && $data['coupon_code'] !== $order->getAttr('coupon_code')) try {
// // 检查优惠券是否有效
//// $where = ['unid' => $this->unid, 'status' => 1, 'deleted' => 0];
//// $coupon = PluginWemallUserCoupon::mk()->where($where)->with('bindCoupon')->findOrEmpty();
//// if ($coupon->isEmpty() || empty($coupon->getAttr('coupon_status')) || $coupon->getAttr('coupon_deleted') > 0) {
//// $this->error('无限优惠券!');
//// }
//// if ($coupon->getAttr('expire') > 0 && $coupon->getAttr('expire') < time()) $this->error('优惠券无效!');
//// if (floatval($coupon->getAttr('limit_amount')) <= $orderAmount) $this->error('未达到使用条件!');
//// [$couponCode, $couponAmount] = [strval($coupon->getAttr('code')), strval($coupon->getAttr('coupon_amount'))];
//// $response = Payment::mk(Payment::COUPON)->create($this->account, $data['order_no'], '优惠券抵扣', $orderAmount, $couponAmount, '', '', '', $couponCode);
//// $order->save(['coupon_code' => $couponCode, 'coupon_amount' => $couponAmount]);
//// $coupon->save(['used' => 1, 'status' => 2, 'used_time' => date('Y-m-d H:i:s')]);
//// if (($leaveAmount = Payment::leaveAmount($data['order_no'], $orderAmount)) <= 0) $this->success('已完成支付!', $response->toArray());
// } catch (HttpResponseException $exception) {
// throw $exception;
// } catch (\Exception $exception) {
// $this->error($exception->getMessage());
// }
// 积分抵扣处理
// if ($leaveAmount > 0 && $data['integral'] > 0) {
// if ($data['integral'] > $order->getAttr('allow_integral')) $this->error("超出积分抵扣!");
// if ($data['integral'] > Integral::recount($this->unid)['usable']) $this->error('账号积分不足!');
// $response = Payment::mk(payment::INTEGRAL)->create($this->account, $data['order_no'], '账号积分抵扣', $orderAmount, $data['integral']);
// if (($leaveAmount = Payment::leaveAmount($data['order_no'], $orderAmount)) <= 0) $this->success('已完成支付!', $response->toArray());
// }
$userPoint = UserPointService::getUserPoint($this->unid);
if ($userPoint['point'] < $leaveAmount) {
$this->error("积分不足!");
} else {
$order->save(['status' => 4]);
UserPointService::addUserPoint($this->unid, -$leaveAmount, '积分购买商品抵扣');
$this->success('积分抵扣成功!', []);
}
// 余额支付扣减
// if ($leaveAmount > 0 && $data['balance'] > 0) {
// if ($data['balance'] > $order->getAttr('allow_balance')) $this->error("超出余额限额!");
// if ($data['balance'] > Balance::recount($this->unid)['usable']) $this->error('账号余额不足!');
// $response = Payment::mk(Payment::BALANCE)->create($this->account, $data['order_no'], '账号余额支付!', $orderAmount, $data['balance']);
// if (($leaveAmount = Payment::leaveAmount($data['order_no'], $orderAmount)) <= 0) $this->success('已完成支付!', $response->toArray());
// }
// 凭证图片保存
// if (!empty($data['payment_image'])) {
// $data['payment_image'] = Storage::saveImage($data['payment_image'])['url'] ?? '';
// }
// 创建支付订单
// $response = Payment::mk($data['channel_code'])->create(
// $this->account, $data['order_no'], '商城订单支付',
// $orderAmount, strval($leaveAmount), '', $data['payment_back'], $data['payment_image']
// );
// 标准化返回结果
// if ($response->status) {
// $this->success($response->message, $response->toArray());
// } else {
// $this->error($response->message, $response->toArray());
// }
} catch (HttpResponseException $exception) {
throw $exception;
} catch (\Exception $exception) {
trace_file($exception);
$this->error($exception->getMessage());
}
}
/**
* 取消未支付订单
* @return void
* @throws \think\admin\Exception
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function cancel()
{
$order = $this->getOrderModel();
if (in_array($order->getAttr('status'), [1, 2, 3])) {
$data = [
'status' => 0,
'cancel_time' => date('Y-m-d H:i:s'),
'cancel_status' => 1,
'cancel_remark' => '用户主动取消订单!',
];
if ($order->save($data) && UserOrderService::stock($order->getAttr('order_no'))) {
// 触发订单取消事件
Payment::refund($order->getAttr('order_no'));
$this->app->event->trigger('PluginWemallOrderCancel', $order);
// 返回处理成功数据
$this->success('取消成功!');
} else {
$this->error('取消失败!');
}
} else {
$this->error('不可取消!');
}
}
/**
* 删除已取消订单
* @return void
*/
public function remove()
{
$order = $this->getOrderModel();
if ($order->isEmpty()) $this->error('读取订单失败!');
if ($order->getAttr('status') == 0) {
if ($order->save([
'status' => 0,
'deleted_time' => date('Y-m-d H:i:s'),
'deleted_status' => 1,
'deleted_remark' => '用户主动删除订单!',
])) {
// 触发订单删除事件
$this->app->event->trigger('PluginWemallOrderRemove', $order);
// 返回处理成功数据
$this->success('删除成功!');
} else {
$this->error('删除失败!');
}
} else {
$this->error('不可删除!');
}
}
/**
* 订单确认收货
* @return void
*/
public function confirm()
{
$order = $this->getOrderModel();
if (in_array($order->getAttr('status'), [5, 6])) {
// 触发订单确认事件
$order->save([
'status' => 6,
'confirm_time' => date('Y-m-d H:i:s'),
'confirm_remark' => '用户主动确认签收订单!',
]);
$this->app->event->trigger('PluginWemallOrderConfirm', $order);
// 返回处理成功数据
$this->success('确认成功!');
} else {
$this->error('确认失败!');
}
}
/**
* 提交订单评论
* @return void
*/
// public function comment()
// {
// $order = $this->getOrderModel();
// if (in_array($order->getAttr('status'), [6, 7])) try {
// // 接收评论数据
// $items = json_decode($this->request->post('data'), true);
// $this->app->db->transaction(function () use ($order, $items) {
// $order->save(['status' => 7]);
// $order->items()->select()->map(function (PointsMallOrderItem $item) use ($items) {
// if (!empty($input = $items[$item->getAttr('ghash')])) {
// UserAction::comment($item, $input['rate'], $input['content'], $input['images']);
// }
// });
// });
// $this->success('评论成功!');
// } catch (HttpResponseException $exception) {
// throw $exception;
// } catch (\Exception $exception) {
// $this->error($exception->getMessage());
// } else {
// $this->error('无需评论!');
// }
// }
/**
* 订单状态统计
* @return void
*/
public function total()
{
$data = ['t0' => 0, 't1' => 0, 't2' => 0, 't3' => 0, 't4' => 0, 't5' => 0, 't6' => 0, 't7' => 0];
$query = PointsMallOrder::mk()->where(['unid' => $this->unid, 'refund_status' => 0, 'deleted_status' => 0]);
foreach ($query->field('status,count(1) count')->group('status')->cursor() as $item) {
$data["t{$item['status']}"] = $item['count'];
}
$this->success('获取统计!', $data);
}
/**
* 物流追踪查询
* @return void
*/
// public function track()
// {
// try {
// $data = $this->_vali([
// 'code.require' => '快递不能为空', 'number.require' => '单号不能为空'
// ]);
// $result = ExpressService::query($data['code'], $data['number']);
// empty($result['code']) ? $this->error($result['info']) : $this->success('快递追踪!', $result);
// } catch (HttpResponseException $exception) {
// throw $exception;
// } catch (\Exception $exception) {
// $this->error($exception->getMessage());
// }
// }
/**
* 获取输入订单模型
* @return PointsMallOrder
*/
private function getOrderModel(): PointsMallOrder
{
$map = $this->_vali(['unid.value' => $this->unid, 'order_no.require' => '单号不能为空']);
$order = PointsMallOrder::mk()->where($map)->findOrEmpty();
if ($order->isEmpty()) $this->error('读取订单失败!');
return $order;
}
}

View File

@ -0,0 +1,46 @@
<?php
// +----------------------------------------------------------------------
// | WeMall Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2022~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// | 会员免费 ( https://thinkadmin.top/vip-introduce )
// +----------------------------------------------------------------------
// | gitee 代码仓库:https://gitee.com/zoujingli/think-plugs-wemall
// | github 代码仓库:https://github.com/zoujingli/think-plugs-wemall
// +----------------------------------------------------------------------
declare (strict_types=1);
if (!function_exists('show_gspec')) {
/**
* 商品规格过滤显示
* @param string $spec 原规格内容
* @return string
*/
function show_gspec(string $spec): string
{
$specs = [];
foreach (explode(';;', $spec) as $sp) {
$specs[] = explode('::', $sp)[1];
}
return join(' ', $specs);
}
}
if (!function_exists('formatdate')) {
/**
* 日期格式过滤
* @param string|null $value
* @return string|null
*/
function formatdate(?string $value): ?string
{
return is_string($value) ? str_replace(['年', '月', '日'], ['-', '-', ''], $value) : $value;
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace plugin\points_mall\model;
use think\admin\Model;
class PointsMallAddress extends Model
{
}

View File

@ -0,0 +1,90 @@
<?php
namespace plugin\points_mall\model;
use think\admin\Model;
class PointsMallGoods extends Model
{
/**
* 日志名称
* @var string
*/
protected $oplogName = '商品';
/**
* 日志类型
* @var string
*/
protected $oplogType = '分销商城管理';
/**
* 关联产品规格
* @return \think\model\relation\HasMany
*/
public function items()
{
return static::mk()
->hasMany(PointsMallGoodsItem::class, 'gcode', 'code')
->withoutField('id,status,create_time,update_time')
->where(['status' => 1]);
}
/**
* 关联商品评论数据
* @return \think\model\relation\HasMany
*/
public function comments()
{
return $this->hasMany(PluginWemallUserActionComment::class, 'gcode', 'code')->with('bindUser');
}
/**
* 关联产品列表
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public static function lists(): array
{
$model = static::mk()->with('items')->withoutField('specs');
return $model->order('sort desc,id desc')->where(['deleted' => 0])->select()->toArray();
}
/**
* 处理商品分类数据
* @param mixed $value
* @return array
*/
public function getCatesAttr($value): array
{
$ckey = 'PointsMallGoodsCateItem';
$cates = sysvar($ckey) ?: sysvar($ckey, PointsMallGoodsCate::items(true));
$cateids = is_string($value) ? str2arr($value) : (array)$value;
foreach ($cates as $cate) if (in_array($cate['id'], $cateids)) return $cate;
return [];
}
/**
* 设置轮播图片
* @param mixed $value
* @return string
*/
public function setSliderAttr($value): string
{
return is_string($value) ? $value : (is_array($value) ? arr2str($value) : '');
}
/**
* 获取轮播图片
* @param mixed $value
* @return array
*/
public function getSliderAttr($value): array
{
return is_string($value) ? str2arr($value, '|') : [];
}
}

View File

@ -0,0 +1,75 @@
<?php
namespace plugin\points_mall\model;
use think\admin\extend\DataExtend;
use think\admin\Model;
class PointsMallGoodsCate extends Model
{
/**
* 获取上级可用选项
* @param int $max 最大级别
* @param array $data 表单数据
* @param array $parent 上级分类
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public static function pdata(int $max, array &$data, array $parent = []): array
{
$items = static::mk()->where(['deleted' => 0])->order('sort desc,id asc')->select()->toArray();
$cates = DataExtend::arr2table(empty($parent) ? $items : array_merge([$parent], $items));
if (isset($data['id'])) foreach ($cates as $cate) if ($cate['id'] === $data['id']) $data = $cate;
foreach ($cates as $key => $cate) {
$isSelf = isset($data['spt']) && isset($data['spc']) && $data['spt'] <= $cate['spt'] && $data['spc'] > 0;
if ($cate['spt'] >= $max || $isSelf) unset($cates[$key]);
}
return $cates;
}
/**
* 获取数据树
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public static function dtree(): array
{
$query = static::mk()->where(['status' => 1, 'deleted' => 0])->order('sort desc,id desc');
return DataExtend::arr2tree($query->withoutField('sort,status,deleted,create_time')->select()->toArray());
}
/**
* 获取列表数据
* @param bool $simple 仅子级别
* @return array
*/
public static function items(bool $simple = false): array
{
$query = static::mk()->where(['status' => 1, 'deleted' => 0])->order('sort desc,id asc');
$cates = array_column(DataExtend::arr2table($query->column('id,pid,name', 'id')), null, 'id');
foreach ($cates as $cate) isset($cates[$cate['pid']]) && $cates[$cate['id']]['parent'] =& $cates[$cate['pid']];
foreach ($cates as $key => $cate) {
$id = $cate['id'];
$cates[$id]['ids'][] = $cate['id'];
$cates[$id]['names'][] = $cate['name'];
while (isset($cate['parent']) && ($cate = $cate['parent'])) {
$cates[$id]['ids'][] = $cate['id'];
$cates[$id]['names'][] = $cate['name'];
}
$cates[$id]['ids'] = array_reverse($cates[$id]['ids']);
$cates[$id]['names'] = array_reverse($cates[$id]['names']);
if (isset($pky) && $simple && in_array($cates[$pky]['name'], $cates[$id]['names'])) {
unset($cates[$pky]);
}
$pky = $key;
}
foreach ($cates as &$cate) {
unset($cate['sps'], $cate['parent']);
}
return array_values($cates);
}
}

View File

@ -0,0 +1,67 @@
<?php
namespace plugin\points_mall\model;
use think\admin\Model;
class PointsMallGoodsItem extends Model
{
/**
* 关联商品信息
* @return \think\model\relation\HasOne
*/
public function goods()
{
return $this->hasOne(PointsMallGoods::class, 'code', 'gcode');
}
/**
* 绑定商品信息
* @return \think\model\relation\HasOne
*/
public function bindGoods()
{
return $this->goods()->bind([
'gname' => 'name',
'gcover' => 'cover',
'gstatus' => 'status',
'gdeleted' => 'deleted'
]);
}
/**
* 获取商品规格JSON数据
* @param string $code
* @return string
*/
public static function itemsJson(string $code): string
{
return json_encode(self::itemsArray($code), 64 | 256);
}
/**
* 获取商品规格数组
* @param string $code
* @return array
*/
public static function itemsArray(string $code): array
{
return self::mk()->where(['gcode' => $code])->column([
'gsku' => 'gsku',
'ghash' => 'hash',
'gspec' => 'spec',
'gcode' => 'gcode',
'gimage' => 'image',
'status' => 'status',
'price_cost' => 'cost',
'price_market' => 'market',
'price_selling' => 'selling',
'allow_balance' => 'allow_balance',
'allow_integral' => 'allow_integral',
'number_virtual' => 'virtual',
'number_express' => 'express',
'reward_balance' => 'balance',
'reward_integral' => 'integral',
], 'ghash');
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace plugin\points_mall\model;
use think\admin\Model;
class PointsMallGoodsStock extends Model
{
}

View File

@ -0,0 +1,97 @@
<?php
namespace plugin\points_mall\model;
use plugin\account\model\PluginAccountUser;
use think\admin\Model;
class PointsMallOrder extends Model
{
/**
* 关联推荐用户
* @return \think\model\relation\HasOne
*/
public function from()
{
return $this->hasOne(PluginAccountUser::class, 'id', 'puid1');
}
/**
* 关联商品数据
* @return \think\model\relation\HasMany
*/
public function items()
{
return $this->hasMany(PointsMallOrderItem::class, 'order_no', 'order_no');
}
//
// /**
// * 关联支付数据
// * @return \think\model\relation\HasOne
// */
// public function payment()
// {
// return $this->hasOne(PluginPaymentRecord::class, 'order_no', 'order_no')->where([
// 'payment_status' => 1,
// ]);
// }
//
// /**
// * 关联支付记录
// * @return \think\model\relation\HasMany
// */
// public function payments()
// {
// return $this->hasMany(PluginPaymentRecord::class, 'order_no', 'order_no')->order('id desc')->withoutField('payment_notify');
// }
//
// /**
// * 关联收货地址
// * @return \think\model\relation\HasOne
// */
// public function address()
// {
// return $this->hasOne(PluginWemallOrderSender::class, 'order_no', 'order_no');
// }
/**
* 格式化支付通道
* @param mixed $value
* @return array
*/
public function getPaymentAllowsAttr($value): array
{
$payments = is_string($value) ? str2arr($value) : [];
return in_array('all', $payments) ? ['all'] : $payments;
}
/**
* 时间格式处理
* @param mixed $value
* @return string
*/
public function getPaymentTimeAttr($value): string
{
return $this->getCreateTimeAttr($value);
}
/**
* 时间格式处理
* @param mixed $value
* @return string
*/
public function setPaymentTimeAttr($value): string
{
return $this->setCreateTimeAttr($value);
}
public function setConfirmTimeAttr($value): string
{
return $this->setCreateTimeAttr($value);
}
public function getConfirmTimeAttr($value): string
{
return $this->getCreateTimeAttr($value);
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace plugin\points_mall\model;
use think\admin\Model;
class PointsMallOrderCart extends Model
{
/**
* 关联产品数据
* @return \think\model\relation\HasOne
*/
public function goods()
{
return $this->hasOne(PointsMallGoods::class, 'code', 'gcode');
}
/**
* 关联规格数据
* @return \think\model\relation\HasOne
*/
public function specs()
{
return $this->hasOne(PointsMallGoodsItem::class, 'ghash', 'ghash');
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace plugin\points_mall\model;
use think\admin\Model;
class PointsMallOrderItem extends Model
{
/**
* 关联订单信息
* @return \think\model\relation\HasOne
*/
public function main()
{
return $this->hasOne(PointsMallOrder::class, 'order_no', 'order_no');
}
/**
* 关联商品信息
* @return \think\model\relation\HasOne
*/
public function goods()
{
return $this->hasOne(PointsMallGoods::class, 'code', 'gcode');
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace plugin\points_mall\model;
use think\admin\Model;
class PointsMallOrderSender extends Model
{
/**
* 关联订单数据
* @return \think\model\relation\HasOne
*/
public function main()
{
return $this->hasOne(PointsMallOrder::class, 'order_no', 'order_no')->with(['items']);
}
/**
* 设置发货时间
* @param mixed $value
* @return string
*/
public function setExpressTimeAttr($value): string
{
return $this->setCreateTimeAttr($value);
}
/**
* 获取发货时间
* @param mixed $value
* @return string
*/
public function getExpressTimeAttr($value): string
{
return $this->getCreateTimeAttr($value);
}
}

View File

@ -0,0 +1,122 @@
<?php
namespace plugin\points_mall\service;
use Exception;
use plugin\points_mall\model\PointsMallGoodsStock;
use plugin\points_mall\model\PointsMallGoods;
use plugin\points_mall\model\PointsMallGoodsItem;
use plugin\points_mall\model\PointsMallOrder;
use plugin\points_mall\model\PointsMallOrderCart;
use plugin\points_mall\model\PointsMallOrderItem;
/**
* 商品数据服务
* @class GoodsService
* @package plugin\points_mall\service
*/
class GoodsService
{
/**
* 更新商品库存数据
* @param string $code
* @return boolean
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public static function stock(string $code): bool
{
// 入库统计
$query = PointsMallGoodsStock::mk()->field('ghash,ifnull(sum(gstock),0) stock_total');
$stockList = $query->where(['gcode' => $code])->group('gcode,ghash')->select()->toArray();
// 销量统计
$query = PointsMallOrder::mk()->alias('a')->field('b.ghash,ifnull(sum(b.stock_sales),0) stock_sales');
$query->join([PointsMallOrderItem::mk()->getTable() => 'b'], 'a.order_no=b.order_no', 'left');
$query->where([['b.gcode', '=', $code], ['a.status', '>', 0], ['a.deleted_status', '=', 0]]);
$salesList = $query->group('b.ghash')->select()->toArray();
// 组装数据
$items = [];
foreach (array_merge($stockList, $salesList) as $vo) {
$key = $vo['ghash'];
$items[$key] = array_merge($items[$key] ?? [], $vo);
if (empty($items[$key]['stock_sales'])) $items[$key]['stock_sales'] = 0;
if (empty($items[$key]['stock_total'])) $items[$key]['stock_total'] = 0;
}
unset($salesList, $stockList);
// 更新商品规格销量及库存
foreach ($items as $hash => $item) {
PointsMallGoodsItem::mk()->where(['ghash' => $hash])->update([
'stock_total' => $item['stock_total'], 'stock_sales' => $item['stock_sales']
]);
}
// 更新商品主体销量及库存
PointsMallGoods::mk()->where(['code' => $code])->update([
'stock_total' => intval(array_sum(array_column($items, 'stock_total'))),
'stock_sales' => intval(array_sum(array_column($items, 'stock_sales'))),
'stock_virtual' => PointsMallGoodsItem::mk()->where(['gcode' => $code])->sum('number_virtual'),
]);
return true;
}
/**
* 解析下单数据
* @param integer $unid 用户编号
* @param string $rules 直接下单
* @param string $carts 购物车下单
* @return array
* @throws \think\admin\Exception
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public static function parse(int $unid, string $rules, string $carts): array
{
// 读取商品数据
[$lines, $carts] = [[], str2arr($carts)];
if (!empty($carts)) {
$where = [['unid', '=', $unid], ['id', 'in', $carts]];
$field = ['ghash' => 'ghash', 'gcode' => 'gcode', 'gspec' => 'gspec', 'number' => 'count'];
PointsMallOrderCart::mk()->field($field)->where($where)->with([
'goods' => function ($query) {
$query->where(['status' => 1, 'deleted' => 0]);
$query->withoutField(['specs', 'content', 'status', 'deleted', 'create_time', 'update_time']);
},
'specs' => function ($query) {
$query->where(['status' => 1]);
$query->withoutField(['status', 'create_time', 'update_time']);
}
])->select()->each(function (Model $model) use (&$lines) {
if (isset($lines[$ghash = $model->getAttr('ghash')])) {
$lines[$ghash]['count'] += $model->getAttr('count');
} else {
$lines[$ghash] = $model->toArray();
}
});
} elseif (!empty($rules)) {
foreach (explode(';', $rules) as $rule) {
[$ghash, $count] = explode(':', "{$rule}:1");
if (isset($lines[$ghash])) {
$lines[$ghash]['count'] += $count;
} else {
$lines[$ghash] = ['ghash' => $ghash, 'gcode' => '', 'gspec' => '', 'count' => $count];
}
}
// 读取规格数据
$map1 = [['status', '=', 1], ['ghash', 'in', array_column($lines, 'ghash')]];
foreach (PointsMallGoodsItem::mk()->where($map1)->select()->toArray() as $item) {
foreach ($lines as &$line) if ($line['ghash'] === $item['ghash']) {
[$line['gcode'], $line['gspec'], $line['specs']] = [$item['gcode'], $item['gspec'], $item];
}
}
// 读取商品数据
$map2 = [['status', '=', 1], ['deleted', '=', 0], ['code', 'in', array_unique(array_column($lines, 'gcode'))]];
foreach (PointsMallGoods::mk()->where($map2)->withoutField(['specs', 'content'])->select()->toArray() as $goods) {
foreach ($lines as &$line) if ($line['gcode'] === $goods['code']) $line['goods'] = $goods;
}
} else {
throw new Exception('无效参数数据!');
}
return array_values($lines);
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace plugin\points_mall\service;
use plugin\account\model\PluginAccountUser;
use plugin\points_mall\model\PointsMallOrderCart;
class UserActionService
{
/**
* 刷新用户行为统计
* @param integer $unid 用户编号
* @param array|null $data 非数组时更新数据
* @return array [collect, history, mycarts]
* @throws \think\db\exception\DbException
*/
public static function recount(int $unid, ?array &$data = null): array
{
$isUpdate = !is_array($data);
if ($isUpdate) $data = [];
// 更新收藏及足迹数量和购物车
$map = ['unid' => $unid];
$data['mycarts_total'] = PointsMallOrderCart::mk()->where($map)->sum('number');
if ($isUpdate && ($user = PluginAccountUser::mk()->findOrEmpty($unid))->isExists()) {
$user->save(['extra' => array_merge($user->getAttr('extra'), $data)]);
}
return [$data['collect_total'], $data['history_total'], $data['mycarts_total']];
}
}

View File

@ -0,0 +1,152 @@
<?php
namespace plugin\points_mall\service;
use Exception;
use plugin\points_mall\model\PointsMallOrder;
use plugin\points_mall\model\PointsMallOrderItem;
use plugin\points_mall\model\PointsMallOrderSender;
use think\admin\Library;
class UserOrderService
{
/**
* 同步订单关联商品的库存
* @param string $orderNo 订单编号
* @return boolean
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public static function stock(string $orderNo): bool
{
$map = ['order_no' => $orderNo];
$codes = PointsMallOrderItem::mk()->where($map)->column('gcode');
foreach (array_unique($codes) as $code) GoodsService::stock($code);
return true;
}
/**
* 获取订单模型
* @param PointsMallOrder|string $order
* @param ?integer $unid 动态绑定变量
* @param ?string $orderNo 动态绑定变量
* @return PointsMallOrder
* @throws \think\admin\Exception
*/
public static function widthOrder($order, ?int &$unid = 0, ?string &$orderNo = '')
{
if (is_string($order)) {
$order = PointsMallOrder::mk()->where(['order_no' => $order])->findOrEmpty();
}
if ($order instanceof PointsMallOrder) {
[$unid, $orderNo] = [intval($order->getAttr('unid')), $order->getAttr('order_no')];
return $order;
}
throw new Exception("无效订单对象!");
}
/**
* 更新订单支付状态
* @param PluginWemallOrder|string $order 订单模型
* @param PluginPaymentRecord $payment 支付行为记录
* @return array|bool|string|void|null
* @throws \think\admin\Exception
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* @remark 订单状态(0已取消,1预订单,2待支付,3待审核,4待发货,5已发货,6已收货,7已评论)
*/
public static function change($order, PluginPaymentRecord $payment)
{
$order = self::widthOrder($order);
if ($order->isEmpty()) return null;
// 同步订单支付统计
$ptotal = Payment::totalPaymentAmount($payment->getAttr('order_no'));
$order->appendData([
'payment_time' => $payment->getAttr('create_time'),
'payment_amount' => $ptotal['amount'] ?? 0,
'amount_payment' => $ptotal['payment'] ?? 0,
'amount_balance' => $ptotal['balance'] ?? 0,
'amount_integral' => $ptotal['integral'] ?? 0,
], true);
// 订单已经支付完成
if ($order->getAttr('payment_amount') >= $order->getAttr('amount_real')) {
// 已完成支付,更新订单状态
$status = $order->getAttr('delivery_type') ? 4 : 5;
$order->save(['status' => $status, 'payment_status' => 1]);
// 确认完成支付,发放余额积分奖励及升级返佣
}
// 退款或部分退款,仅更新订单支付统计
if ($payment->getAttr('refund_status')) {
return $order->save();
}
// 提交支付凭证,只需更新订单状态为【待审核】
$isVoucher = $payment->getAttr('channel_type') === Payment::VOUCHER;
if ($isVoucher && $payment->getAttr('audit_status') === 1) {
return $order->save(['status' => 3, 'payment_status' => 1]);
}
// 凭证支付审核被拒绝,订单回滚到未支付状态
if ($isVoucher && $payment->getAttr('audit_status') === 0) {
if ($order->getAttr('status') === 3) $order->save(['status' => 2]);
} else {
$order->save();
}
}
/**
* 更新订单收货地址
* @param PointsMallOrder $order
* @param $address
* @return boolean
* @throws \think\admin\Exception
*/
public static function perfect(PointsMallOrder $order, $address): bool
{
$unid = $order->getAttr('unid');
$orderNo = $order->getAttr('order_no');
// 创建订单发货信息
$data = [
'delivery_code' => 0, 'delivery_count' => 0, 'unid' => $unid,
'delivery_remark' => '', 'delivery_amount' => 0, 'status' => 1,
];
$data['order_no'] = $orderNo;
$data['address_id'] = $address->getAttr('id');
// 收货人信息
$data['user_name'] = $address->getAttr('user_name');
$data['user_phone'] = $address->getAttr('user_phone');
$data['user_idcode'] = $address->getAttr('idcode');
$data['user_idimg1'] = $address->getAttr('idimg1');
$data['user_idimg2'] = $address->getAttr('idimg2');
// 收货地址信息
$data['region_prov'] = $address->getAttr('region_prov');
$data['region_city'] = $address->getAttr('region_city');
$data['region_area'] = $address->getAttr('region_area');
$data['region_addr'] = $address->getAttr('region_addr');
// 记录原地址信息
$data['extra'] = $data;
PointsMallOrderSender::mk()->where(['order_no' => $orderNo])->findOrEmpty()->save($data);
// 组装更新订单数据, 重新计算订单金额
$update = ['status' => 2, 'amount_express' => $data['delivery_amount']];
$update['amount_real'] = round($order->getAttr('amount_discount') - $order->getAttr('amount_reduct'), 2);
$update['amount_total'] = round($order->getAttr('amount_goods'), 2);
// 支付金额不能少于零
if ($update['amount_real'] <= 0) $update['amount_real'] = 0.00;
if ($update['amount_total'] <= 0) $update['amount_total'] = 0.00;
// 更新用户订单数据
if ($order->save($update)) {
// 触发订单确认事件
Library::$sapp->event->trigger('PluginWemallOrderPerfect', $order);
// 返回处理成功数据
return true;
} else {
return false;
}
}
}

View File

@ -0,0 +1,349 @@
{extend name="main"}
{block name="button"}
<button data-target-submit class='layui-btn layui-btn-sm'>保存数据</button>
<button data-target-backup class="layui-btn layui-btn-sm layui-btn-danger" data-confirm="确认要取消编辑吗?">取消编辑</button>
{/block}
{block name="content"}
{include file='goods/form_style'}
<form action="{:sysuri()}" method="post" data-auto="true" class="layui-form layui-card">
<div class="layui-card-body">
<!--{notempty name='marks'}-->
<div class="layui-form-item">
<span class="help-label label-required-prev"><b>商品标签</b>Goods Mark</span>
<div class="layui-textarea help-checks">
{foreach $marks as $mark}
<label class="think-checkbox">
{if isset($vo.marks) && is_array($vo.marks) && in_array($mark, $vo.marks)}
<input name="marks[]" type="checkbox" value="{$mark}" lay-ignore checked> {$mark}
{else}
<input name="marks[]" type="checkbox" value="{$mark}" lay-ignore> {$mark}
{/if}
</label>
{/foreach}
</div>
</div>
<!--{/notempty}-->
<!--{notempty name='cates'}-->
<div class="layui-form-item block relative">
<span class="help-label label-required-prev"><b>所属分类</b>Category Name</span>
<select class="layui-select" lay-search name="cates">
{foreach $cates as $cate}{if in_array($cate.id, $vo.cates)}
<option selected value="{:arr2str($cate.ids)}">{:join(' > ', $cate.names)}</option>
{else}
<option value="{:arr2str($cate.ids)}">{:join(' > ', $cate.names)}</option>
{/if}{/foreach}
</select>
</div>
<!--{/notempty}-->
<label class="layui-form-item block relative">
<span class="help-label"><b>商品名称</b>Product Name</span>
<input class="layui-input" name="name" placeholder="请输入商品名称" vali-name="商品名称" required value="{$vo.name|default=''}">
</label>
<div class="layui-form-item">
<span class="help-label label-required-prev"><b>商品封面及轮播图片</b>Cover and Pictures</span>
<table class="layui-table">
<thead>
<tr>
<th class="text-center">商品封面</th>
<th class="full-width">轮播图片 <span class="color-desc font-w1">( 轮播图片推荐的宽高比为 5:3 )</span></th>
</tr>
<tr>
<td class="text-center text-top ta-p-0">
<div class="help-images">
<input name="cover" data-max-width="500" data-max-height="500" type="hidden" value="{$vo.cover|default=''}">
<script>$('[name="cover"]').uploadOneImage();</script>
</div>
</td>
<td class="text-left ta-p-0">
<div class="help-images">
<input name="slider" data-max-width="2048" data-max-height="1024" type="hidden" value="{$vo.slider|default=''}">
<script>$('[name="slider"]').uploadMultipleImage();</script>
</div>
</td>
</tr>
</thead>
</table>
</div>
<div class="layui-form-item" id="GoodsSpecsEditor">
<span class="help-label label-required-prev">
<b>商品规格</b><span class="color-red font-s12">( 规格填写后不允许再次增加规格分组,规格图片推荐的宽高比为 5:3 )</span>
</span>
<div class="ta-mb-10" v-for="(x,$index) in specs" :key="$index">
<div class="goods-spec-box ta-pr-10" style="background:#ddd">
<span class="text-center goods-spec-name">分组</span>
<label class="label-required-null inline-block">
<input v-model.trim="x.name" @change="trimSpace(x,'name')" vali-name="分组" placeholder="请输入分组名称" required>
</label>
<div class="pull-right flex">
<a class="layui-btn layui-btn-sm layui-btn-primary goods-spec-btn" @click="addSpecVal(x.list)">增加</a>
<a class="layui-btn layui-btn-sm layui-btn-primary goods-spec-btn" @click="upSpecRow(specs,$index)" :class="{false:'layui-btn-disabled'}[$index>0]">上移</a>
<a class="layui-btn layui-btn-sm layui-btn-primary goods-spec-btn" @click="dnSpecRow(specs,$index)" :class="{false:'layui-btn-disabled'}[$index<specs.length-1]">下移</a>
<div style="display:none" :class="{true:'layui-show'}[mode==='add' && specs.length>0]">
<a class="layui-btn layui-btn-sm layui-btn-primary goods-spec-btn" @click="delSpecRow(specs,$index)" v-if="specs.length>1">删除</a>
</div>
</div>
</div>
<div class="goods-spec-box" v-if="x.list && x.list.length>0">
<label class="label-required-null nowrap" v-for="xx in x.list">
<input lay-ignore @click="xx.check=checkListChecked(x.list,$event.target.checked)" v-model.trim="xx.check" type="checkbox">
<input v-model.trim="xx.name" @change="trimSpace(xx,'name')" vali-name="规格" placeholder="请输入规格" required type="text">
<a class="layui-icon layui-icon-close goods-spec-close" @click="delSpecVal(x.list,$index)" v-if="x.list.length>1"></a>
</label>
</div>
</div>
<div v-if="mode==='add'">
<a class="layui-btn layui-btn-sm layui-btn-primary" @click="addSpecRow(specs)" v-if="specs.length<3">增加规则分组</a>
<p class="ta-mt-10"><span class="color-red">请完成属性修改后再编辑下面的规格信息,否则规格数据会丢失!</span></p>
</div>
<table class="layui-table goods-spec-table">
<thead>
<tr>
<th class="layui-bg-gray" :colspan="specs.length">规格</th>
<th colspan="3">商品价格</th>
<th colspan="5">其他属性</th>
</tr>
<tr>
<th class="nowrap layui-bg-gray" v-for="x in specs"><b>{{x.name}}</b></th>
<th class="nowrap" @click="batchSet('cost',2,'请输入商品成本价格')"><b>成本价</b><i class="layui-icon">&#xe63c;</i></th>
<th class="nowrap" @click="batchSet('market',2,'请输入商品市场价格')"><b>市场价</b><i class="layui-icon">&#xe63c;</i></th>
<th class="nowrap" @click="batchSet('selling',2,'请输入商品销售积分格')"><b>销售积分</b><i class="layui-icon">&#xe63c;</i></th>
<th class="nowrap" @click="batchSet('virtual',0,'请输入虚拟销量数值')"><b>虚拟销量</b><i class="layui-icon">&#xe63c;</i></th>
<th class="nowrap" @click="batchSet('express',0,'请输入快递计费系数')"><b>快递系数</b><i class="layui-icon">&#xe63c;</i></th>
<th class="nowrap" width="9%" @click="batchSet('gsku',null,'请输入商品SKU代码')"><b>商品SKU</b> <i class="layui-icon">&#xe63c;</i></th>
<th class="nowrap" width="6%">规格图片</th>
<th class="nowrap" width="6%"><b>销售状态</b></th>
</tr>
</thead>
<tbody>
<tr v-for="(item,hash) in items" :key="hash">
<td class="layui-bg-gray nowrap text-center" v-if="s.check" v-for="s in item.s">{{s.name}}</td>
<td><label><input class="layui-input" @blur="syncSet(hash)" v-model.trim="item.v.cost"></label></td>
<td><label><input class="layui-input" @blur="syncSet(hash)" v-model.trim="item.v.market"></label></td>
<td><label><input class="layui-input" @blur="syncSet(hash)" v-model.trim="item.v.selling"></label></td>
<td><label><input class="layui-input" @blur="syncSet(hash)" v-model.trim="item.v.virtual"></label></td>
<td><label><input class="layui-input" @blur="syncSet(hash)" v-model.trim="item.v.express"></label></td>
<td><label><input class="layui-input font-code" v-model.trim="item.v.gsku"></label></td>
<td class="upload-image-xs ta-p-0 text-center">
<upload-image v-model.trim="item.v.image" :showinput="false"></upload-image>
</td>
<td class="layui-bg-gray"><label class="think-checkbox"><input lay-ignore v-model.trim="item.v.status" type="checkbox"></label></td>
</tr>
</tbody>
</table>
<p class="color-desc">请注意商品的 sku 在系统中仅作为显示之用,系统会根据规格生成哈希值作为商品唯一区别码!</p>
<label class="layui-hide">
<textarea class="layui-textarea" name="specs">{{JSON.stringify(specs)}}</textarea>
<textarea class="layui-textarea" name="items">{{JSON.stringify(attrs)}}</textarea>
</label>
</div>
<label class="layui-form-item block">
<span class="help-label"><b>商品简介描述</b></span>
<textarea class="layui-textarea" name="remark" placeholder="请输入商品简介描述">{$vo.remark|default=''|raw}</textarea>
</label>
<div class="layui-form-item block">
<span class="help-label label-required-prev"><b>商品富文本详情</b></span>
<textarea class="layui-hide" name="content">{$vo.content|default=''|raw}</textarea>
</div>
<div class="hr-line-dashed ta-mt-40"></div>
<input name="code" type="hidden" value="{$vo.code}">
<div class="layui-form-item text-center">
<button class="layui-btn layui-btn-danger" type="button" data-target-backup>取消编辑</button>
<button class="layui-btn" type="submit">保存商品</button>
</div>
</div>
</form>
{/block}
{block name='script'}
<label class="layui-hide">
<textarea id="GoodsSpecs">{$vo.specs|raw|default=''}</textarea>
<textarea id="GoodsItems">{$vo.items|raw|default=''}</textarea>
</label>
<script>
/*! 加载扩展插件 */
require(['md5', 'vue', 'ckeditor'], function (md5, Vue) {
// 创建富文本
window.createEditor('[name=content]', {height: 500});
// 图片上传组件
Vue.component('UploadImage', {
data: () => ({eid: Math.random().toString().replace('0.', 'up')}),
model: {prop: 'mvalue', event: 'change'}, props: {mvalue: {type: String, default: ''}},
template: '<input class="layui-hide" v-model="mvalue" :id="eid" readonly>',
mounted: function () {
this.$nextTick(() => $('#' + this.eid).uploadOneImage().on('change', e => this.$emit('change', e.target.value)));
}
});
// 字段格式规则
let rules = {
image: '_',
cost: '(parseFloat(_)||0).toFixed(2)',
market: '(parseFloat(_)||0).toFixed(2)',
selling: '(parseFloat(_)||0).toFixed(2)',
balance: '(parseFloat(_)||0).toFixed(2)',
integral: '(parseFloat(_)||0).toFixed(0)',
express: '(parseFloat(_)||0).toFixed(0)',
virtual: '(parseFloat(_)||0).toFixed(0)',
allow_balance: '(parseFloat(_)||0).toFixed(2)',
allow_integral: '(parseFloat(_)||0).toFixed(0)',
};
// 历史及缓存数据
let cache = {};
layui.each(JSON.parse($('#GoodsItems').val() || '{}') || {}, function (k, v) {
for (let i in v) setValue(k, i, v[i]);
});
// 创建 Vue2 实例
let app = new Vue({
el: '#GoodsSpecsEditor', data: () => ({
mode: '{$mode|default="add"}', items: {}, attrs: {},
specs: JSON.parse(JSON.parse($('#GoodsSpecs').val() || '[]')) || []
}),
created: function () {
this.specs.length < 1 && addSpecRow(this.specs);
},
watch: {
specs: {
deep: true, immediate: true, handler: function (values) {
// 笛卡尔积生成商品SKU多规格算法
let items = {}, attrs = [], list = values.reduce((a, b) => {
let res = [];
a.map(x => b.list.map(y => y.check && res.push(x.concat(Object.assign({group: b.name}, y)))));
return res;
}, [[]]);
// 初始化商品规格同步变量值
list.map(function (cols) {
let keys = [], specs = [], unids = [];
cols.map(v => keys.push(v.group + '::' + v.name) && specs.push(v) && unids.push(v.unid));
let hash = md5.hash("{$vo.code}#" + unids.sort().join(';')), values = {
hash: hash,
spec: keys.join(';;'),
gsku: getValue(hash, 'gsku', withRandString(14, 'S')),
image: getValue(hash, 'image', ''),
status: !!getValue(hash, 'status', 1),
cost: getValue(hash, 'cost', '0.00'),
market: getValue(hash, 'market', '0.00'),
selling: getValue(hash, 'selling', '0.00'),
balance: getValue(hash, 'balance', '0.00'),
integral: getValue(hash, 'integral', '0'),
express: getValue(hash, 'express', '1'),
virtual: getValue(hash, 'virtual', '0'),
allow_balance: getValue(hash, 'allow_balance', '0.00'),
allow_integral: getValue(hash, 'allow_integral', '0'),
};
items[hash] = {s: specs, v: values};
attrs.push(values)
})
this.attrs = attrs;
this.items = items;
}
}
},
methods: {
/*! 同步格式化值 */
syncSet: function (hash) {
let v = this.items[hash].v;
for (let k in v) v[k] = setValue(hash, k, v[k])
},
/*! 批量设置数值 */
batchSet: function (name, fixed, title) {
let min = (0 / Math.pow(10, parseInt(fixed))).toFixed(fixed), max = (999999).toFixed(fixed);
layer.prompt({
title: title || (fixed === null ? '请输入内容' : '请输入数量【 取值范围:1 - 999999 】'),
formType: 0, value: fixed === null ? '' : min, success: function ($el) {
$el.find('.layui-layer-input').attr({'data-value-min': min, 'data-value-max': max, 'data-blur-number': fixed});
}
}, function (value, index) {
if (fixed !== null) value = (parseFloat(value) || 0).toFixed(fixed);
for (let i in app.items) app.items[i].v[name] = value;
layer.close(index) || app.$forceUpdate()
});
},
/*! 去除空白字符 */
trimSpace: function (x, i) {
return x[i] = (x[i] || '').replace(/[\s,;'"]+/ig, '');
},
/*! 判断规则选择 */
checkListChecked: function (data, check) {
for (let i in data) if (data[i].check) return check;
return true;
},
/*! 下移整行规格分组 */
dnSpecRow: function (items, index) {
index + 1 < items.length && (item => items.splice(index + 1, 1) && items.splice(index, 0, item))(items[index + 1]);
},
/*! 上移整行规格分组 */
upSpecRow: function (items, index) {
index > 0 && (item => items.splice(index - 1, 1) && items.splice(index, 0, item))(items[index - 1]);
},
/*! 增加整行规格分组 */
addSpecRow: function (data) {
addSpecRow(data);
},
/*! 移除整行规格分组 */
delSpecRow: function (items, index) {
items.splice(index, 1);
},
/*! 增加分组的属性 */
addSpecVal: function (data) {
addSpecVal(data);
},
/*! 移除分组的属性 */
delSpecVal: function (data, $index) {
data.splice($index, 1);
},
}
})
/*! 动态设置内容 */
function setValue(hash, name, value, filter) {
filter = filter || rules[name] || null;
if (typeof filter === 'string' && filter.indexOf('_') > -1) {
value = eval(filter.replace('_', "'" + value + "'"));
}
cache[hash] = cache[hash] || {};
return cache[hash][name] = value;
}
/*!动态读取内容 */
function getValue(hash, name, value) {
let _cache = cache[hash] || {};
if (typeof _cache[name] === 'undefined') {
setValue(hash, name, value, '_')
_cache = cache[hash] || {};
}
return _cache[name];
}
// 创建分组
function addSpecRow(data) {
data.push({name: '规格分组' + (data.length || ''), list: addSpecVal([])})
}
// 创建规格
function addSpecVal(data) {
data.push({name: '规格属性' + (data.length || ''), unid: withRandString(16), check: true});
return data;
}
// 随机字符串
function withRandString(length, prefix) {
return (function (time, code) {
code += parseInt(time.substring(0, 1)) + parseInt(time.substring(1, 2)) + time.substring(2);
while (code.length < length) code += (Math.random()).toString().replace('.', '');
return code.substring(0, length);
})(Date.now().toString(), prefix || '')
}
});
</script>
{/block}

View File

@ -0,0 +1,185 @@
<style>
.upload-image-xs {
padding: 2px !important;
}
.upload-image-xs .uploadimage {
width: 30px;
height: 30px;
margin: 0;
}
.upload-image-xs .uploadimage span:first-child {
display: none !important;
}
.goods-item-box fieldset {
width: 260px;
height: 80px;
padding: 15px 20px;
display: inline-block;
margin: 0 15px 15px 0;
}
.goods-spec-box {
position: relative;
margin: 0 10px 10px 0;
background: #EEEEEE;
vertical-align: middle;
}
.goods-spec-name {
z-index: 2;
width: 40px;
color: #fff;
height: 25px;
position: absolute;
background: #999;
line-height: 26px;
}
.goods-spec-btn {
height: 28px;
margin-left: 5px !important;
line-height: 28px !important;
}
.goods-spec-box {
margin: 0;
padding: 10px 0 5px 10px;
position: relative;
}
.goods-spec-box label {
display: inline-block;
position: relative;
margin-right: 10px;
margin-bottom: 5px;
}
.goods-spec-box input:not([type=checkbox]) {
width: 120px;
border: 1px solid #999;
display: inline-block !important;
z-index: 1;
position: relative;
line-height: 24px;
padding-left: 45px;
padding-right: 5px;
}
.goods-spec-box input[type=checkbox] {
width: 40px;
height: 26px;
border: none;
cursor: pointer;
z-index: 2;
position: absolute;
appearance: none;
-webkit-appearance: none;
}
.goods-spec-box input[type=checkbox]:after,
.goods-spec-box input[type=checkbox]:before {
top: 1px;
left: 1px;
width: 40px;
height: 24px;
position: absolute;
}
.goods-spec-box input[type=checkbox]:before {
content: '';
background: #c9c9c9;
}
.goods-spec-box input[type=checkbox]:after {
color: #999;
content: '\e63f';
font-size: 16px;
text-align: center;
line-height: 24px;
font-family: 'layui-icon', serif;
}
.goods-spec-box input[type=checkbox]:checked:after {
color: #333;
content: '\e605';
}
.goods-spec-box label .goods-spec-close {
top: 5px;
right: 5px;
width: 16px;
bottom: 3px;
height: 16px;
z-index: 2;
color: white;
display: none;
position: absolute;
font-size: 12px;
text-align: center;
box-sizing: border-box;
background: rgba(252, 24, 24, 0.6);
line-height: 16px;;
border-radius: 50%;
}
.goods-spec-box label .goods-spec-close:hover {
color: white;
background: rgba(252, 24, 24, 1.0);
}
.goods-spec-box label:hover .goods-spec-close {
display: inline-block;
}
.goods-spec-table {
margin-top: 10px;
}
.goods-spec-table tr th {
padding: 9px 0;
text-align: center;
}
.goods-spec-table tr th .layui-icon {
font-size: 13px;
margin-left: 3px;
}
.goods-spec-table tr td {
padding: 9px 6px;
}
.goods-spec-table tr td:has(input) {
padding: 0;
}
.goods-spec-table tr td:last-child {
text-align: center;
}
.goods-spec-table tr td:last-child label {
margin: 0;
width: 100%;
height: 100%;
display: block;
}
.goods-spec-table tr td label {
margin: 0;
padding: 0;
}
.goods-spec-table tr td label input.layui-input {
border: none;
width: 100%;
height: 38px !important;
text-align: center;
line-height: 38px !important;
padding-left: 0;
}
</style>

View File

@ -0,0 +1,113 @@
{extend name="table"}
{block name="button"}
<!--{if auth("add")}-->
<button class='layui-btn layui-btn-sm layui-btn-primary' data-open='{:url("add")}'>添加商品</button>
<!--{/if}-->
<!--{if auth("goods_cate/index")}-->
<button class='layui-btn layui-btn-sm layui-btn-primary' data-modal='{:url("goods_cate/index")}' data-title="分类管理" data-width="999px">分类管理</button>
<!--{/if}-->
{/block}
{block name="content"}
<div class="layui-tab layui-tab-card think-bg-white">
<ul class="layui-tab-title">
{foreach ['index'=>'商品管理','recycle'=>'下架商品'] as $k=>$v}
{if isset($type) and $type eq $k}
<li class="layui-this" data-open="{:url('index')}?type={$k}">{$v}</li>
{else}
<li data-open="{:url('index')}?type={$k}">{$v}</li>
{/if}{/foreach}
</ul>
<div class="layui-tab-content">
{include file='goods/index_search'}
<table id="GoodsTable" data-line="2" data-url="{:request()->url()}"></table>
</div>
</div>
{/block}
{block name='script'}
<script>
$(function () {
let $table = $('#GoodsTable').layTable({
even: true, height: 'full',
sort: {field: 'sort desc,id', type: 'desc'},
cols: [[
{field: 'id', hide: true},
{field: 'sort', title: '排序权重', width: 100, align: 'center', sort: true, templet: '#SortInputTpl'},
{
title: '商品名称', minWidth: 220, templet: function (d) {
d.html = showTableImage(d.cover, false, 'sm')
return laytpl("<div class='flex'><div>{{-d.html}}</div><div class='ta-pl-10 nowrap'>名称:{{d.name}}<br>编号:<b class='color-blue'>{{d.code}}</b></div></div>").render(d);
}
},
{
title: '价格及库存 ', width: '15%', templet: function (d) {
let tpls = [];
tpls.push('售价: <b>{{Number(d.price_selling)}}</b> 积分/件');
tpls.push('总库存 <b>{{d.stock_total}}</b> 件,剩余库存 <b>{{d.stock_total-d.stock_sales}}</b> 件');
return laytpl('<div class="sub-strong-blue nowrap">' + tpls.join('<br>') + '</div>').render(d);
}
},
{field: 'status', title: '商品状态', minWidth: 110, width: '5%', align: 'center', templet: '#StatusSwitchTpl'},
{field: 'create_time', title: '创建时间', minWidth: 200, width: '5%', templet: "<div>更新:{{d.update_time}}<br>创建:{{d.create_time}}</div>"},
{toolbar: '#toolbar', title: '操作面板', minWidth: 155, width: '5%', align: 'center', fixed: 'right'},
]]
});
// 数据状态切换操作
layui.form.on('switch(StatusSwitch)', function (obj) {
let data = {code: obj.value, status: obj.elem.checked > 0 ? 1 : 0};
$.form.load("{:url('state')}", data, 'post', function (ret) {
let fn = () => $table.trigger('reload');
ret.code < 1 ? $.msg.error(ret.info, 3, fn) : fn();
return false;
}, false);
});
});
</script>
<!-- 数据状态切换模板 -->
<script type="text/html" id="StatusSwitchTpl">
<div class="ta-pt-10 block relative"></div>
<!--{if auth("state")}-->
<input type="checkbox" value="{{d.code}}" lay-skin="switch" lay-text="已上架|已下架" lay-filter="StatusSwitch" {{-d.status>0?'checked':''}}>
<!--{else}-->
{{-d.status ? '<b class="color-green">已上架</b>' : '<b class="color-red">已下架</b>'}}
<!--{/if}-->
</script>
<!-- 列表排序权重模板 -->
<script type="text/html" id="SortInputTpl">
<label class="ta-pt-5 block relative">
<input type="number" min="0" data-blur-number="0" data-action-blur="{:sysuri()}" data-value="id#{{d.id}};action#sort;sort#{value}" data-loading="false" value="{{d.sort}}" class="layui-input text-center">
</label>
</script>
<!-- 操控面板的模板 -->
<script type="text/html" id="toolbar">
{if $type eq 'index'}
<!--{if auth('stock')}-->
<a class="layui-btn layui-btn-sm layui-btn-normal" data-title="商品入库" data-modal='{:url("stock")}?code={{d.code}}'> </a>
<!--{/if}-->
<!--{if auth('edit')}-->
<a class="layui-btn layui-btn-sm" data-open='{:url("edit")}?code={{d.code}}'> </a>
<!--{/if}-->
{else}
<!--{if auth('edit')}-->
<a class="layui-btn layui-btn-sm" data-open='{:url("edit")}?code={{d.code}}'> </a>
<!--{/if}-->
<!--{if auth('remove')}-->
<a class="layui-btn layui-btn-sm layui-btn-danger" data-confirm="确定要删除此商品吗?" data-action="{:url('remove')}" data-value="code#{{d.code}}"> </a>
<!--{/if}-->
{/if}
</script>
{/block}

View File

@ -0,0 +1,32 @@
<form action="{:sysuri()}" autocomplete="off" data-table-id="GoodsTable" class="layui-form layui-form-pane form-search" method="get" onsubmit="return false">
<div class="layui-form-item layui-inline">
<label class="layui-form-label">商品名称</label>
<label class="layui-input-inline">
<input class="layui-input" name="name" placeholder="请输入编号或名称" value="{$get.name|default=''}">
</label>
</div>
<!--{notempty name='cates'}-->
<div class="layui-form-item layui-inline">
<label class="layui-form-label">商品分类</label>
<div class="layui-input-inline">
<label class="layui-input-inline">
<select class="layui-select" lay-search name="cates">
<option value="">-- 全部分类 --</option>
{foreach $cates as $cate}{if input('cates') eq $cate.id}
<option selected value="{$cate.id}">{$cate.spl}{$cate.name|default=''}</option>
{else}
<option value="{$cate.id}">{$cate.spl}{$cate.name|default=''}</option>
{/if}{/foreach}
</select>
</label>
</div>
</div>
<!--{/notempty}-->
<div class="layui-form-item layui-inline">
<button class="layui-btn layui-btn-primary"><i class="layui-icon">&#xe615;</i> 搜 索</button>
</div>
</form>

View File

@ -0,0 +1,35 @@
<div class="relative">
{include file='shop/goods/select_search'}
<table id="GoodsTableSelect" data-line="2" data-url="{:request()->url()}"></table>
</div>
<script>
$(function () {
$('#GoodsTableSelect').layTable({
even: true, width: 'full', height: 'full', page: true,
sort: {field: 'sort desc,id', type: 'desc'},
cols: [[
// {checkbox: true},
{field: 'id', hide: true},
{field: 'sort', hide: true},
{field: 'cover', title: '商品封面', width: 80, align: 'center', templet: '<div>{{-showTableImage(d.cover,false,"sm")}}</div>'},
{field: 'name', title: '商品名称', templet: "<div>商品名称:{{d.name}}<br>商品编号:<b class='color-blue'>{{d.code}}</b></div>"},
{field: 'create_time', title: '创建时间', width: 210, templet: "<div>更新:{{d.update_time}}<br>创建:{{d.create_time}}</div>"},
{toolbar: '#SelectToolbar', title: '操作面板', width: 80, align: 'center', fixed: 'right'},
]]
}).trigger('tool', function (item) {
let attr = ['商品详情', item.data.code, item.data.name];
window.setItemValue(item.data, attr.join('#'));
$.msg.closeThisModal(this);
});
});
</script>
<!-- 操作工具条模板 -->
<script type="text/html" id="SelectToolbar">
{{# if (typeof checkItemValue !== 'function' || checkItemValue(d.id)){ }}
<a class="layui-btn layui-btn-sm" lay-event="select"> </a>
{{# }else{ }}
<a class="layui-btn layui-btn-sm layui-btn-disabled"> </a>
{{# } }}
</script>

View File

@ -0,0 +1,24 @@
<fieldset>
<legend>{:lang('条件搜索')}</legend>
<form action="{:sysuri()}" autocomplete="off" data-table-id="GoodsTableSelect" class="layui-form layui-form-pane form-search" method="get" onsubmit="return false">
<div class="layui-form-item layui-inline">
<label class="layui-form-label">商品名称</label>
<label class="layui-input-inline">
<input class="layui-input" name="name" placeholder="请输入商品名称或编号" value="{$get.name|default=''}">
</label>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">创建时间</label>
<div class="layui-input-inline">
<input class="layui-input" data-date-range name="create_time" placeholder="请选择创建时间" value="{$get.create_time|default=''}">
</div>
</div>
<div class="layui-form-item layui-inline">
<button class="layui-btn layui-btn-primary"><i class="layui-icon">&#xe615;</i> 搜 索</button>
</div>
</form>
</fieldset>

View File

@ -0,0 +1,98 @@
<form action="{:sysuri()}?code={$get.code|default=''}" data-table-id="GoodsTable" class="layui-form layui-card" data-auto="true" method="post">
<div class="layui-card-body ta-pl-40">
<div class="layui-form-item relative">
<span class="help-label"><b>商品编号</b>Goods Code</span>
<div class="layui-input layui-bg-gray">{$vo.code|default=''}</div>
</div>
<div class="layui-form-item relative">
<span class="help-label"><b>商品名称</b>Goods Name</span>
<div class="layui-input layui-bg-gray">{$vo.name|default=''}</div>
</div>
<div class="layui-form-item relative">
<span class="help-label"><b>库存数据</b>Goods Stock Data</span>
<table class="layui-table border-0 ta-m-0" lay-skin="nob">
<colgroup>
<col style="width:auto">
<col style="width:80px">
<col style="width:80px">
<col style="width:80px">
<col style="width:80px">
<col style="width:80px">
<col style="width:99px">
<col style="width:18px">
</colgroup>
<thead>
<tr class="layui-bg-cyan notselect">
<th class="text-left nowrap">商品规格</th>
<th class="text-center nowrap">市场价格</th>
<th class="text-center nowrap">销售积分</th>
<th class="text-center nowrap">库存统计</th>
<th class="text-center nowrap">总销统计</th>
<th class="text-center nowrap">库存剩余</th>
<th class="text-center nowrap pointer" data-batchset data-tips-text="批量设置库存">
<i class="layui-icon font-s10" style="color:#FFF">&#xe63c;</i> 入库数量
</th>
<th class="ta-p-0"></th>
</tr>
</thead>
</table>
<div style="max-height:500px;overflow-y:scroll">
<table class="layui-table ta-m-0" lay-skin="line">
<colgroup>
<col style="width:auto">
<col style="width:80px">
<col style="width:80px">
<col style="width:80px">
<col style="width:80px">
<col style="width:80px">
<col style="width:99px">
</colgroup>
<tbody>
{foreach $vo.items as $goods}
<tr>
<td class="layui-bg-gray layui-elip">{$goods.gspec|show_gspec}</td>
<td class="layui-bg-gray text-center">{$goods.price_market+0}</td>
<td class="layui-bg-gray text-center">{$goods.price_selling+0}</td>
<td class="layui-bg-gray text-center">{$goods.stock_total|default=0}</td>
<td class="layui-bg-gray text-center">{$goods.stock_sales|default=0}</td>
<td class="layui-bg-gray text-center">{$goods.stock_total-$goods.stock_sales}</td>
<td class="ta-p-0 nowrap">
<input name="gcode[]" type="hidden" value="{$goods.gcode|default=''}">
<input name="gspec[]" type="hidden" value="{$goods.gspec|default=''}">
<input name="ghash[]" type="hidden" value="{$goods.ghash|default=''}">
<label class="relative flex flex-align-center">
<input class="layui-input text-center border-0 flex-1" type="number" min="0" data-blur-number="0" maxlength="20" name="gstock[]" value="0">
<span class="layui-bg-gray ta-pl-5 ta-pr-5"></span>
</label>
</td>
</tr>
{/foreach}
</tbody>
</table>
</div>
</div>
</div>
<div class="hr-line-dashed"></div>
<div class="layui-form-item text-center">
<button class="layui-btn" type='submit'>确定入库</button>
<button class="layui-btn layui-btn-danger" data-close data-confirm="确定要取消入库吗?" type='button'>取消入库</button>
</div>
</form>
<script>
$('[data-batchset]').on('click', function () {
layer.prompt({
title: '请输入库存数量【 取值范围:1 - 999999 】',
formType: 0, value: 1, success: function ($ele) {
$ele.find('.layui-layer-input').attr({'data-value-min': 1, 'data-value-max': 999999, 'data-blur-number': 0});
}
}, function (value, index) {
layer.close(index) || $('[name^="gstock"]').val(parseInt(value) || 0);
});
});
</script>

View File

@ -0,0 +1,49 @@
<form action="{:sysuri()}" data-table-id="CateTable" class="layui-form layui-card" data-auto="true" method="post">
<div class="layui-card-body ta-pl-40">
<div class="layui-form-item label-required-prev">
<span class="help-label"><b>绑定上级分类</b>Category Parent</span>
<select class='layui-select' name='pid' lay-search>
{foreach $cates as $cate}
{eq name='cate.id' value='$vo.pid|default=0'}
<option selected value='{$cate.id}'>{$cate.spl|raw}{$cate.name}</option>
{else}
<option value='{$cate.id}'>{$cate.spl|raw}{$cate.name}</option>
{/eq}{/foreach}
</select>
<span class="help-block"><b>必选,</b>请选择上级分类或顶级分类(目前最多支持{$cateLevel|default=0}级分类)</span>
</div>
<label class="layui-form-item relative block">
<span class="help-label"><b>商品分类名称</b>Category Name</span>
<input class="layui-input" name="name" placeholder="请输入分类名称" required value='{$vo.name|default=""}'>
<span class="help-block"><b>必填,</b>请填写商品分类名称,建议字符不要太长,一般 4-6 个汉字</span>
</label>
<div class="layui-form-item relative block">
<span class="help-label"><b>商品分类图标</b>Category Cover</span>
<div class="relative block">
<label>
<input class="layui-input think-bg-gray" data-tips-hover data-tips-image readonly name="cover" placeholder="请上传商品分类图标" value='{$vo.cover|default=""}'>
</label>
<a class="input-right-icon layui-icon layui-icon-upload" data-field="cover" data-file data-type="png,jpg,gif"></a>
</div>
<span class="help-block"><b>可选,</b>请上传商品分类图标,需要是 http 可访问的图片资源链接</span>
</div>
<label class="layui-form-item relative block">
<span class="color-green font-w7">商品分类描述</span>
<span class="color-desc ta-ml-5">Category Remark</span>
<textarea class="layui-textarea" name="remark" placeholder="请输入分类描述">{$vo.remark|default=''}</textarea>
</label>
</div>
<div class="hr-line-dashed"></div>
{notempty name='vo.id'}<input name='id' type='hidden' value='{$vo.id}'>{/notempty}
<div class="layui-form-item text-center">
<button class="layui-btn" type='submit'>保存数据</button>
<button class="layui-btn layui-btn-danger" data-close type='button' data-confirm="确定要取消编辑吗?">取消编辑</button>
</div>
</form>

View File

@ -0,0 +1,78 @@
<div class="think-box-notify">
<b>注意:</b>商品分类需要在上传商品前添加,当商品分类关联有商品时不建议进行 <b class="color-blue">移动</b> <b class="color-blue">删除</b> 操作!
<div class="pull-right" style="margin-top:-8px">
<!--{if auth("add")}-->
<button class='layui-btn layui-btn-sm layui-btn-primary' data-table-id="CateTable" data-modal='{:url("add")}' data-title="添加分类">添加分类</button>
<!--{/if}-->
<!--{if auth("remove")}-->
<button class='layui-btn layui-btn-sm layui-btn-primary' data-table-id="CateTable" data-action='{:url("remove")}' data-rule="id#{sps}">删除分类</button>
<!--{/if}-->
</div>
</div>
<div class="think-box-shadow shadow-none" style="padding-top:0!important;">
<table id="CateTable" data-url="{:request()->url()}"></table>
</div>
<script>
$(function () {
let $table = $('#CateTable').layTable({
even: true, height: 'full', page: false,
sort: {field: 'sort desc,id', type: 'asc'},
cols: [[
{checkbox: true, fixed: true},
{field: 'sort', title: '排序权重', width: 100, align: 'center', sort: true, templet: '#SortInputTplCate'},
{field: 'name', title: '分类名称', minWidth: 220, templet: '<div><span class="color-desc">{{d.spl}}</span>{{d.name}}</div>'},
{field: 'status', title: '分类状态', width: 110, align: 'center', templet: '#StatusSwitchTplCate'},
{field: 'create_time', title: '创建时间', width: 170, align: 'center'},
{toolbar: '#ToolBarCate', title: '操作面板', width: 220, align: 'center', fixed: 'right'},
]]
});
// 数据状态切换操作
layui.form.on('switch(StatusSwitch)', function (object) {
object.data = {status: object.elem.checked > 0 ? 1 : 0};
object.data.id = object.value.split('|')[object.data.status] || object.value;
$.form.load("{:url('state')}", object.data, 'post', function (ret) {
let fn = () => $table.trigger('reload')
ret.code < 1 ? $.msg.error(ret.info, 3, fn) : fn()
return false;
}, false);
});
});
</script>
<!-- 数据状态切换模板 -->
<script type="text/html" id="StatusSwitchTplCate">
<!--{if auth("state")}-->
<input type="checkbox" value="{{d.sps}}|{{d.spp}}" lay-text="已激活|已禁用" lay-filter="StatusSwitch" lay-skin="switch" {{-d.status>0?'checked':''}}>
<!--{else}-->
{{-d.status ? '<b class="color-green">已激活</b>' : '<b class="color-red">已禁用</b>'}}
<!--{/if}-->
</script>
<!-- 列表排序权重模板 -->
<script type="text/html" id="SortInputTplCate">
<input type="number" min="0" data-blur-number="0" data-action-blur="{:sysuri()}" data-value="id#{{d.id}};action#sort;sort#{value}" data-loading="false" value="{{d.sort}}" class="layui-input text-center">
</script>
<!-- 操控面板的模板 -->
<script type="text/html" id="ToolBarCate">
<!--{if auth('add')}-->
{{# if(d.spt<'{$maxLevel-1}'){ }}
<a class="layui-btn layui-btn-sm layui-btn-primary" data-title="添加商品分类" data-modal='{:url("add")}?pid={{d.id}}'> </a>
{{# }else{ }}
<a class="layui-btn layui-btn-sm layui-btn-disabled"> </a>
{{# } }}
<!--{/if}-->
<!--{if auth('edit')}-->
<a class="layui-btn layui-btn-sm" data-title="编辑商品分类" data-modal='{:url("edit")}?id={{d.id}}'> </a>
<!--{/if}-->
<!--{if auth('remove')}-->
<a class="layui-btn layui-btn-sm layui-btn-danger" data-confirm="确定要删除此分类吗?" data-action="{:url('remove')}" data-value="id#{{d.sps}}"> </a>
<!--{/if}-->
</script>

View File

@ -0,0 +1,32 @@
<div class="relative">
<table id="CateTableSelect" data-url="{:request()->url()}" data-target-search="form.form-search"></table>
</div>
<script>
$(function () {
$('#CateTableSelect').layTable({
even: true, height: 'full', page: false, width: 'full',
sort: {field: 'sort desc,id', type: 'asc'},
cols: [[
{checkbox: true, fixed: true, hide: true},
{field: 'sort', hide: true},
{field: 'name', title: '分类名称', minWidth: 220, templet: '<div><span class="color-desc">{{d.spl}}</span>{{d.name}}</div>'},
{field: 'create_time', title: '创建时间', width: 170, align: 'center'},
{toolbar: '#SelectToolbar', title: '操作面板', width: 160, align: 'center', fixed: 'right'},
]]
}).trigger('tool', function (item) {
let attr = ['商品分类', item.data.id, item.data.name];
window.setItemValue(item.data, attr.join('#'));
$.msg.closeThisModal(this);
});
});
</script>
<!-- 操作工具条模板 -->
<script type="text/html" id="SelectToolbar">
{{# if (typeof checkItemValue !== 'function' || checkItemValue(d.id)){ }}
<a class="layui-btn layui-btn-sm" lay-event="select"> </a>
{{# }else{ }}
<a class="layui-btn layui-btn-sm layui-btn-disabled"> </a>
{{# } }}
</script>

View File

@ -0,0 +1,167 @@
{extend name="main"}
{block name="button"}
{/block}
{block name="content"}
<div class="layui-tab layui-tab-card">
<ul class="layui-tab-title notselect">
{foreach $types as $k=>$v}{if isset($type) and 't'.$type eq $k}
<li class="layui-this" data-open="{:url('index')}?type={$k}">{$v}<sup class="layui-badge border-radius">{$total[$k]??0}</sup></li>
{else}
<li data-open="{:url('index')}?type={$k}">{$v}<sup class="layui-badge border-radius">{$total[$k]??0}</sup></li>
{/if}{/foreach}
</ul>
<div class="layui-tab-content">
{include file='order/index_search'}
<table id="OrderTable" data-line="3" data-url="{:request()->url()}" data-target-search="form.form-search"></table>
</div>
</div>
{/block}
{block name='script'}
<script>
function str2num(v) {
return parseFloat(v);
}
function str2name(v) {
let _ = [];
v.split(';;').forEach(s => _.push(s.split('::').pop()));
return _.join(' ');
}
let ostatus = ['已取消', '预订单', '待支付', '待审核', '待发货', '已发货', '已收货', '已评论'];
let ostyles = ['layui-bg-gray', 'layui-bg-red', 'layui-bg-orange', 'layui-bg-black', 'layui-bg-blue', 'layui-bg-blue', 'layui-bg-green', 'layui-bg-green'];
let rstatus = ['未售后', '预订单', '待审核', '待退货', '已退货', '待退款', '已退货', '已完成'];
let rstyles = ['layui-bg-gray', 'layui-bg-gray', 'layui-bg-blue', 'layui-bg-blue', 'layui-bg-blue', 'layui-bg-blue', 'layui-bg-blue', 'layui-bg-green'];
$(function () {
$('#OrderTable').layTable({
even: true, height: 'full', sort: {field: 'id', type: 'desc'},
cols: [[
{field: 'headimg', title: '头 像', width: 90, align: 'center', templet: '<div>{{-showTableImage(d.user.headimg,true,"md")}}</div>'},
{
title: '会员用户', width: 170, templet: function (d) {
let tpls = [];
if (d.user) {
tpls.push('<div>用户昵称:{{d.user.nickname||d.user.username||"-"}}</div>');
tpls.push('<div>用户手机:<b class="font-code">{{d.user.phone}}</b></div>');
} else {
tpls.push('<div class="color-desc ta-pt-10">无用户账号</div>')
}
return laytpl("<div class='nowrap ta-pt-10'>" + tpls.join('') + "</div>").render(d);
}
},
{
title: '推广用户', width: 170, templet: function (d) {
let tpls = [];
if (d.from) {
tpls.push('<div>用户昵称:{{d.from.nickname||d.from.username||"-"}}</div>');
tpls.push('<div>用户手机:<b class="font-code">{{d.from.phone}}</b></div>');
} else {
tpls.push('<div class="color-desc ta-pt-10">无推荐人</div>')
}
return laytpl("<div class='nowrap ta-pt-10'>" + tpls.join('') + "</div>").render(d);
}
},
{
title: '订单信息', minWidth: 250, templet: function (d) {
d.showPayments = "{:url('plugin-payment/record/index')}"
let tpls = ' <a data-tips-text="查看支付信息" data-title="查看支付信息" class="layui-icon layui-icon-rmb font-s12 ta-mr-5" data-width="999px" data-close-refresh="OrderTable" data-modal="{{d.showPayments}}?orderinfo={{d.order_no}}"></a>';
tpls += '订单号 <b class="font-code">{{d.order_no}}</b>';
if (d.amount_real > 0) {
if (d.payment_status > 0) {
tpls += '<br>已支付 <b class="font-code" data-width="1024px" data-title="查看支付详情">{{str2num(d.amount_real)}}</b> 元';
} else {
tpls += '<br>需支付 <b class="font-code" data-width="1024px" data-title="查看支付详情">{{str2num(d.amount_real)}}</b> 元';
}
} else {
tpls += '<br>无需支付';
}
if (d.amount_express > 0) {
tpls += ' ( 随减 <b class="font-code">{{str2num(d.amount_reduct)}}</b> 元,含邮费 <b class="font-code">{{str2num(d.amount_express)}}</b> 元)';
} else {
tpls += ' ( 随减 <b class="font-code">{{str2num(d.amount_reduct)}}</b> 元,包邮免费 )';
}
tpls += '<br>'
if (d.amount_balance > 0) {
tpls += "余额 " + d.amount_balance + " 元,"
} else {
tpls += '未使用余额,'
}
if (d.amount_integral > 0) {
tpls += "积分 " + d.amount_balance + ""
} else {
tpls += '未使用积分,'
}
if (d.coupon_code) {
tpls += "优惠券 " + d.coupon_amount + "元,"
} else {
tpls += '未使用优惠券。'
}
let status = laytpl('<span class="layui-badge layui-badge-middle flex-center {{d.style}}" >{{d.status}}</span>').render({
status: ostatus[d.status], style: ostyles[d.status]
});
let refund = laytpl('<span class="layui-badge layui-badge-middle flex-center {{d.style}}">{{d.status}}</span>').render({
status: rstatus[d.refund_status], style: rstyles[d.refund_status]
});
return '<div class="flex" style="margin-top:-4px">' + status + refund + '<div class="nowrap sub-strong-blue">' + laytpl(tpls).render(d) + '</div></div>';
}
},
{
title: '商品详情', minWidth: 220, templet: function (d) {
let tpls = [];
d.items.map(function (v) {
tpls.push(laytpl(
'<div class="flex">' +
' <div style="padding-top:3px">{{-showTableImage(d.gcover,false,"md")}}</div>' +
' <div class="ta-pl-5"></div>' +
' <div>' +
' <div>{{d.gcode}}</div>' +
' <div>' +
' <span>{{d.gname}}</span>' +
' <span class="ta-pl-5 color-desc">{{str2name(d.gspec)}}</span><br>' +
' <span>{{d.stock_sales}}件 x {{str2num(d.price_selling)}}元/件,计 {{str2num(d.total_price_selling)}}元</span> ' +
' </div>' +
' </div>' +
'</div>'
).render(v));
});
return tpls.join('<br>');
}
},
{
title: '收货地址', templet: function (d) {
if (d.address) {
let tpls = [];
if (d.address.status > 1) {
tpls.push('<b>发货物流:</b><span class="ta-mr-5">{{d.company_name}}</span><span class="color-blue font-code">{{d.express_code}}</span>');
} else {
tpls.push('<b>发货物流:</b><span class="color-desc">未发货</span>');
}
tpls.push('<b>联系方式:</b><span class="ta-mr-5">{{d.user_name}}</span><span class="color-blue font-code">{{d.user_phone}}</span>');
tpls.push('<b>收货地址:</b>{{d.region_prov}} {{d.region_city}} {{d.region_area}} {{d.region_addr}}');
return laytpl(tpls.join('<br>')).render(d.address);
} else {
return '<div class="ta-pt-20 color-desc">无收货信息</div>';
}
}
},
{field: 'id', sort: true, title: '创建时间', minWidth: 170, templet: "下单时间:{{d.create_time}}<br><div>支付时间:{{d.payment_time}}<br>签收时间:{{d.confirm_time}}</div>"},
{toolbar: '#toolbar', hide: true, title: '操作面板', width: 120, align: 'center', fixed: 'right'},
]]
});
});
</script>
<!-- 操控面板的模板 -->
<script type="text/html" id="toolbar">
<!--{if auth('remove')}-->
<a class="layui-btn layui-btn-sm layui-btn-danger" data-confirm="确定要删除此商品吗?" data-action="{:url('remove')}" data-value="code#{{d.code}}"> </a>
<!--{/if}-->
</script>
{/block}

View File

@ -0,0 +1,93 @@
<form action="{:sysuri()}" autocomplete="off" class="layui-form layui-form-pane form-search" method="get" onsubmit="return false">
<div class="layui-form-item layui-inline">
<label class="layui-form-label">会员用户</label>
<label class="layui-input-inline">
<input class="layui-input" name="user_keys" placeholder="请输入手机或昵称" value="{$get.user_keys|default=''}">
</label>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">订单单号</label>
<label class="layui-input-inline">
<input class="layui-input" name="order_no" placeholder="请输入订单单号" value="{$get.order_no|default=''}">
</label>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">发货单号</label>
<label class="layui-input-inline">
<input class="layui-input" name="delivery_express_code" placeholder="请输入发货单号" value="{$get.delivery_express_code|default=''}">
</label>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">订单状态</label>
<label class="layui-input-inline">
<select class="layui-select" name="status">
<option value=''>- 全部订单 -</option>
{foreach ['1'=>'预购订单','2'=>'等待支付','3'=>'等待审核','4'=>'等待发货','5'=>'已经发货','6'=>'已经完成','7'=>'已经评论','0'=>'已经取消'] as $k=>$v}
{if input('status') eq $k.''}
<option selected value="{$k}">{$v}</option>
{else}
<option value="{$k}">{$v}</option>
{/if}{/foreach}
</select>
</label>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">下单时间</label>
<label class="layui-input-inline">
<input class="layui-input" data-date-range name="create_time" placeholder="请选择下单时间" value="{$get.create_time|default=''}">
</label>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">支付时间</label>
<label class="layui-input-inline">
<input class="layui-input" data-date-range name="payment_time" placeholder="请选择支付时间" value="{$get.payment_time|default=''}">
</label>
</div>
<div class="layui-form-item layui-inline">
<label class="layui-form-label">收货信息</label>
<label class="layui-input-inline">
<input class="layui-input" name="address" placeholder="请输入收货信息" value="{$get.address|default=''}">
</label>
</div>
<div class="layui-form-item layui-inline">
<button class="layui-btn layui-btn-primary" type="submit"><i class="layui-icon">&#xe615;</i> 搜 索</button>
<button class="layui-btn layui-btn-primary" data-form-export="{:url('index')}?type={$type|default=''}" type="button">
<i class="layui-icon layui-icon-export"></i>
</button>
</div>
</form>
<script>
require(['excel'], function (excel) {
excel.bind(function (data, items) {
data.forEach(function (order) {
items.push([
order.order_no,
order.user.phone,
order.user.username || order.user.nickname || '',
order.delivery_type ? '虚拟商品' : '实物商品',
order.payment_trade || '',
order.payment_name || order.payment_type || '',
order.payment_status ? '已支付' : '未支付',
order.payment_amount || '0.00',
order.payment_time || '',
]);
});
// 设置表头内容
items.unshift(['订单号', '用户手机', '用户姓名', '订单类型', '支付单号', '支付方式', '支付状态', '支付金额', '支付时间']);
// 应用表格样式
return this.withStyle(items, {I: 120});
}, '用户订单记录' + layui.util.toDateString(Date.now(), '_yyyyMMdd_HHmmss'));
});
</script>