车辆管理

This commit is contained in:
hantao 2025-06-25 18:20:34 +08:00
parent be0c2fa3a2
commit d8956b42bd
10 changed files with 413 additions and 115 deletions

View File

@ -2,10 +2,13 @@
namespace app\admin\controller;
use app\admin\model\Admin;
use app\admin\model\car\AttributeValue;
use app\common\controller\Backend;
use think\Db;
use think\exception\PDOException;
use think\exception\ValidateException;
use function Symfony\Component\Clock\now;
/**
*
@ -20,6 +23,7 @@ class Cars extends Backend
* @var \app\admin\model\Cars
*/
protected $model = null;
protected $series,$brands;
public function _initialize()
{
@ -28,6 +32,10 @@ class Cars extends Backend
$series = Db::query("SELECT a.id,brand_id as pid, a.name FROM series a LEFT JOIN brands b ON a.brand_id = b.id;");
$brands = Db::query("SELECT id, 0 as pid, `name` FROM brands;");
$this->series = $series;
$this->brands = $brands;
$data = array_merge($brands,$series);
$tree = $this->buildTree($data);
@ -42,6 +50,37 @@ class Cars extends Backend
}
public function index()
{
//设置过滤方法
$this->request->filter(['strip_tags', 'trim']);
if (false === $this->request->isAjax()) {
return $this->view->fetch();
}
//如果发送的来源是 Selectpage则转发到 Selectpage
if ($this->request->request('keyField')) {
return $this->selectpage();
}
[$where, $sort, $order, $offset, $limit] = $this->buildparams();
$list = $this->model
->where($where)
->order($sort, $order)
->with([
'contact' =>function ($q) {
$q->field('id,nickname,mobile');
},
'brand' =>function ($q) {
$q->field('id,name');
},
'series' =>function ($q) {
$q->field('id,name');
},
]
)
->paginate($limit);
$result = ['total' => $list->total(), 'rows' => $list->items()];
return json($result);
}
/**
@ -73,7 +112,11 @@ class Cars extends Backend
$validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.add' : $name) : $this->modelValidate;
$this->model->validateFailException()->validate($validate);
}
$result = $this->model->allowField(true)->save($params);
$data = $this->getModelData($params);
$data['created_at'] = now()->format('Y-m-d H:i:s');
$data['updated_at'] = now()->format('Y-m-d H:i:s');
$result = $this->model->allowField(true)->save($data);
$this->dealExtend($params,$this->model->id);
Db::commit();
} catch (ValidateException|PDOException|\Exception $e) {
Db::rollback();
@ -86,4 +129,124 @@ class Cars extends Backend
}
public function edit($ids = null)
{
$row = $this->model->get($ids);
if (!$row) {
$this->error(__('No Results were found'));
}
$adminIds = $this->getDataLimitAdminIds();
if (is_array($adminIds) && !in_array($row[$this->dataLimitField], $adminIds)) {
$this->error(__('You have no permission'));
}
if (false === $this->request->isPost()) {
$series_map = array_column($this->series,'name','id');
$series = $series_map[$row->series_id] ?? '';
$brand_map = array_column($this->brands,'name','id');
$brand = $brand_map[$row->brand_id] ?? '';
$series_name = $brand . ' / ' . $series;
$row->series_name = $series_name;
$this->view->assign('row', $row);
return $this->view->fetch();
}
$params = $this->request->post('row/a');
if (empty($params)) {
$this->error(__('Parameter %s can not be empty', ''));
}
$params = $this->preExcludeFields($params);
$result = false;
Db::startTrans();
try {
//是否采用模型验证
if ($this->modelValidate) {
$name = str_replace("\\model\\", "\\validate\\", get_class($this->model));
$validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.edit' : $name) : $this->modelValidate;
$row->validateFailException()->validate($validate);
}
$data = $this->getModelData($params);
$data['updated_at'] = now()->format('Y-m-d H:i:s');
$result = $row->allowField(true)->save($data);
$this->dealExtend($params,$row->id);
Db::commit();
} catch (ValidateException|PDOException|Exception $e) {
Db::rollback();
$this->error($e->getMessage());
}
if (false === $result) {
$this->error(__('No rows were updated'));
}
$this->success();
}
public function extend(){
$data = \app\admin\model\car\Attributes::order('sort_order')->select();
$id = request()->get('id');
$value_map = [];
if ($id){
$values = AttributeValue::where('car_id',$id)->select();
// dd($values);
foreach ($values as $value){
$value_map[$value->attribute_id] = $value->value;
}
}
$res = [];
foreach ($data as $datum){
$datum->options = json_decode($datum->options);
$re = $datum->toArray();
$out = [
'id' => $re['id'],
'label' => $re['name'],
'type' => $re['input_type'],
'options' => $re['options'],
'name' => $re['field_key'],
'value' => $value_map[$re['id']] ?? null,
];
$res [] =$out;
}
$this->success(data:$res);
}
private function dealExtend(array $params,$car_id)
{
$data = \app\admin\model\car\Attributes::order('sort_order')->field('id,field_key')->select();
$insert = [];
// dd($data);
foreach ($data as $datum){
if ($params[$datum['field_key']]){
$insert [] = [
'car_id' => $car_id,
'attribute_id' => $datum->id,
'value' => $params[$datum['field_key']],
];
}
}
// dd($insert);
AttributeValue::where('car_id',$car_id)->delete();
AttributeValue::insertAll($insert);
}
private function getModelData(array $params)
{
$res = [
'title' => $params['title'],
'price' => $params['price'],
'cover_image' => $params['cover_image'],
'contact_id' => $params['contact_id'] ?? $this->auth->id,
];
$series_id = $params['series_id'];
$series = \app\admin\model\Series::where('id',$series_id)->find();
$res ['series_id'] = $series_id;
$res ['brand_id'] = $series->brand_id;
if (!$res['contact_id'] || $res['contact_id'] == -1){
$res['contact_id'] = $this->auth->id;
}
return $res;
}
}

View File

@ -221,4 +221,6 @@ return [
'User Module' => '会员模块',
'Register' => '注册',
'User Center' => '会员中心',
'Created_at' => '创建时间',
'Updated_at' => '更新时间',
];

View File

@ -8,5 +8,7 @@ return [
'Cover_image' => '图片',
'Location' => '城市名',
'Contact_id' => '联系人',
'Contact_name' => '联系人',
'Contact_phone' => '联系电话',
'Is_active' => '是否上架'
];

View File

@ -29,6 +29,20 @@ class Cars extends Model
];
public function contact()
{
return $this->belongsTo(Admin::class,'contact_id');
}
public function brand()
{
return $this->belongsTo(Brands::class,'brand_id');
}
public function series()
{
return $this->belongsTo(Series::class,'series_id');
}

View File

@ -0,0 +1,49 @@
<?php
namespace app\admin\model\car;
use think\Model;
class AttributeValue extends Model
{
// 表名
protected $table = 'car_attribute_values';
// 自动写入时间戳字段
protected $autoWriteTimestamp = false;
// 定义时间戳字段名
protected $createTime = 'created_at';
protected $updateTime = false;
protected $deleteTime = false;
// 追加属性
protected $append = [
'input_type_text'
];
public function getInputTypeList()
{
return ['text' => __('Text'), 'select' => __('Select'), 'range' => __('Range'), 'checkbox' => __('Checkbox')];
}
public function getInputTypeTextAttr($value, $data)
{
$value = $value ?: ($data['input_type'] ?? '');
$list = $this->getInputTypeList();
return $list[$value] ?? '';
}
}

View File

@ -12,7 +12,8 @@
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Series_id')}:</label>
<div class="col-xs-12 col-sm-8">
<input id="c-series_id" data-rule="required" class="form-control" name="row[series_id]" type="text" value="">
<input id="c-series_id" data-rule="required" class="form-control" type="text" value="" autocomplete="off">
<input id="c-series_id_value" data-rule="required" class="form-control" name="row[series_id]" type="hidden" value="">
</div>
</div>
<div class="form-group">
@ -39,8 +40,8 @@
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Contact_id')}:</label>
<div class="col-xs-12 col-sm-8">
<select id="c-users" data-live-search="true" title="默认当前登录账号" name="row[admin_id]" class="form-control selectpicker show-tick">
<option value="-1">默认当前登录账号</option>
<select id="c-users" data-live-search="true" title="默认当前登录账号" name="row[contact_id]" class="form-control selectpicker show-tick">
<option selected value="-1">默认当前登录账号</option>
{foreach $users as $item}
<option value="{$item['id']}">{$item['nickname']}</option>
{/foreach}
@ -57,12 +58,6 @@
</select>
</div>
</div>
<div class="form-group layer-footer">
<label class="control-label col-xs-12 col-sm-2"></label>
<div class="col-xs-12 col-sm-8">
<button type="submit" class="btn btn-primary btn-embossed disabled">{:__('OK')}</button>
</div>
</div>
</form>
</div>

View File

@ -1,95 +1,67 @@
<form id="edit-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
<link href="/assets/css/car/index.css" rel="stylesheet">
<link rel="stylesheet" href="/assets/css/car/select.css">
<div class="my-cart">
<form id="add-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
<input id="c-id" data-rule="required" class="form-control" name="row[id]" type="hidden" value="{$row.id}">
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Title')}:</label>
<div class="col-xs-12 col-sm-8">
<input id="c-title" class="form-control" name="row[title]" type="text" value="{$row.title|htmlentities}">
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Brand_id')}:</label>
<div class="col-xs-12 col-sm-8">
<input id="c-brand_id" data-rule="required" data-source="brand/index" class="form-control selectpage" name="row[brand_id]" type="text" value="{$row.brand_id|htmlentities}">
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Series_id')}:</label>
<div class="col-xs-12 col-sm-8">
<input id="c-series_id" data-rule="required" data-source="series/index" class="form-control selectpage" name="row[series_id]" type="text" value="{$row.series_id|htmlentities}">
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Price')}:</label>
<div class="col-xs-12 col-sm-8">
<input id="c-price" class="form-control" step="0.01" name="row[price]" type="number" value="{$row.price|htmlentities}">
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Cover_image')}:</label>
<div class="col-xs-12 col-sm-8">
<div class="input-group">
<input id="c-cover_image" class="form-control" size="50" name="row[cover_image]" type="text" value="{$row.cover_image|htmlentities}">
<div class="input-group-addon no-border no-padding">
<span><button type="button" id="faupload-cover_image" class="btn btn-danger faupload" data-input-id="c-cover_image" data-mimetype="image/gif,image/jpeg,image/png,image/jpg,image/bmp,image/webp" data-multiple="false" data-preview-id="p-cover_image"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
<span><button type="button" id="fachoose-cover_image" class="btn btn-primary fachoose" data-input-id="c-cover_image" data-mimetype="image/*" data-multiple="false"><i class="fa fa-list"></i> {:__('Choose')}</button></span>
</div>
<span class="msg-box n-right" for="c-cover_image"></span>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Title')}:</label>
<div class="col-xs-12 col-sm-8">
<input id="c-title" class="form-control" name="row[title]" value="{$row.title}" type="text">
</div>
<ul class="row list-inline faupload-preview" id="p-cover_image"></ul>
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Location')}:</label>
<div class="col-xs-12 col-sm-8">
<input id="c-location" class="form-control" name="row[location]" type="text" value="{$row.location|htmlentities}">
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Series_id')}:</label>
<div class="col-xs-12 col-sm-8">
<input id="c-series_id" data-rule="required" class="form-control" type="text" data-value="{$row.series_name}" autocomplete="off">
<input id="c-series_id_value" data-rule="required" class="form-control" name="row[series_id]" type="hidden" value="{$row.series_id}">
</div>
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Contact_name')}:</label>
<div class="col-xs-12 col-sm-8">
<input id="c-contact_name" class="form-control" name="row[contact_name]" type="text" value="{$row.contact_name|htmlentities}">
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Price')}:</label>
<div class="col-xs-12 col-sm-8">
<input id="c-price" class="form-control" step="0.01" name="row[price]" value="{$row.price}" type="number">
</div>
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Contact_id')}:</label>
<div class="col-xs-12 col-sm-8">
<input id="c-contact_id" data-rule="required" data-source="contact/index" class="form-control selectpage" name="row[contact_id]" type="text" value="{$row.contact_id|htmlentities}">
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Cover_image')}:</label>
<div class="col-xs-12 col-sm-8">
<div class="input-group">
<input id="c-cover_image" class="form-control" size="50" name="row[cover_image]" value="{$row.cover_image}" type="text">
<div class="input-group-addon no-border no-padding">
<span><button type="button" id="faupload-cover_image" class="btn btn-danger faupload" data-input-id="c-cover_image" data-mimetype="image/gif,image/jpeg,image/png,image/jpg,image/bmp,image/webp" data-multiple="true" data-preview-id="p-cover_image"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
<span><button type="button" id="fachoose-cover_image" class="btn btn-primary fachoose" data-input-id="c-cover_image" data-mimetype="image/*" data-multiple="true"><i class="fa fa-list"></i> {:__('Choose')}</button></span>
</div>
<span class="msg-box n-right" for="c-cover_image"></span>
</div>
<ul class="row list-inline faupload-preview" id="p-cover_image"></ul>
</div>
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Contact_phone')}:</label>
<div class="col-xs-12 col-sm-8">
<input id="c-contact_phone" class="form-control" name="row[contact_phone]" type="text" value="{$row.contact_phone|htmlentities}">
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Contact_id')}:</label>
<div class="col-xs-12 col-sm-8">
<select id="c-users" data-live-search="true" title="默认当前登录账号" name="row[contact_id]" class="form-control selectpicker show-tick">
<option value="-1">默认当前登录账号</option>
{foreach $users as $item}
<option {if $item['id'] == $row.contact_id} selected {/if} value="{$item['id']}">{$item['nickname']}</option>
{/foreach}
</select>
</div>
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Wechat_qrcode')}:</label>
<div class="col-xs-12 col-sm-8">
<input id="c-wechat_qrcode" class="form-control" name="row[wechat_qrcode]" type="text" value="{$row.wechat_qrcode|htmlentities}">
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Is_active')}:</label>
<div class="col-xs-12 col-sm-8">
<select name="row[is_active]" class="form-control selectpicker">
<option {if 1 == $row.is_active} selected {/if} value="1">上架</option>
<option {if 0 == $row.is_active} selected {/if} value="0">不上架</option>
</select>
</div>
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Created_at')}:</label>
<div class="col-xs-12 col-sm-8">
<input id="c-created_at" class="form-control datetimepicker" data-date-format="YYYY-MM-DD HH:mm:ss" data-use-current="true" name="row[created_at]" type="text" value="{$row.created_at}">
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Updated_at')}:</label>
<div class="col-xs-12 col-sm-8">
<input id="c-updated_at" class="form-control datetimepicker" data-date-format="YYYY-MM-DD HH:mm:ss" data-use-current="true" name="row[updated_at]" type="text" value="{$row.updated_at}">
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Is_active')}:</label>
<div class="col-xs-12 col-sm-8">
<input id="c-is_active" class="form-control" name="row[is_active]" type="number" value="{$row.is_active|htmlentities}">
</div>
</div>
<div class="form-group layer-footer">
<label class="control-label col-xs-12 col-sm-2"></label>
<div class="col-xs-12 col-sm-8">
<button type="submit" class="btn btn-primary btn-embossed disabled">{:__('OK')}</button>
</div>
</div>
</form>
</form>
</div>
<script>
var series = {:json_encode($series); };
</script>

View File

@ -29,7 +29,8 @@
"ext-json": "*",
"ext-curl": "*",
"ext-pdo": "*",
"ext-bcmath": "*"
"ext-bcmath": "*",
"nesbot/carbon": "^3.10"
},
"config": {
"preferred-install": "dist",

View File

@ -5,4 +5,5 @@
padding-left:15px;
padding-right:15px;
padding-top:10px;
overflow-y:auto;
}

View File

@ -1,4 +1,4 @@
define(['jquery', 'bootstrap', 'backend', 'table', 'form','cascader'], function ($, undefined, Backend, Table, Form) {
define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'cascader'], function ($, undefined, Backend, Table, Form) {
var Controller = {
index: function () {
@ -28,20 +28,53 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form','cascader'], function
[
{checkbox: true},
{field: 'id', title: __('Id')},
{field: 'title', title: __('Title'), operate: 'LIKE', table: table, class: 'autocontent', formatter: Table.api.formatter.content},
{field: 'brand_id', title: __('Brand_id')},
{field: 'series_id', title: __('Series_id')},
{field: 'price', title: __('Price'), operate:'BETWEEN'},
{field: 'cover_image', title: __('Cover_image'), operate: false, events: Table.api.events.image, formatter: Table.api.formatter.image},
{field: 'location', title: __('Location'), operate: 'LIKE'},
{field: 'contact_name', title: __('Contact_name'), operate: 'LIKE'},
{field: 'contact_id', title: __('Contact_id')},
{field: 'contact_phone', title: __('Contact_phone'), operate: 'LIKE'},
{field: 'wechat_qrcode', title: __('Wechat_qrcode'), operate: 'LIKE', table: table, class: 'autocontent', formatter: Table.api.formatter.content},
{field: 'created_at', title: __('Created_at'), operate:'RANGE', addclass:'datetimerange', autocomplete:false},
{field: 'updated_at', title: __('Updated_at'), operate:'RANGE', addclass:'datetimerange', autocomplete:false},
{field: 'is_active', title: __('Is_active')},
{field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate}
{
field: 'title',
title: __('Title'),
operate: 'LIKE',
table: table,
class: 'autocontent',
formatter: Table.api.formatter.content
},
{field: 'brand.name', title: __('Brand_id')},
{field: 'series.name', title: __('Series_id')},
{field: 'price', title: __('Price'), operate: 'BETWEEN'},
{
field: 'cover_image',
title: __('Cover_image'),
operate: false,
events: Table.api.events.image,
formatter: Table.api.formatter.image
},
{field: 'contact.nickname', title: __('Contact_name'), operate: 'LIKE'},
{field: 'contact.mobile', title: __('Contact_phone'), operate: 'LIKE'},
{
field: 'created_at',
title: __('Created_at'),
operate: 'RANGE',
addclass: 'datetimerange',
autocomplete: false
},
{
field: 'updated_at',
title: __('Updated_at'),
operate: 'RANGE',
addclass: 'datetimerange',
autocomplete: false
},
{field: 'is_active', title: __('Is_active'),
searchList: {
"0": '下架',
"1": '上架',
},
formatter: Table.api.formatter.status},
{
field: 'operate',
title: __('Operate'),
table: table,
events: Table.api.events.operate,
formatter: Table.api.formatter.operate
}
]
]
});
@ -52,25 +85,91 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form','cascader'], function
add: function () {
Controller.api.bindevent();
Controller.api.series();
Controller.api.extend();
},
edit: function () {
Controller.api.bindevent();
Controller.api.series();
Controller.api.extend();
},
api: {
bindevent: function () {
Form.api.bindevent($("form[role=form]"));
},
series:function (){
series: function () {
var _data = series;
$('#c-series_id').zdCascader({
data: _data,
onChange: function ($this, data, allPathData) {
// console.log(data,allPathData);
$('#item_id_value').val(data.value);
$('#c-series_id_value').val(data.value);
}
});
$('#c-series_id').val($('#c-series_id').data('value'));
},
extend: function () {
const id = $('input[name="row[id]"]').val();
let url = "cars/extend";
if (id) {
url += '&id=' + id;
}
Fast.api.ajax({
url: url, // 你的 API 地址
type: "get",
contentType: 'application/json',
dataType: "json"
},
function (data) {
var container = $('#add-form');
data.forEach(function (field) {
let html = `
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">${field.label}:</label>
<div class="col-xs-12 col-sm-8">`;
// ✅ 单选框radio替代 checkbox
if (field.type === 'checkbox' || field.type === 'radio') {
field.options.forEach(function (opt) {
// 是否选中
const checked = String(field.value) === String(opt.value) ? 'checked' : '';
html += `
<label class="radio-inline">
<input data-rule="required" type="radio" name="row[${field.name}]" value="${opt.value}" ${checked}> ${opt.key}
</label>`;
});
}
// ✅ 数值范围输入(单 input 版本)
else if (field.type === 'range') {
const opts = field.options || {};
const val = field.value ?? ''; // 可能是字符串、数字或空
const min = opts.start ?? '';
const max = opts.end ?? '';
const unit = opts.unit ?? '';
html += `
<div class="input-group">
<input data-rule="required" type="number" name="row[${field.name}]"
class="form-control" placeholder="${unit}"
min="${min}" max="${max}" value="${val}">
<span class="input-group-addon">${unit}</span>
</div>`;
}
html += `</div></div>`;
container.append(html);
});
// ✅ 添加提交按钮区域
container.append(`
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2"></label>
<div class="col-xs-12 col-sm-8">
<button type="submit" class="btn btn-primary btn-embossed">提交</button>
</div>
</div>`);
return false;
})
}
}
};