liuhairui 2 hafta önce
ebeveyn
işleme
006e3641b3

+ 213 - 16
application/admin/controller/Customer.php

@@ -2,32 +2,32 @@
 
 namespace app\admin\controller;
 
+use app\admin\model\Customer as CustomerModel;
 use app\common\controller\Backend;
 use think\Db;
+use think\exception\PDOException;
+use think\exception\ValidateException;
+use Exception;
 
 /**
- *
+ * 客户管理(customer 表,含 H5 登录账号)
  *
  * @icon fa fa-circle-o
  */
 class Customer extends Backend
 {
-
     /**
-     * Customer模型对象
      * @var \app\admin\model\Customer
      */
     protected $model = null;
 
-    /**
-     * 弹窗内 AJAX 拉取业务分类候选,免配菜单权限(与路由 action 小写一致)
-     */
     protected $noNeedRight = ['company_type_options', 'companyTypeOptions'];
 
     public function _initialize()
     {
         parent::_initialize();
-        $this->model = new \app\admin\model\Customer;
+        $this->model = new CustomerModel;
+        $this->view->assign('statusList', $this->model->getStatusList());
 
         $action = strtolower((string)$this->request->action());
         if (in_array($action, ['add', 'edit'], true)) {
@@ -40,9 +40,6 @@ class Customer extends Backend
         }
     }
 
-    /**
-     * 从 customer.company_type 拆词去重,供新增/编辑多选
-     */
     public function companyTypeOptions()
     {
         try {
@@ -50,7 +47,7 @@ class Customer extends Backend
         } catch (\Throwable $e) {
             $list = [];
         }
-        // 直接 JSON,避免 success 参数顺序与 isAjax 在弹层内不一致导致前端拿不到 list
+
         return json(['code' => 1, 'msg' => '', 'data' => ['list' => $list]]);
     }
 
@@ -78,12 +75,212 @@ class Customer extends Backend
         }
         $list = array_keys($set);
         sort($list, SORT_STRING);
+
         return $list;
     }
 
-    /*
-     * 新增/编辑/删除:未在本类重写时,直接使用父类混入的 \app\admin\library\traits\Backend 中的 add/edit/del(弹窗提交、校验、入库等)。
-     * 本模块相关逻辑位置:业务分类下拉数据在 _initialize 里 assign;表单与多选在 view + public/assets/js/backend/customer.js;入库前字段处理在 model Customer。
-     * 若要在保存前后加自定义逻辑,再把 trait 里对应方法复制到本类并修改(勿只写 parent::add() 空壳)。
-     */
+    public function index()
+    {
+        $this->relationSearch = false;
+        $this->request->filter(['strip_tags', 'trim']);
+        if ($this->request->isAjax()) {
+            if ($this->request->request('keyField')) {
+                return $this->selectpage();
+            }
+            list($where, $sort, $order, $offset, $limit) = $this->buildparams();
+
+            $list = $this->model
+                ->where($where)
+                ->order($sort, $order)
+                ->paginate($limit);
+
+            foreach ($list as $row) {
+                $row->visible([
+                    'id', 'company_name', 'username', 'account', 'email', 'phone', 'company_type',
+                    'createtime', 'updatetime', 'status',
+                ]);
+            }
+
+            return json(['total' => $list->total(), 'rows' => $list->items()]);
+        }
+
+        return $this->view->fetch();
+    }
+
+    public function add()
+    {
+        if (false === $this->request->isPost()) {
+            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);
+
+        $phone = trim((string)($params['phone'] ?? ''));
+        $phone = str_replace([',', ';', ';', '|', '|', "\n", "\r", "\t"], ',', $phone);
+        if (strpos($phone, ',') !== false) {
+            $phone = trim(explode(',', $phone)[0]);
+        }
+        if ($phone === '') {
+            $this->error('请填写手机号');
+        }
+        if (!preg_match('/^1\d{10}$/', $phone)) {
+            $this->error('手机号须为11位');
+        }
+
+        $account = trim((string)($params['account'] ?? ''));
+        if ($account === '') {
+            $account = $phone;
+        }
+        if (!preg_match('/^1\d{10}$/', $account)) {
+            $this->error('登录账号须为11位手机号');
+        }
+        if (Db::name('customer')->where('phone', $phone)->find()) {
+            $this->error('该手机号已存在');
+        }
+        if (Db::name('customer')->where('account', $account)->find()) {
+            $this->error('该登录账号已存在');
+        }
+
+        $email = trim((string)($params['email'] ?? ''));
+        $email = str_replace([',', ';', ';', '|', '|', "\n", "\r", "\t"], ',', $email);
+        if (strpos($email, ',') !== false) {
+            $email = trim(explode(',', $email)[0]);
+        }
+
+        $pwd = trim((string)($params['password'] ?? ''));
+        if ($pwd === '') {
+            $pwd = $phone;
+        }
+
+        $status = isset($params['status']) ? (string)$params['status'] : '';
+        if ($status === '') {
+            $status = '1';
+        }
+
+        $now = date('Y-m-d H:i:s');
+        $data = [
+            'company_name' => trim((string)($params['company_name'] ?? '')),
+            'username'     => trim((string)($params['username'] ?? '')),
+            'phone'        => $phone,
+            'account'      => $account,
+            'email'        => $email,
+            'password'     => md5(md5($pwd)),
+            'company_type' => trim((string)($params['company_type'] ?? '')),
+            'status'       => $status,
+            'createtime'   => $now,
+            'updatetime'   => $now,
+        ];
+
+        if ($this->dataLimit && $this->dataLimitFieldAutoFill) {
+            $data[$this->dataLimitField] = $this->auth->id;
+        }
+
+        $v = validate('app\\admin\\validate\\Customer');
+        if (!$v->scene('add')->check($data)) {
+            $this->error($v->getError());
+        }
+
+        try {
+            Db::name('customer')->insert($data);
+        } catch (PDOException|Exception $e) {
+            $this->error($e->getMessage());
+        }
+        $this->success();
+    }
+
+    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()) {
+            $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);
+        $rowId = (int)$row['id'];
+
+        $phone = trim((string)($params['phone'] ?? ''));
+        $phone = str_replace([',', ';', ';', '|', '|', "\n", "\r", "\t"], ',', $phone);
+        if (strpos($phone, ',') !== false) {
+            $phone = trim(explode(',', $phone)[0]);
+        }
+        if ($phone === '') {
+            $this->error('请填写手机号');
+        }
+        if (!preg_match('/^1\d{10}$/', $phone)) {
+            $this->error('手机号须为11位');
+        }
+
+        $account = trim((string)($params['account'] ?? ''));
+        if ($account === '') {
+            $account = $phone;
+        }
+        if (!preg_match('/^1\d{10}$/', $account)) {
+            $this->error('登录账号须为11位手机号');
+        }
+        if (Db::name('customer')->where('phone', $phone)->where('id', '<>', $rowId)->find()) {
+            $this->error('该手机号已存在');
+        }
+        if (Db::name('customer')->where('account', $account)->where('id', '<>', $rowId)->find()) {
+            $this->error('该登录账号已存在');
+        }
+
+        $email = trim((string)($params['email'] ?? ''));
+        $email = str_replace([',', ';', ';', '|', '|', "\n", "\r", "\t"], ',', $email);
+        if (strpos($email, ',') !== false) {
+            $email = trim(explode(',', $email)[0]);
+        }
+
+        $status = isset($params['status']) ? (string)$params['status'] : '1';
+        if ($status === '') {
+            $status = '1';
+        }
+
+        $data = [
+            'id'           => $rowId,
+            'company_name' => trim((string)($params['company_name'] ?? '')),
+            'username'     => trim((string)($params['username'] ?? '')),
+            'phone'        => $phone,
+            'account'      => $account,
+            'email'        => $email,
+            'company_type' => trim((string)($params['company_type'] ?? '')),
+            'status'       => $status,
+            'updatetime'   => date('Y-m-d H:i:s'),
+        ];
+
+        $pwd = trim((string)($params['password'] ?? ''));
+        if ($pwd !== '') {
+            $data['password'] = md5(md5($pwd));
+        }
+
+        $v = validate('app\\admin\\validate\\Customer');
+        if (!$v->scene('edit')->check($data)) {
+            $this->error($v->getError());
+        }
+
+        unset($data['id']);
+        try {
+            $aff = Db::name('customer')->where('id', $rowId)->update($data);
+        } catch (PDOException|Exception $e) {
+            $this->error($e->getMessage());
+        }
+        if ($aff === false) {
+            $this->error(__('No rows were updated'));
+        }
+        $this->success();
+    }
 }

