fixedTable ?: $this->request->param('table', ''); $config = ManageConfig::get($table); if (!$config) { $this->error('模块不存在或未授权'); } $this->table = $table; $this->config = $config; $this->columns = $this->describeTable($table); $this->view->assign([ 'table' => $table, 'tableTitle' => $config['title'], 'tableGroup' => $config['group'], 'readonlyTable' => !empty($config['readonly_table']), ]); // 所有业务模块复用同一套前端脚本与视图 $this->assignconfig('controllername', 'manage'); $this->assignconfig('table', $table); $this->assignconfig('columns', $this->columnMeta()); } /* ============================================================ * 列表 * ============================================================ */ public function index() { if ($this->request->isAjax()) { return json($this->getList()); } return $this->view->fetch('manage/index'); } /** 列定义(供前端 bootstrapTable 构建) */ public function columns() { return json(['code' => 1, 'data' => ['columns' => $this->columnMeta()]]); } /* ============================================================ * 新增 / 编辑 * ============================================================ */ public function add() { if ($this->request->isPost()) { return $this->save(); } $users = $this->userOptions(); $this->view->assign('row', []); $this->view->assign('users', $users); $this->view->assign('formHtml', FormBuilder::render($this->config['fields'], [], $users)); return $this->view->fetch('manage/add'); } public function edit($ids = null) { $ids = $this->request->param('ids', ''); if ($this->request->isPost()) { return $this->save($ids); } $row = Db::name($this->shortTable())->where('id', (int)$ids)->find(); if (!$row) { $this->error('记录不存在'); } $users = $this->userOptions(); $formRow = $this->formatRowForForm($row); $this->view->assign('row', $formRow); $this->view->assign('users', $users); $this->view->assign('formHtml', FormBuilder::render($this->config['fields'], $formRow, $users)); return $this->view->fetch('manage/edit'); } protected function save($ids = '') { if (!empty($this->config['readonly_table'])) { $this->error('该模块仅支持查看'); } $data = $this->collectPost(); if (!$data) { $this->error('没有需要保存的字段'); } $short = $this->shortTable(); try { if ($ids) { $exist = Db::name($short)->where('id', (int)$ids)->find(); if (!$exist) { $this->error('记录不存在'); } if (isset($this->columns['updatetime'])) { $data['updatetime'] = time(); } Db::name($short)->where('id', (int)$ids)->update($data); $id = (int)$ids; } else { if (isset($this->columns['createtime'])) { $data['createtime'] = time(); } if (isset($this->columns['updatetime'])) { $data['updatetime'] = time(); } $id = Db::name($short)->insertGetId($data); } } catch (ValidateException $e) { $this->error($e->getMessage()); } catch (PDOException $e) { $this->error($e->getMessage()); } $this->success($ids ? '修改成功' : '新增成功', null, ['id' => $id]); } /* ============================================================ * 删除 / 批量 * ============================================================ */ public function del($ids = null) { if (!empty($this->config['readonly_table'])) { $this->error('该模块仅支持查看'); } $ids = $this->parseIds($ids); if (!$ids) { $this->error('参数错误'); } $short = $this->shortTable(); if (isset($this->columns['deletetime'])) { Db::name($short)->whereIn('id', $ids)->update(['deletetime' => time()]); // 支持恢复 if ($this->request->param('restore')) { Db::name($short)->whereIn('id', $ids)->update(['deletetime' => null]); } } else { Db::name($short)->whereIn('id', $ids)->delete(); } $this->success('删除成功'); } /** 批量设置字段(如状态、审核结果) */ public function multi($ids = null) { if (!empty($this->config['readonly_table'])) { $this->error('该模块仅支持查看'); } $ids = $this->parseIds($ids); if (!$ids) { $this->error('参数错误'); } $params = $this->request->post(); $allow = []; foreach ($params as $field => $value) { if ($field === 'ids' || !isset($this->columns[$field])) { continue; } if (!isset($this->config['fields'][$field])) { continue; } $allow[$field] = $this->castValue($field, $value); } if (!$allow) { $this->error('没有可设置的字段'); } if (isset($this->columns['updatetime'])) { $allow['updatetime'] = time(); } Db::name($this->shortTable())->whereIn('id', $ids)->update($allow); $this->success('批量更新成功'); } /* ============================================================ * 内部方法 * ============================================================ */ protected function getList() { $short = $this->shortTable(); $query = Db::name($short); // 过滤已删除 if (isset($this->columns['deletetime']) && !$this->request->param('with_deleted')) { $query->whereNull('deletetime'); } // 关键词搜索 $search = trim((string)$this->request->param('search', '')); if ($search !== '') { $like = []; foreach ($this->config['fields'] as $field => $def) { if (!empty($def['search']) && isset($this->columns[$field])) { $like[] = [$field, 'like', '%' . $search . '%']; } } if ($like) { $query->where(function ($q) use ($like) { foreach ($like as $index => $item) { if ($index === 0) { $q->where($item[0], $item[1], $item[2]); } else { $q->whereOr($item[0], $item[1], $item[2]); } } }); } } // 精确过滤 $filter = $this->request->param('filter', ''); if ($filter) { $filterArr = is_array($filter) ? $filter : json_decode(htmlspecialchars_decode($filter, ENT_QUOTES), true); if (is_array($filterArr)) { foreach ($filterArr as $field => $value) { if ($value === '' || $value === null || !isset($this->columns[$field])) { continue; } $query->where($field, $value); } } } $total = (clone $query)->count(); $limit = min(200, max(1, (int)$this->request->param('limit', 20))); $offset = max(0, (int)$this->request->param('offset', 0)); $sort = $this->request->param('sort', ''); $order = strtolower($this->request->param('order', 'desc')) === 'asc' ? 'asc' : 'desc'; if ($sort && isset($this->columns[$sort])) { $query->order($sort, $order); } else { $query->orderRaw($this->config['sort'] ?? 'id desc'); } $rows = $query->limit($offset, $limit)->select(); $users = $this->userMap($rows); $list = []; foreach ($rows as $row) { $list[] = $this->formatRow($row, $users); } return ['total' => $total, 'rows' => $list]; } protected function columnMeta() { $meta = [['field' => 'id', 'title' => 'ID', 'sortable' => true, 'width' => 70]]; foreach ($this->config['fields'] as $field => $def) { if (isset($def['form']) && $def['form'] === false) { continue; } if (!isset($this->columns[$field])) { continue; } $item = [ 'field' => $field, 'title' => $def['label'], 'type' => $def['type'] ?? 'text', 'sortable' => true, 'search' => !empty($def['search']), ]; if (($def['type'] ?? '') === 'select') { $item['options'] = $this->optionsOf($def); } $meta[] = $item; } return $meta; } protected function formatRow($row, $users = []) { $data = ['id' => (int)$row['id']]; foreach ($this->config['fields'] as $field => $def) { if (!array_key_exists($field, $row)) { continue; } $value = $row[$field]; $type = $def['type'] ?? 'text'; if ($type === 'user') { $uid = (int)$value; $data[$field] = $uid ? (isset($users[$uid]) ? $users[$uid] : '#' . $uid) : '-'; if (isset($data[$field . '_text']) === false) { $data[$field . '_text'] = $data[$field]; } } elseif ($type === 'select') { $options = $this->optionsOf($def); $data[$field] = isset($options[(string)$value]) ? $options[(string)$value] : $value; } elseif ($type === 'bool') { $data[$field] = $value ? '是' : '否'; } elseif ($type === 'datetime' || $type === 'date') { $data[$field] = $this->timeText($field, $value); } else { $data[$field] = is_string($value) && mb_strlen($value) > 60 ? mb_substr($value, 0, 60) . '…' : $value; } if (isset($def['form']) && $def['form'] === false) { unset($data[$field]); } } if (isset($this->columns['deletetime'])) { $data['is_deleted'] = !empty($row['deletetime']) ? 1 : 0; } return $data; } protected function formatRowForForm($row) { $data = $row; foreach ($this->config['fields'] as $field => $def) { if (!array_key_exists($field, $row)) { continue; } $type = $def['type'] ?? 'text'; if ($type === 'datetime' || $type === 'date') { $value = $row[$field]; if (is_numeric($value) && (int)$value > 1000000000) { $data[$field] = $type === 'datetime' ? date('Y-m-d\TH:i', (int)$value) : date('Y-m-d', (int)$value); } elseif (is_numeric($value) && (int)$value > 0 && (int)$value < 1000 && $type === 'datetime') { // 秒数等小整数不转换 $data[$field] = $value; } } if ($type === 'bool') { $data[$field] = (int)$value; } } return $data; } protected function collectPost() { $data = []; foreach ($this->config['fields'] as $field => $def) { if (isset($def['readonly']) && $def['readonly']) { continue; } if (isset($def['form']) && $def['form'] === false) { continue; } if (!isset($this->columns[$field])) { continue; } $value = $this->request->post($field, null); if ($value === null) { continue; } $data[$field] = $this->castValue($field, $value); } return $data; } protected function castValue($field, $value) { $def = $this->config['fields'][$field] ?? []; $type = $def['type'] ?? 'text'; if ($type === 'bool') { return (int)!!$value; } if ($type === 'number') { return (int)$value; } if ($type === 'decimal') { return round((float)$value, 2); } if ($type === 'datetime') { if ($value === '') { return null; } $ts = strtotime(str_replace('T', ' ', (string)$value)); return $ts ?: null; } if ($type === 'date') { return $value === '' ? null : date('Y-m-d', strtotime((string)$value)); } if ($type === 'user') { return (int)$value; } if ($type === 'textarea' || $type === 'text') { return (string)$value; } return $value; } protected function timeText($field, $value) { if ($value === null || $value === '' || $value === '0') { return ''; } $columnType = $this->columns[$field] ?? 'varchar'; if (in_array($columnType, ['bigint', 'int', 'timestamp'], true)) { $ts = (int)$value; return $ts > 0 ? date('Y-m-d H:i', $ts) : ''; } return (string)$value; } protected function optionsOf($def) { $options = $def['options'] ?? []; if (is_string($options)) { $options = ManageConfig::$enums[$options] ?? []; } return $options; } protected function describeTable($table) { $columns = []; $rows = Db::query('SHOW COLUMNS FROM `' . str_replace('`', '', $table) . '`'); foreach ($rows as $row) { $type = strtolower($row['Type']); $columns[$row['Field']] = preg_match('/^(tiny|small|medium|big)?int/', $type) ? 'int' : (preg_match('/^(decimal|float|double)/', $type) ? 'decimal' : (preg_match('/^date$/', $type) ? 'date' : (preg_match('/^(datetime|timestamp)/', $type) ? 'datetime' : 'varchar'))); } return $columns; } protected function shortTable() { return str_replace('fa_', '', $this->table); } protected function parseIds($ids) { if (is_array($ids)) { return array_map('intval', $ids); } return array_values(array_filter(array_map('intval', explode(',', (string)$ids)))); } protected function userMap($rows) { $ids = []; foreach ($rows as $row) { if (isset($row['user_id']) && (int)$row['user_id'] > 0) { $ids[] = (int)$row['user_id']; } if (isset($row['member_user_id']) && (int)$row['member_user_id'] > 0) { $ids[] = (int)$row['member_user_id']; } } if (!$ids) { return []; } $users = Db::name('user')->whereIn('id', array_unique($ids))->column('nickname,username', 'id'); $map = []; foreach ($users as $id => $item) { $name = $item['nickname'] ?: $item['username']; $map[$id] = $name . ' (#' . $id . ')'; } return $map; } protected function userOptions() { $rows = Db::name('user')->order('id', 'desc')->limit(500)->field('id,username,nickname')->select(); $options = ['' => '未选择']; foreach ($rows as $row) { $options[$row['id']] = (($row['nickname'] ?: $row['username'])) . ' (#' . $row['id'] . ')'; } return $options; } }