+ 8 - 0
application/admin/lang/zh-cn/customer.php

@@ -1,9 +1,17 @@
 <?php
 
 return [
+    'Id'           => '序号',
     'Company_name' => '客户名称',
     'Username'     => '姓名',
+    'Account'      => '登录账号',
+    'Password'     => '密码',
     'Email'        => '邮箱',
     'Phone'        => '手机号',
     'Company_type' => '业务分类',
+    'Createtime'   => '创建时间',
+    'Updatetime'   => '更新时间',
+    'Status'       => '状态',
+    'Status 1'     => '正常',
+    'Status 0'     => '禁止登录',
 ];

+ 49 - 19
application/admin/model/Customer.php

@@ -4,25 +4,63 @@ namespace app\admin\model;
 
 use think\Model;
 
-
 class Customer extends Model
 {
-    // 表名
     protected $table = 'customer';
-    
-    // 自动写入时间戳字段
+
+    // 时间由控制器直接写入 datetime 字符串,避免自动写成 Unix 时间戳
     protected $autoWriteTimestamp = false;
 
-    // 定义时间戳字段名
     protected $createTime = false;
     protected $updateTime = false;
     protected $deleteTime = false;
 
-    // 追加属性
     protected $append = [
-
+        'status_text',
     ];
 
+    public function getStatusList()
+    {
+        return [
+            '1' => __('Status 1'),
+            '0' => __('Status 0'),
+        ];
+    }
+
+    public function getStatusTextAttr($value, $data)
+    {
+        $value = $value !== '' && $value !== null ? $value : (isset($data['status']) ? $data['status'] : '');
+        $list = $this->getStatusList();
+
+        return isset($list[(string)$value]) ? $list[(string)$value] : '';
+    }
+
+    /**
+     * 与手机端登录一致:md5(md5(明文))
+     */
+    public static function hashPassword(string $plain): string
+    {
+        return md5(md5($plain));
+    }
+
+    /**
+     * 邮箱、手机号仅保留单个值(去掉逗号等多值写法)
+     */
+    public static function normalizeSingleContact(string $value): string
+    {
+        $value = trim($value);
+        if ($value === '') {
+            return '';
+        }
+        $value = str_replace([',', ';', ';', '|', '|', "\n", "\r", "\t"], ',', $value);
+        $parts = preg_split('/\s*,\s*/', $value, -1, PREG_SPLIT_NO_EMPTY);
+        if (!$parts) {
+            return $value;
+        }
+
+        return trim((string)$parts[0]);
+    }
+
     protected static function init()
     {
         self::beforeWrite(function ($model) {
@@ -31,19 +69,11 @@ class Customer extends Model
                 if (!array_key_exists($field, $data)) {
                     continue;
                 }
-                $v = $data[$field];
-                if ($v === null || $v === '') {
-                    continue;
-                }
-                $v = str_replace([',', ';', ';', '|', '|', "\n", "\r", "\t"], ',', (string)$v);
-                $parts = preg_split('/\s*,\s*/', $v, -1, PREG_SPLIT_NO_EMPTY);
-                $parts = array_map('trim', $parts);
-                $parts = array_filter($parts, function ($s) {
-                    return $s !== '';
-                });
-                $model->setAttr($field, implode(',', $parts));
+                $model->setAttr($field, self::normalizeSingleContact((string)$data[$field]));
+            }
+            if (array_key_exists('account', $data)) {
+                $model->setAttr('account', trim((string)$data['account']));
             }
         });
     }
-
 }

+ 19 - 12
application/admin/validate/Customer.php

@@ -6,22 +6,29 @@ use think\Validate;
 
 class Customer extends Validate
 {
-    /**
-     * 验证规则
-     */
     protected $rule = [
+        'company_name' => 'require',
+        'username'     => 'require',
+        'phone'        => 'require|regex:^1\d{10}$|unique:customer,phone^id',
+        'account'      => 'regex:^1\d{10}$|unique:customer,account^id',
+        'email'        => 'email',
+        'status'       => 'in:0,1',
     ];
-    /**
-     * 提示消息
-     */
+
     protected $message = [
+        'company_name.require' => '请填写客户名称',
+        'username.require'     => '请填写姓名',
+        'phone.require'        => '请填写手机号',
+        'phone.regex'          => '手机号须为11位',
+        'phone.unique'         => '该手机号已存在',
+        'account.regex'        => '登录账号须为11位手机号',
+        'account.unique'       => '该登录账号已存在',
+        'email.email'          => '邮箱格式不正确',
+        'status.in'            => '状态无效',
     ];
-    /**
-     * 验证场景
-     */
+
     protected $scene = [
-        'add'  => [],
-        'edit' => [],
+        'add'  => ['company_name', 'username', 'phone', 'account', 'email', 'status'],
+        'edit' => ['company_name', 'username', 'phone', 'account', 'email', 'status'],
     ];
-    
 }

+ 28 - 6
application/admin/view/customer/add.html

@@ -3,25 +3,37 @@
     <div class="form-group">
         <label class="control-label col-xs-12 col-sm-2">{:__('Company_name')}:</label>
         <div class="col-xs-12 col-sm-8">
-            <input id="c-company_name" class="form-control" name="row[company_name]" type="text">
+            <input id="c-company_name" class="form-control" name="row[company_name]" type="text" data-rule="required">
         </div>
     </div>
     <div class="form-group">
         <label class="control-label col-xs-12 col-sm-2">{:__('Username')}:</label>
         <div class="col-xs-12 col-sm-8">
-            <input id="c-username" class="form-control" name="row[username]" type="text">
+            <input id="c-username" class="form-control" name="row[username]" type="text" data-rule="required">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Phone')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-phone" class="form-control" name="row[phone]" type="tel" maxlength="11" data-rule="required;mobile">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Account')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-account" class="form-control" name="row[account]" type="tel" maxlength="11" placeholder="手机号相同">
         </div>
     </div>
     <div class="form-group">
         <label class="control-label col-xs-12 col-sm-2">{:__('Email')}:</label>
         <div class="col-xs-12 col-sm-8">
-            <input id="c-email" class="form-control" name="row[email]" type="text" placeholder="多条请用英文逗号 , 分隔">
+            <input id="c-email" class="form-control" name="row[email]" type="email">
         </div>
     </div>
     <div class="form-group">
-        <label class="control-label col-xs-12 col-sm-2">{:__('Phone')}:</label>
+        <label class="control-label col-xs-12 col-sm-2">{:__('Password')}:</label>
         <div class="col-xs-12 col-sm-8">
-            <input id="c-phone" class="form-control" name="row[phone]" type="text" placeholder="多条请用英文逗号 , 分隔">
+            <input id="c-password" class="form-control" name="row[password]" type="password" autocomplete="new-password" placeholder="留空则默认与手机号相同">
         </div>
     </div>
     <div class="form-group company-type-form-group">
@@ -36,7 +48,17 @@
                 </select>
                 <span class="input-group-addon" id="c-company_type_count" style="min-width:6.5em;text-align:center;">已选 0</span>
             </div>
-            <p class="help-block" style="margin-top:6px;">下拉中搜索;列表显示「无匹配」时,在搜索框内按 <strong>Enter</strong> 可将当前文字添加为新分类并选中。</p>
+            <p class="help-block" style="margin-top:6px;">列表显示「无匹配」时,可将当前分类添加为新分类。</p>
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <div class="radio">
+            {foreach name="statusList" item="vo"}
+            <label for="row[status]-{$key}"><input id="row[status]-{$key}" name="row[status]" type="radio" value="{$key}" {eq name="key" value="1"}checked{/eq} /> {$vo}</label>
+            {/foreach}
+            </div>
         </div>
     </div>
     <div class="form-group layer-footer">

+ 28 - 6
application/admin/view/customer/edit.html

@@ -3,25 +3,37 @@
     <div class="form-group">
         <label class="control-label col-xs-12 col-sm-2">{:__('Company_name')}:</label>
         <div class="col-xs-12 col-sm-8">
-            <input id="c-company_name" class="form-control" name="row[company_name]" type="text" value="{$row.company_name|htmlentities}">
+            <input id="c-company_name" class="form-control" name="row[company_name]" type="text" value="{$row.company_name|htmlentities}" data-rule="required">
         </div>
     </div>
     <div class="form-group">
         <label class="control-label col-xs-12 col-sm-2">{:__('Username')}:</label>
         <div class="col-xs-12 col-sm-8">
-            <input id="c-username" class="form-control" name="row[username]" type="text" value="{$row.username|htmlentities}">
+            <input id="c-username" class="form-control" name="row[username]" type="text" value="{$row.username|htmlentities}" data-rule="required">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Phone')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-phone" class="form-control" name="row[phone]" type="tel" maxlength="11" value="{$row.phone|htmlentities}" data-rule="required;mobile">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Account')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-account" class="form-control" name="row[account]" type="tel" maxlength="11" value="{$row.account|htmlentities}" placeholder="留空则与手机号相同">
         </div>
     </div>
     <div class="form-group">
         <label class="control-label col-xs-12 col-sm-2">{:__('Email')}:</label>
         <div class="col-xs-12 col-sm-8">
-            <input id="c-email" class="form-control" name="row[email]" type="text" value="{$row.email|htmlentities}" placeholder="多条请用英文逗号 , 分隔">
+            <input id="c-email" class="form-control" name="row[email]" type="email" value="{$row.email|htmlentities}">
         </div>
     </div>
     <div class="form-group">
-        <label class="control-label col-xs-12 col-sm-2">{:__('Phone')}:</label>
+        <label class="control-label col-xs-12 col-sm-2">{:__('Password')}:</label>
         <div class="col-xs-12 col-sm-8">
-            <input id="c-phone" class="form-control" name="row[phone]" type="text" value="{$row.phone|htmlentities}" placeholder="多条请用英文逗号 , 分隔">
+            <input id="c-password" class="form-control" name="row[password]" type="password" autocomplete="new-password" placeholder="留空表示不修改密码">
         </div>
     </div>
     <div class="form-group company-type-form-group">
@@ -36,7 +48,17 @@
                 </select>
                 <span class="input-group-addon" id="c-company_type_count" style="min-width:6.5em;text-align:center;">已选 0</span>
             </div>
-            <p class="help-block">点击展开下拉可多选;已选前有√;右侧显示已选数量;保存时多项以「、」连接。列表显示「无匹配」时,在搜索框内按 <strong>Enter</strong> 可添加为新分类。</p>
+            <p class="help-block" style="margin-top:6px;">列表显示「无匹配」时,可将当前分类添加为新分类。</p>
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <div class="radio">
+            {foreach name="statusList" item="vo"}
+            <label for="row[status]-{$key}"><input id="row[status]-{$key}" name="row[status]" type="radio" value="{$key}" {in name="key" value="$row.status"}checked{/in} /> {$vo}</label>
+            {/foreach}
+            </div>
         </div>
     </div>
     <div class="form-group layer-footer">

+ 14 - 0
application/admin/view/procuremen/review.html

@@ -284,6 +284,7 @@
     }
     .review-table-scroll .table-responsive {
         margin-bottom: 0;
+        overflow: visible;
     }
     .review-table-scroll table {
         margin-bottom: 0;
@@ -292,9 +293,22 @@
     .review-table-scroll .table > tbody > tr > td {
         padding: 5px 6px;
     }
+    /* 客户列表滚动时表头固定在滚动区域顶部 */
+    .review-table-scroll .review-company-table > thead > tr > th {
+        position: sticky;
+        top: 0;
+        z-index: 3;
+        background-color: #f5f5f5;
+        box-shadow: 0 1px 0 #ddd;
+    }
+    .review-table-scroll .review-company-table > thead > tr > th.review-th-cb {
+        z-index: 4;
+    }
     .review-company-table {
         table-layout: fixed;
         width: 100%;
+        border-collapse: separate;
+        border-spacing: 0;
     }
     /* 姓名、邮箱列收窄(colgroup 为主,此处补 max-width 防撑开) */
     .review-company-table > thead > tr > th:nth-child(3),

+ 49 - 0
application/api/controller/Index.php

@@ -11,7 +11,56 @@ class Index extends Api{
     public function index(){
         $this->success('请求新华接口成功');
     }
+    public function customer_index()
+    {
+        // 1. 查询:account有值 + password为空 的客户
+        $list = Db::name("customer")
+            ->where("account", "<>", "")
+            ->where(function ($query) {
+                $query->whereNull("password")
+                    ->whereOr("password", "");
+            })
+            ->select();
+
+        if (empty($list)) {
+            return json([
+                "code" => 1,
+                "msg"  => "所有密码已填充完毕,无需处理"
+            ]);
+        }
+
+        $success = 0;
+
+        // 循环逐条更新(FastAdmin/TP5 标准写法)
+        Db::startTrans();
+        try {
+            foreach ($list as $item) {
+                $account = trim($item['account']);
+                $password = md5(md5($account)); // FastAdmin 密码加密
+
+                Db::name("customer")
+                    ->where("id", $item['id'])
+                    ->update([
+                        "password" => $password
+                    ]);
 
+                $success++;
+            }
+
+            Db::commit();
+            return json([
+                "code" => 1,
+                "msg"  => "密码填充成功,共更新 " . $success . " 条"
+            ]);
+
+        } catch (\Exception $e) {
+            Db::rollback();
+            return json([
+                "code" => 0,
+                "msg"  => "失败:" . $e->getMessage()
+            ]);
+        }
+    }
     /** 定义的函数方法 $this->方法名调取*/
     //获取当前年份的一月份
     public function year_January(){

+ 16 - 0
application/extra/public_entry.php

@@ -0,0 +1,16 @@
+<?php
+
+/**
+ * public/index.php 入口行为(与 FastAdmin 分离后台入口配合使用)
+ *
+ * 后台实际入口为 public 目录下绑定 admin 的脚本(如 lqHKfByepX.php),
+ * index.php 默认进 index 模块(手机外发等前端)。若希望「只打开 index.php、URL 上无任何 GET、且无 path」时进入后台,
+ * 可将 index_bare_redirect_to_admin 设为 true,并填写与 public 目录一致的后台文件名。
+ * 注意:带 ?addtabs=1&sort=… 等(后台表格 Ajax 常走 index.php)时不会跳转,否则会请求失败。
+ */
+return [
+    /** 仅当 index.php 无 pathinfo、无 s 参数、且 Query 完全为空时,是否 302 到后台入口 */
+    'index_bare_redirect_to_admin' => true,
+    /** 后台入口文件名,须与 public 目录下实际文件一致(勿随意改名) */
+    'admin_bootstrap'              => 'lqHKfByepX.php',
+];

+ 132 - 100
application/index/controller/Index.php

@@ -4,14 +4,16 @@ namespace app\index\controller;
 
 use app\common\controller\Frontend;
 use think\Cache;
+use think\captcha\Captcha;
 use think\Config;
 use think\Cookie;
 use think\Db;
 use think\Session;
+use think\Validate;
 
 /**
  * 手机端:外发明细(purchase_order_detail)验证码 / 账号密码登录 + 列表
- * 普通用户:customer_user(手机号验证码 或 账号密码);管理员:admin 表账号密码(看全部、仅查看)
+ * 普通用户:customer 表(手机号验证码 或 登录账号+密码);管理员:admin 表账号密码(看全部、仅查看)
  */
 class Index extends Frontend
 {
@@ -59,7 +61,7 @@ class Index extends Frontend
             return null;
         }
         // 手机登录必有 phone;账号密码登录必有 username
-        if (empty($user['phone']) && empty($user['username'])) {
+        if (empty($user['phone']) && empty($user['account']) && empty($user['username'])) {
             return null;
         }
         if (time() - (int)($user['login_time'] ?? 0) > $this->mprocTtlSeconds) {
@@ -259,11 +261,15 @@ class Index extends Frontend
     }
 
     /**
-     * customer_user 是否允许登录(status:1 / 正常)
+     * customer 是否允许登录(status:1 / 正常;空视为可登录以兼容旧数据
      */
     protected function mprocCustomerUserActive(array $row): bool
     {
         $st = $row['status'] ?? '';
+        if ($st === '' || $st === null) {
+            return true;
+        }
+
         return $st === 1 || $st === '1';
     }
 
@@ -272,23 +278,12 @@ class Index extends Frontend
      */
     protected function mprocFindCustomerUserByMobile(string $phone): ?array
     {
-        $phone = trim($phone);
-        if ($phone === '' || !preg_match('/^1\d{10}$/', $phone)) {
-            return null;
-        }
-        try {
-            $row = Db::table('customer_user')->where('mobile', $phone)->order('id', 'asc')->find();
-        } catch (\Throwable $e) {
-            return null;
-        }
-        if (!is_array($row) || $row === [] || !$this->mprocCustomerUserActive($row)) {
-            return null;
-        }
-
-        return $row;
+        return $this->mprocFindCustomerRowByPhone($phone);
     }
 
     /**
+     * 按登录账号(account)查找 customer;兼容旧会话把 11 位手机号写在 username 里
+     *
      * @return array<string, mixed>|null
      */
     protected function mprocFindCustomerUserByUsername(string $username): ?array
@@ -298,9 +293,12 @@ class Index extends Frontend
             return null;
         }
         try {
-            $row = Db::table('customer_user')->where('username', $username)->order('id', 'asc')->find();
+            $row = Db::table('customer')->where('account', $username)->order('id', 'asc')->find();
         } catch (\Throwable $e) {
-            return null;
+            $row = null;
+        }
+        if ((!is_array($row) || $row === []) && preg_match('/^1\d{10}$/', $username)) {
+            $row = $this->mprocFindCustomerRowByPhone($username);
         }
         if (!is_array($row) || $row === [] || !$this->mprocCustomerUserActive($row)) {
             return null;
@@ -310,7 +308,7 @@ class Index extends Frontend
     }
 
     /**
-     * customer_user 密码校验(兼容 md5(md5+salt)、md5(md5) 无 salt)
+     * customer 密码校验(md5(md5) 无 salt;兼容 bcrypt)
      */
     protected function mprocVerifyCustomerUserPassword(array $row, string $password): bool
     {
@@ -321,18 +319,50 @@ class Index extends Frontend
         if (preg_match('/^\$2[ayb]\$/', $stored)) {
             return password_verify($password, $stored);
         }
-        $salt = (string)($row['salt'] ?? '');
-        $hash = md5(md5($password) . $salt);
 
-        return hash_equals($stored, $hash) || ($salt === '' && hash_equals($stored, md5(md5($password))));
+        return hash_equals($stored, md5(md5($password)));
     }
 
     /**
-     * 生成 customer_user 登录密码密文(与校验规则一致,保留原 salt)
+     * 生成 customer 登录密码密文
      */
     protected function mprocHashCustomerUserPassword(string $password, string $existingSalt = ''): string
     {
-        return md5(md5($password) . $existingSalt);
+        unset($existingSalt);
+
+        return md5(md5($password));
+    }
+
+    /**
+     * @param array<string, mixed> $cu
+     * @return array<string, mixed>
+     */
+    protected function mprocLoginPayloadFromCustomer(array $cu, string $loginType): array
+    {
+        $phone = trim((string)($cu['phone'] ?? ''));
+        $account = trim((string)($cu['account'] ?? ''));
+        if ($phone === '' && preg_match('/^1\d{10}$/', $account)) {
+            $phone = $account;
+        }
+        if ($account === '' && preg_match('/^1\d{10}$/', $phone)) {
+            $account = $phone;
+        }
+        $companyName = trim((string)($cu['company_name'] ?? ''));
+        if ($companyName === '' && $phone !== '') {
+            $companyName = $this->mprocResolveCompanyForLoginPhone($phone);
+        }
+        $id = (int)($cu['id'] ?? 0);
+
+        return [
+            'phone'            => $phone,
+            'account'          => $account,
+            'company_name'     => $companyName,
+            'username'         => trim((string)($cu['username'] ?? '')),
+            'customer_id'      => $id,
+            'customer_user_id' => $id,
+            'login_type'       => $loginType,
+            'is_admin'         => 0,
+        ];
     }
 
     /**
@@ -361,7 +391,7 @@ class Index extends Frontend
     }
 
     /**
-     * 登录手机号对应的外协单位名称:优先 customer_user;再 customer 表;否则 purchase_order_detail
+     * 登录手机号对应的外协单位名称:customer 表;否则 purchase_order_detail
      */
     protected function mprocResolveCompanyForLoginPhone(string $phone): string
     {
@@ -369,13 +399,6 @@ class Index extends Frontend
         if ($phone === '' || !preg_match('/^1\d{10}$/', $phone)) {
             return '';
         }
-        $cu = $this->mprocFindCustomerUserByMobile($phone);
-        if (is_array($cu) && $cu !== []) {
-            $co = trim((string)($cu['company_name'] ?? ''));
-            if ($co !== '') {
-                return $co;
-            }
-        }
         $cust = $this->mprocFindCustomerRowByPhone($phone);
         if (is_array($cust) && $cust !== []) {
             $co = $this->mprocCustomerPickField($cust, ['company_name', 'name']);
@@ -400,7 +423,7 @@ class Index extends Frontend
     }
 
     /**
-     * 仅按手机号在 customer.phone(多号逗号分隔)中匹配一条客户记录
+     * 按手机号匹配 customer(phone 或 account 单值相等)
      *
      * @return array<string, mixed>|null
      */
@@ -411,26 +434,20 @@ class Index extends Frontend
             return null;
         }
         try {
-            $rows = Db::table('customer')->where('phone', '<>', '')->select();
+            $row = Db::table('customer')
+                ->where(function ($q) use ($phone) {
+                    $q->where('phone', $phone)->whereOr('account', $phone);
+                })
+                ->order('id', 'asc')
+                ->find();
         } catch (\Throwable $e) {
             return null;
         }
-        if (!is_array($rows)) {
+        if (!is_array($row) || $row === [] || !$this->mprocCustomerUserActive($row)) {
             return null;
         }
-        foreach ($rows as $r) {
-            if (!is_array($r)) {
-                continue;
-            }
-            $raw = (string)($r['phone'] ?? '');
-            $raw = str_replace(["\r", "\n", "\t", ' ', ' ', ','], ['', '', '', '', '', ','], $raw);
-            foreach (explode(',', $raw) as $seg) {
-                if (trim($seg) === $phone) {
-                    return $r;
-                }
-            }
-        }
-        return null;
+
+        return $row;
     }
 
     /**
@@ -457,13 +474,16 @@ class Index extends Frontend
     }
 
     /**
-     * 在 customer 表中匹配当前用户:优先手机号(支持多号逗号分隔),否则按公司名与 company_name / name
+     * 在 customer 表中匹配当前用户:优先手机号,否则按公司名
      *
      * @return array<string, mixed>|null
      */
     protected function mprocFindCustomerRowForUser(array $user): ?array
     {
         $phone = trim((string)($user['phone'] ?? ''));
+        if ($phone === '') {
+            $phone = trim((string)($user['account'] ?? ''));
+        }
         $cn = trim((string)($user['company_name'] ?? ''));
         if ($phone !== '' && preg_match('/^1\d{10}$/', $phone)) {
             $byPhone = $this->mprocFindCustomerRowByPhone($phone);
@@ -620,7 +640,7 @@ class Index extends Frontend
     }
 
     /**
-     * 按登录态解析 customer_user(id / 用户名 / 手机号)
+     * 按登录态解析 customer 行
      *
      * @return array<string, mixed>|null
      */
@@ -629,10 +649,10 @@ class Index extends Frontend
         if (!empty($user['is_admin'])) {
             return null;
         }
-        $cuId = (int)($user['customer_user_id'] ?? 0);
+        $cuId = (int)($user['customer_id'] ?? $user['customer_user_id'] ?? 0);
         if ($cuId > 0) {
             try {
-                $row = Db::table('customer_user')->where('id', $cuId)->find();
+                $row = Db::table('customer')->where('id', $cuId)->find();
             } catch (\Throwable $e) {
                 $row = null;
             }
@@ -640,38 +660,43 @@ class Index extends Frontend
                 return $row;
             }
         }
-        $uname = trim((string)($user['username'] ?? ''));
-        if ($uname !== '') {
-            $byName = $this->mprocFindCustomerUserByUsername($uname);
-            if ($byName !== null) {
-                return $byName;
+        $account = trim((string)($user['account'] ?? ''));
+        if ($account !== '') {
+            $byAcc = $this->mprocFindCustomerUserByUsername($account);
+            if ($byAcc !== null) {
+                return $byAcc;
             }
         }
         $phone = trim((string)($user['phone'] ?? ''));
         if ($phone !== '' && preg_match('/^1\d{10}$/', $phone)) {
             return $this->mprocFindCustomerUserByMobile($phone);
         }
+        $uname = trim((string)($user['username'] ?? ''));
+        if ($uname !== '' && preg_match('/^1\d{10}$/', $uname)) {
+            return $this->mprocFindCustomerUserByMobile($uname);
+        }
 
         return null;
     }
 
     /**
-     * customer_user 表字段 →「我的」展示结构
+     * customer 表字段 →「我的」展示结构
      *
      * @param array<string, mixed> $cu
      * @return array{company_name:string,contact_name:string,phone:string,email:string}
      */
     protected function mprocProfileFromCustomerUserRow(array $cu): array
     {
-        $nm = trim((string)($cu['nickname'] ?? ''));
-        if ($nm === '') {
-            $nm = trim((string)($cu['username'] ?? ''));
+        $nm = trim((string)($cu['username'] ?? ''));
+        $phone = trim((string)($cu['phone'] ?? ''));
+        if ($phone === '') {
+            $phone = trim((string)($cu['account'] ?? ''));
         }
 
         return [
             'company_name' => trim((string)($cu['company_name'] ?? '')),
             'contact_name' => $nm,
-            'phone'        => trim((string)($cu['mobile'] ?? '')),
+            'phone'        => $phone,
             'email'        => trim((string)($cu['email'] ?? '')),
         ];
     }
@@ -718,7 +743,7 @@ class Index extends Frontend
     }
 
     /**
-     * 旧会话补全 customer_user_id 等字段(登录后改表结构时无需重新登录)
+     * 旧会话补全 customer_id 等字段
      */
     protected function mprocSyncSessionCustomerUser(array $user): array
     {
@@ -729,10 +754,16 @@ class Index extends Frontend
         if (!$cu) {
             return $user;
         }
-        $user['customer_user_id'] = (int)($cu['id'] ?? 0);
+        $id = (int)($cu['id'] ?? 0);
+        $user['customer_id'] = $id;
+        $user['customer_user_id'] = $id;
         $user['username'] = trim((string)($cu['username'] ?? $user['username'] ?? ''));
         $user['company_name'] = trim((string)($cu['company_name'] ?? ''));
-        $mob = trim((string)($cu['mobile'] ?? ''));
+        $user['account'] = trim((string)($cu['account'] ?? ''));
+        $mob = trim((string)($cu['phone'] ?? ''));
+        if ($mob === '') {
+            $mob = trim((string)($cu['account'] ?? ''));
+        }
         if ($mob !== '') {
             $user['phone'] = $mob;
         }
@@ -746,7 +777,7 @@ class Index extends Frontend
     }
 
     /**
-     * 「我的」:普通用户 customer_user;管理员 admin
+     * 「我的」:普通用户 customer;管理员 admin
      */
     protected function mprocProfileForUser(array $user)
     {
@@ -758,10 +789,15 @@ class Index extends Frontend
             return $this->mprocProfileFromCustomerUserRow($cu);
         }
 
+        $phone = trim((string)($user['phone'] ?? ''));
+        if ($phone === '') {
+            $phone = trim((string)($user['account'] ?? ''));
+        }
+
         return [
             'company_name' => trim((string)($user['company_name'] ?? '')),
             'contact_name' => trim((string)($user['username'] ?? '')),
-            'phone'        => trim((string)($user['phone'] ?? '')),
+            'phone'        => $phone,
             'email'        => '',
         ];
     }
@@ -980,7 +1016,8 @@ class Index extends Frontend
         $this->view->assign('mprocSearchQ', $q);
         $this->view->assign('mprocProfile', $profile);
         $this->view->assign('mprocIsAdmin', !empty($user['is_admin']) ? 1 : 0);
-        $this->view->assign('mprocCanChangePwd', empty($user['is_admin']) && (int)($user['customer_user_id'] ?? 0) > 0 ? 1 : 0);
+        $cid = (int)($user['customer_id'] ?? $user['customer_user_id'] ?? 0);
+        $this->view->assign('mprocCanChangePwd', empty($user['is_admin']) && $cid > 0 ? 1 : 0);
         $this->view->assign('mprocFocusEid', $mprocFocusEid);
 
         if ($mainTab === 'me') {
@@ -1053,12 +1090,24 @@ class Index extends Frontend
             Session::set('mproc_intended_url', $redirect);
         }
         $this->view->assign('mprocLoginRedirect', $redirect);
+        $this->view->assign('mprocCaptchaUrl', url('index/index/captcha'));
+        $this->view->assign('mprocCaptchaLen', (int)(Config::get('captcha.length') ?: 4));
 
         return $this->view->fetch();
     }
 
     /**
-     * 发送登录验证码(POST:phone)
+     * 图形验证码(手机号登录用)
+     */
+    public function captcha($id = '')
+    {
+        $captcha = new Captcha((array)Config::get('captcha'));
+
+        return $captcha->entry($id);
+    }
+
+    /**
+     * 发送登录验证码(POST:phone、captcha)
      */
     public function sendSms()
     {
@@ -1066,6 +1115,13 @@ class Index extends Frontend
             $this->error('请使用 POST');
         }
         $phone = trim((string)$this->request->post('phone', ''));
+        $captcha = trim((string)$this->request->post('captcha', ''));
+        if ($captcha === '') {
+            $this->error('请输入图形验证码');
+        }
+        if (!Validate::is($captcha, 'captcha')) {
+            $this->error('图形验证码不正确');
+        }
         if (!preg_match('/^1\d{10}$/', $phone)) {
             $this->error('请输入正确的11位手机号');
         }
@@ -1124,24 +1180,13 @@ class Index extends Frontend
         if (!$cu) {
             $this->error('该手机号未开通或已禁用,请联系管理员');
         }
-        $companyName = trim((string)($cu['company_name'] ?? ''));
-        if ($companyName === '') {
-            $companyName = $this->mprocResolveCompanyForLoginPhone($phone);
-        }
 
-        $this->mprocFinishLogin([
-            'phone'            => $phone,
-            'company_name'     => $companyName,
-            'username'         => trim((string)($cu['username'] ?? '')),
-            'customer_user_id' => (int)($cu['id'] ?? 0),
-            'login_type'       => 'sms',
-            'is_admin'         => 0,
-        ]);
+        $this->mprocFinishLogin($this->mprocLoginPayloadFromCustomer($cu, 'sms'));
     }
 
     /**
      * 账号密码登录(POST:username、password)
-     * 先 customer_user(普通用户),未命中再 admin(管理员);admin 密码规则同 FastAdmin Auth::login
+     * 先 customer(account),未命中再 admin;admin 密码规则同 FastAdmin Auth::login
      */
     public function doLoginPwd()
     {
@@ -1159,19 +1204,7 @@ class Index extends Frontend
             if (!$this->mprocVerifyCustomerUserPassword($cu, $password)) {
                 $this->error('账号或密码错误');
             }
-            $phone = trim((string)($cu['mobile'] ?? ''));
-            $companyName = trim((string)($cu['company_name'] ?? ''));
-            if ($companyName === '' && $phone !== '' && preg_match('/^1\d{10}$/', $phone)) {
-                $companyName = $this->mprocResolveCompanyForLoginPhone($phone);
-            }
-            $this->mprocFinishLogin([
-                'phone'            => $phone,
-                'company_name'     => $companyName,
-                'username'         => trim((string)($cu['username'] ?? '')),
-                'customer_user_id' => (int)($cu['id'] ?? 0),
-                'login_type'       => 'pwd',
-                'is_admin'         => 0,
-            ]);
+            $this->mprocFinishLogin($this->mprocLoginPayloadFromCustomer($cu, 'pwd'));
         }
 
         // 管理员:表 admin
@@ -1233,7 +1266,7 @@ class Index extends Frontend
 
     /**
      * 是否允许当前登录用户修改该条 purchase_order_detail 的金额、交期
-     * 仅普通用户(customer_user)可改;管理员(admin 账号密码)仅可查看
+     * 仅普通用户(customer)可改;管理员(admin)仅可查看
      */
     protected function mprocCanEditRow(array $user, array $row)
     {
@@ -1407,13 +1440,12 @@ class Index extends Frontend
         if (!$this->mprocVerifyCustomerUserPassword($cu, $oldPwd)) {
             $this->error('原密码不正确');
         }
-        $salt = (string)($cu['salt'] ?? '');
         $data = [
-            'password'   => $this->mprocHashCustomerUserPassword($newPwd, $salt),
+            'password'   => $this->mprocHashCustomerUserPassword($newPwd),
             'updatetime' => date('Y-m-d H:i:s'),
         ];
         try {
-            Db::table('customer_user')->where('id', $cuId)->update($data);
+            Db::table('customer')->where('id', $cuId)->update($data);
         } catch (\Throwable $e) {
             $this->error('修改失败:' . $e->getMessage());
         }

+ 1 - 1
application/index/view/index/index.html

@@ -222,7 +222,7 @@
 </head>
 <body class="{eq name='mprocMainTab' value='me'}mproc-layout-me{else/}mproc-layout-orders{/eq}">
 <div class="bar">
-    <h1>外发明细</h1>
+    <h1></h1>
     <a href="{:url('index/index/logout')}" class="mproc-bar-logout">退出</a>
 </div>
 

+ 45 - 6
application/index/view/index/login.html

@@ -51,9 +51,19 @@
         .field label { display: block; font-size: 0.85rem; color: #555; margin-bottom: 6px; }
         .field input { width: 100%; padding: 12px 14px; border: 1px solid #ddd; border-radius: 8px; font-size: 16px; }
         .field input:focus { outline: none; border-color: #3c8dbc; }
-        .row-code { display: flex; gap: 10px; }
-        .row-code input { flex: 1; }
-        .btn-code { flex-shrink: 0; padding: 0 14px; border: 1px solid #3c8dbc; background: #fff; color: #3c8dbc; border-radius: 8px; font-size: 14px; white-space: nowrap; min-width: 108px; }
+        .row-code { display: flex; gap: 10px; align-items: center; }
+        .row-code input { flex: 1; min-width: 0; }
+        .row-captcha img {
+            flex-shrink: 0;
+            width: 110px;
+            height: 40px;
+            border-radius: 8px;
+            border: 1px solid #ddd;
+            cursor: pointer;
+            display: block;
+            background: #f5f5f5;
+        }
+        .btn-code { flex-shrink: 0; padding: 0 14px; border: 1px solid #3c8dbc; background: #fff; color: #3c8dbc; border-radius: 8px; font-size: 14px; white-space: nowrap; min-width: 108px; height: 44px; }
         .btn-code:disabled { opacity: .55; border-color: #ccc; color: #999; }
         .btn-submit { width: 100%; padding: 14px; border: none; border-radius: 8px; background: #3c8dbc; color: #fff; font-size: 16px; font-weight: 600; margin-top: 8px; }
         .btn-submit:active { opacity: .9; }
@@ -109,7 +119,7 @@
         <div id="panel-account" class="panel active">
             <div class="field">
                 <label for="username">账号</label>
-                <input type="text" id="username" name="username" maxlength="64" placeholder="用户名" autocomplete="username">
+                <input type="text" id="username" name="username" maxlength="11" placeholder="登录账号(手机号)" autocomplete="username">
             </div>
             <div class="field">
                 <label for="password">密码</label>
@@ -123,7 +133,14 @@
                 <input type="tel" id="phone" name="phone" maxlength="11" placeholder="请输入11位手机号" autocomplete="tel">
             </div>
             <div class="field">
-                <label for="code">验证码</label>
+                <label for="captcha">图形验证码</label>
+                <div class="row-code row-captcha">
+                    <input type="text" id="captcha" name="captcha" maxlength="6" placeholder="请输入图形验证码" autocomplete="off">
+                    <img id="img-captcha" src="{$mprocCaptchaUrl|default=''|htmlentities}" width="110" height="40" alt="图形验证码" title="点击刷新">
+                </div>
+            </div>
+            <div class="field">
+                <label for="code">短信验证码</label>
                 <div class="row-code">
                     <input type="text" id="code" name="code" maxlength="6" inputmode="numeric" placeholder="6位验证码" autocomplete="one-time-code">
                     <button type="button" class="btn-code" id="btn-send">获取验证码</button>
@@ -144,12 +161,15 @@
     var loginUrl = "{:url('index/index/doLogin')}";
     var loginPwdUrl = "{:url('index/index/doLoginPwd')}";
     var indexUrl = "{:url('index/index/index')}";
+    var captchaBaseUrl = {:json_encode(isset($mprocCaptchaUrl) ? $mprocCaptchaUrl : url('index/index/captcha'), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)};
     var loginRedirect = {:json_encode(isset($mprocLoginRedirect) ? $mprocLoginRedirect : '', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)};
 
     var $tabs = document.querySelectorAll('.login-tabs button');
     var $panelPhone = document.getElementById('panel-phone');
     var $panelAccount = document.getElementById('panel-account');
     var $phone = document.getElementById('phone');
+    var $captcha = document.getElementById('captcha');
+    var $imgCaptcha = document.getElementById('img-captcha');
     var $code = document.getElementById('code');
     var $username = document.getElementById('username');
     var $password = document.getElementById('password');
@@ -249,12 +269,23 @@
         }
     }
 
+    function refreshCaptcha() {
+        if (!$imgCaptcha || !captchaBaseUrl) return;
+        $imgCaptcha.src = captchaBaseUrl + (captchaBaseUrl.indexOf('?') >= 0 ? '&' : '?') + 'r=' + Date.now();
+        if ($captcha) $captcha.value = '';
+    }
+
+    if ($imgCaptcha) {
+        $imgCaptcha.addEventListener('click', refreshCaptcha);
+    }
+
     $tabs.forEach(function (btn) {
         btn.addEventListener('click', function () {
             var tab = btn.getAttribute('data-tab');
             $tabs.forEach(function (b) { b.classList.toggle('active', b === btn); });
             $panelPhone.classList.toggle('active', tab === 'phone');
             $panelAccount.classList.toggle('active', tab === 'account');
+            if (tab === 'phone') refreshCaptcha();
         });
     });
 
@@ -272,27 +303,35 @@
 
     $btnSend.addEventListener('click', function () {
         var phone = ($phone.value || '').trim();
+        var captcha = ($captcha && $captcha.value) ? $captcha.value.trim() : '';
         if (!/^1\d{10}$/.test(phone)) {
             showToast('请输入正确的手机号', 'err');
             return;
         }
+        if (!captcha) {
+            showToast('请输入图形验证码', 'err');
+            return;
+        }
         if (cd > 0) return;
         $btnSend.disabled = true;
         $btnSend.textContent = '发送中…';
-        postForm(sendUrl, { phone: phone }, 25000).then(function (ret) {
+        postForm(sendUrl, { phone: phone, captcha: captcha }, 25000).then(function (ret) {
             if (ret && (ret.code === 1 || ret.code === '1')) {
                 cd = 60;
                 tick();
                 timer = setInterval(tick, 1000);
                 showToast('验证码已发送,请查收短信', 'info');
+                refreshCaptcha();
             } else {
                 $btnSend.disabled = false;
                 $btnSend.textContent = '获取验证码';
                 showToast(ret && ret.msg ? ret.msg : '发送失败', 'err');
+                refreshCaptcha();
             }
         }).catch(function (e) {
             $btnSend.disabled = false;
             $btnSend.textContent = '获取验证码';
+            refreshCaptcha();
             if (e && e.name === 'AbortError') {
                 showToast('请求超时,请稍后再试', 'err');
             } else {

+ 28 - 6
public/assets/js/backend/customer.js

@@ -50,12 +50,34 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
                 columns: [
                     [
                         {checkbox: true},
-                        {field: 'id', title: __('序号')},
-                        {field: 'company_name', title: __('客户名称'), operate: 'LIKE', table: table, class: 'autocontent', formatter: Table.api.formatter.content},
-                        {field: 'username', title: __('姓名'), operate: 'LIKE', table: table, class: 'autocontent', formatter: Table.api.formatter.content},
-                        {field: 'email', title: __('邮箱'), operate: 'LIKE', table: table, class: 'autocontent', formatter: Table.api.formatter.content},
-                        {field: 'phone', title: __('手机号'), operate: 'LIKE', table: table, class: 'autocontent', formatter: Table.api.formatter.content},
-                        {field: 'company_type', title: __('业务分类'), operate: 'LIKE', table: table, class: 'autocontent', formatter: Table.api.formatter.content},
+                        {field: 'id', title: __('Id')},
+                        {field: 'company_name', title: __('Company_name'), operate: 'LIKE', table: table, class: 'autocontent', formatter: Table.api.formatter.content},
+                        {field: 'username', title: __('Username'), operate: 'LIKE', table: table, class: 'autocontent', formatter: Table.api.formatter.content},
+                        {field: 'account', title: __('Account'), operate: 'LIKE', table: table, class: 'autocontent', formatter: function (value, row) {
+                            var v = value != null && String(value).trim() !== '' ? String(value).trim() : '';
+                            if (!v && row.phone) {
+                                v = String(row.phone).trim();
+                            }
+                            return Table.api.formatter.content.call(this, v, row);
+                        }},
+                        {field: 'email', title: __('Email'), operate: 'LIKE', table: table, class: 'autocontent', formatter: Table.api.formatter.content},
+                        {field: 'phone', title: __('Phone'), operate: 'LIKE', table: table, class: 'autocontent', formatter: Table.api.formatter.content},
+                        {field: 'company_type', title: __('Company_type'), operate: 'LIKE', table: table, class: 'autocontent', formatter: Table.api.formatter.content},
+                        {
+                            field: 'status',
+                            title: __('Status'),
+                            searchList: {'1': __('Status 1'), '0': __('Status 0')},
+                            formatter: function (value) {
+                                var st = value !== undefined && value !== null ? String(value) : '';
+                                if (st === '1') {
+                                    return '<span class="label label-success">' + __('Status 1') + '</span>';
+                                }
+                                if (st === '0') {
+                                    return '<span class="label label-danger">' + __('Status 0') + '</span>';
+                                }
+                                return st !== '' ? st : '-';
+                            }
+                        },
                         {field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate}
                     ]
                 ]