rabin 1 year ago
commit
465f5be16b
19 changed files with 1416 additions and 0 deletions
  1. 13 0
      api/Admin.php
  2. 57 0
      api/Login.php
  3. 70 0
      api/Menu.php
  4. 138 0
      api/Page/Data.php
  5. 96 0
      api/Page/Oper.php
  6. 11 0
      api/Page/Stat.php
  7. 224 0
      api/Page/Update.php
  8. 5 0
      index.php
  9. 27 0
      lib/Auth.php
  10. 17 0
      lib/Common.php
  11. 53 0
      lib/Menu.php
  12. 191 0
      lib/Page.php
  13. 49 0
      table/admin.php
  14. 311 0
      table/manage/admin.php
  15. 51 0
      table/manage/core.php
  16. 16 0
      table/manage/recycler.php
  17. 51 0
      table/menu.php
  18. 23 0
      table/recycler.php
  19. 13 0
      table/role.php

+ 13 - 0
api/Admin.php

@@ -0,0 +1,13 @@
+<?php namespace Manage\Api;
+use Dever;
+use Manage\Lib\Auth;
+class Admin extends Auth
+{
+    public function info()
+    {
+        $this->user['username'] = '1';
+        $this->user['roles'] = ["Admin"];
+        $this->user['permissions'] = ["read:system", "write:system", "delete:system"];
+        return $this->user;
+    }
+}

+ 57 - 0
api/Login.php

@@ -0,0 +1,57 @@
+<?php namespace Manage\Api;
+use Dever;
+use Dever\Helper\Secure;
+use Dever\Helper\Str;
+use Dever\Helper\Code;
+class Login
+{
+    public function act()
+    {
+        //$this->checkCode();
+        $where['mobile'] = Dever::input('mobile', Dever::rule('mobile'), '手机号');
+        $password = Dever::input('password', 'is_string', '密码');
+        $admin = Dever::db('admin')->find($where);
+        if (!$admin) {
+            $total = Dever::db('admin')->count();
+            if ($total <= 0) {
+                $insert['name'] = Str::hide($where['mobile']);
+                $insert['mobile'] = $where['mobile'];
+                $insert += Dever::load('common')->createPwd($password);
+                $id = Dever::db('admin')->insert($insert);
+                $admin = Dever::db('admin')->find($id);
+            } else {
+                Dever::error('登录失败');
+            }
+        }
+        if (!$admin) {
+            Dever::error('登录失败');
+        }
+        if (Dever::load('common')->hash($password, $admin['salt']) != $admin['password']) {
+            Dever::error('登录失败');
+        }
+        return array('token' => Secure::login($admin['id']));
+    }
+    private function checkCode()
+    {
+        $code = Dever::input('verificationCode');
+        if (!$code) {
+            Dever::error('请输入验证码');
+        }
+        $save = Dever::session('code');
+        if ($code != $save) {
+            Dever::error('验证码错误');
+        }
+    }
+    public function code()
+    {
+        echo Dever::session('code', Code::create(), 3600);die;
+    }
+    public function out()
+    {
+        return 'ok';
+    }
+    public function loadMenu()
+    {
+        return Dever::load('menu')->init();
+    }
+}

+ 70 - 0
api/Menu.php

@@ -0,0 +1,70 @@
+<?php namespace Manage\Api;
+use Dever;
+use Manage\Lib\Auth;
+class Menu extends Auth
+{
+    private $list = 'list,table,card';
+    public function info()
+    {
+        $top = Dever::db('menu')->select(array('parent_key' => '/'));
+        $result = $menu = array();
+        foreach ($top as $v) {
+            $menu = $this->getMenu($v);
+        }
+        $result[] = $menu;
+        $result[] = array
+        (
+            'path' => '/:pathMatch(.*)*',
+            'component' => '@/views/403',
+            'name' => 'NotFound',
+            'meta' => array
+            (
+                'hidden' => true,
+            )
+        );
+        return array('list' => $result);
+    }
+    private function getMenu($v)
+    {
+        $info = array
+        (
+            'path' => $v['key'],
+            'name' => ucfirst($v['key']),
+            'meta' => array
+            (
+                'title' => $v['name'],
+                'icon' => $v['icon'],
+                //'noClosable' => true,
+                //'breadcrumbHidden' => true,
+                'dynamicNewTab' => true,
+            )
+        );
+        if ($v['show'] == 2) {
+            $info['meta']['hidden'] = true;
+        }
+        if (isset($v['active']) && $v['active']) {
+            $info['meta']['activeMenu'] = $v['active'];
+        }
+        if ($v['parent_key'] == '/') {
+            $info['path'] = '/';
+            $info['component'] = 'Layout';
+        }
+        $child = Dever::db('menu')->select(array('parent_key' => $v['key']))->fetchAll();
+        if ($child) {
+            foreach ($child as $v1) {
+                $info['children'][] = $this->getMenu($v1);
+                if (isset($v1['link']) && strstr($this->list, $v1['link'])) {
+                    $v1['active'] = '/' . $v1['key'];
+                    $v1['name'] .= '更新';
+                    $v1['link'] = 'update';
+                    $v1['key'] .= '/update';
+                    $v1['show'] = 2;
+                    $info['children'][] = $this->getMenu($v1);
+                }
+            }
+        } elseif ($v['link']) {
+            $info['component'] = '@/views/page/' . $v['link'];
+        }
+        return $info;
+    }
+}

+ 138 - 0
api/Page/Data.php

@@ -0,0 +1,138 @@
+<?php namespace Manage\Api\Page;
+use Dever;
+use Manage\Lib\Page;
+# 数据获取
+class Data extends Page
+{
+    public function __construct()
+    {
+        parent::__construct('list');
+    }
+    public function list()
+    {
+        $where = $data['head'] = array();
+        $data['field'] = $this->setting('field', $data['head']);
+        $data['search'] = $this->search($where);
+        $data['button'] = $this->button();
+        $data['body_button'] = $this->button('data_button');
+        $data['body'] = $this->data($where);
+        $data['total'] = Dever::page('total');
+        $data['height'] = $this->config['height'] ?? '100%';
+        $data['filter'] = $this->config['filter'] ?? 'name';
+        $data['desc'] = $this->config['desc'] ?? '';
+        return $data;
+    }
+
+    private function data($where)
+    {
+        $set['num'] = Dever::input('pgnum', '', '', 10);
+        $data = $this->db->select($where, $set);
+        if ($data) {
+            foreach ($data as $k => $v) {
+                foreach ($v as $key => $value) {
+                    if (isset($this->config['field'][$key]) && $show = Dever::isset($this->config['field'][$key], 'show')) {
+                        $data[$k][$key] = $this->getShow($show, $v);
+                    } elseif ($value && isset($this->db->config['struct'][$key]['value']) && $this->db->config['struct'][$key]['value']) {
+                        $data[$k][$key] = $this->db->value($key, $value);
+                    } elseif ($key == 'cdate') {
+                        $data[$k][$key] = date('Y-m-d H:i', $value);
+                    }
+                }
+            }
+
+        }
+        return $data;
+    }
+
+    private function search(&$where)
+    {
+        $search = Dever::input('search');
+        $list_search = $result = array();
+        $this->setting('search', $list_search, false, 'text');
+        if ($list_search && $search) {
+            foreach ($list_search as $v) {
+                if ($v['type'] != 'hidden') {
+                    $result[] = $v;
+                }
+                if ($value = Dever::isset($search, $v['key'])) {
+                    if ($v['type'] == 'group') {
+                        $where[$v['key']] = array('group', $value);
+                    } elseif ($v['type'] == 'selects') {
+                        $where[$v['key']] = array('group', $value);
+                    } elseif ($v['type'] == 'like') {
+                        $where[$v['key']] = array('like', $value);
+                    } elseif ($v['type'] == 'in') {
+                        $where[$v['key']] = array('in', $value);
+                    } else {
+                        $where[$v['key']] = $value;
+                    }
+                }
+            }
+        } else {
+            $result = $list_search;
+        }
+        return $result;
+    }
+
+    private function button($key = 'button')
+    {
+        $result = array();
+        if (empty($this->config[$key])) {
+            if ($key == 'list_button') {
+                $this->config[$key] = array('新增' => 'fastadd', '删除' => 'del');
+            } else {
+                $this->config[$key] = array('编辑' => 'fastedit', '删除' => 'del');
+            }
+        }
+        foreach ($this->config[$key] as $k => $v) {
+            $p = '';
+            $i = '';
+            if (is_array($v)) {
+                if (isset($v[2])) {
+                    $i = $v[2];
+                }
+                if (isset($v[1])) {
+                    $p = $v[1];
+                }
+                $v = $v[0];
+            }
+            if (strstr($v, 'add')) {
+                $icon = 'Plus';
+                $button = 'primary';
+            } elseif (strstr($v, 'edit')) {
+                $icon = 'Edit';
+                $button = 'primary';
+            } elseif (strstr($v, 'view')) {
+                $icon = 'View';
+                $button = '';
+            } elseif ($v == 'del') {
+                $icon = 'Delete';
+                $button = 'danger';
+            } elseif ($v == 'oper') {
+                $icon = 'Notification';
+                $button = 'warning';
+            } elseif ($v == 'link') {
+                $icon = 'Link';
+                $button = 'success';
+            } elseif ($v == 'recover') {
+                $icon = 'HelpFilled';
+                $button = 'info';
+            } elseif ($v == 'recycler') {
+                $icon = 'HelpFilled';
+                $button = 'info';
+            }
+            if ($i) {
+                $icon = $i;
+            }
+            $result[] = array
+            (
+                'name' => $k,
+                'type' => $v,
+                'param' => $p,
+                'icon' => $icon,
+                'button' => $button,
+            );
+        }
+        return $result;
+    }
+}

+ 96 - 0
api/Page/Oper.php

@@ -0,0 +1,96 @@
+<?php namespace Manage\Api\Page;
+use Dever;
+use Manage\Lib\Page;
+# 操作
+class Oper extends Page
+{
+    public function __construct()
+    {
+        parent::__construct();
+        $this->id = Dever::input('id');
+        if (!$this->id) {
+            Dever::error('无效数据');
+        }
+    }
+
+    # 更改某个字段的值
+    public function up_commit(){}
+    public function up()
+    {
+        $input = Dever::input();
+        $field = explode(',', Dever::input('field'));
+        foreach ($field as $k => $v) {
+            if (isset($input[$v]) && $value = $input[$v]) {
+                if (is_array($value)) {
+                    $value = implode(',', $value);
+                }
+                $data[$v] = $value;
+            }
+        }
+        $where['id'] = array('in', $this->id);
+        $state = $this->db->update($where, $data);
+        if ($state) {
+            return '操作成功';
+        } else {
+            Dever::error('操作失败');
+        }
+    }
+
+    # 删除 删除到回收站
+    public function del_commit(){}
+    public function del()
+    {
+        $where['id'] = array('in', $this->id);
+        $data = $this->db->select($where)->fetchAll();
+        if ($data) {
+            foreach ($data as $k => $v) {
+                $insert['table'] = Dever::input('load');
+                $insert['table_id'] = $v['id'];
+                $insert['content'] = Dever::json_encode($v);
+                $state = Dever::db('manage/recycler')->insert($insert);
+                if (!$state) {
+                    Dever::error('删除失败,请重试');
+                }
+                $state = $this->db->delete($v['id']);
+                if (!$state) {
+                    Dever::error('删除失败,请重试');
+                }
+            }
+        }
+        return '操作成功';
+    }
+
+    # 从回收站恢复
+    public function recover_commit(){}
+    public function recover()
+    {
+        $where['id'] = array('in', $this->id);
+        $data = Dever::db('manage/recycler')->select($where);
+        if ($data) {
+            foreach ($data as $k => $v) {
+                $state = Dever::db('manage/recycler')->delete($v['id']);
+                if (!$state) {
+                    Dever::error('删除失败,请重试');
+                }
+                $v['content'] = Dever::json_decode($v['content']);
+                $state = $this->db->insert($v['content']);
+                if (!$state) {
+                    Dever::error('删除失败,请重试');
+                }
+            }
+        }
+        return '操作成功';
+    }
+
+    # 直接删除
+    public function delete_commit(){}
+    public function delete()
+    {
+        $where['id'] = array('in', $this->id);
+        $state = $this->db->delete($where);
+        if (!$state) {
+            Dever::error('删除失败,请重试');
+        }
+        return '操作成功';
+    }
+}

+ 11 - 0
api/Page/Stat.php

@@ -0,0 +1,11 @@
+<?php namespace Manage\Api;
+use Dever;
+use Manage\Lib\Page;
+# 统计页
+class Stat extends Page
+{
+    public function get()
+    {
+        
+    }
+}

+ 224 - 0
api/Page/Update.php

@@ -0,0 +1,224 @@
+<?php namespace Manage\Api\Page;
+use Dever;
+use Manage\Lib\Page;
+# 更新页
+class Update extends Page
+{
+    public function __construct()
+    {
+        parent::__construct('update');
+    }
+    public function get()
+    {
+        $data['update'] = $data['field'] = array();
+        $this->setting('field', $data['update'], true, 'text');
+        foreach ($data['update'] as $k => $v) {
+            $data['field'][$v['key']] = $v['value'];
+        }
+        if ($this->id) {
+            $data['info'] = $this->db->find($this->id);
+            if ($data['info']) {
+                foreach ($data['info'] as $k => $v) {
+                    if (isset($data['field'][$k])) {
+                        if (is_array($data['field'][$k])) {
+                            $v = explode(',', $v);
+                        }
+                        if (isset($this->config['field'][$k]) && isset($this->config['field'][$k]['value'])) {
+                            $v = $this->config['field'][$k]['value'];
+                        }
+                        $data['field'][$k] = $v;
+                    }
+                }
+            }
+        }
+        $data['desc'] = $this->config['desc'] ?? '';
+        $this->layout($data);
+        $this->tab($data, 'step');
+        if (!$data['step']) {
+            $this->tab($data);
+        }
+        return $data;
+    }
+
+    private function tab(&$data, $type = 'tab')
+    {
+        $field = Dever::input('field');
+        $data[$type] = array();
+        if (empty($data['layout']) && !$field && isset($this->config[$type])) {
+            foreach ($this->config[$type] as $k => $v) {
+                if (is_string($v)) {
+                    $field = array();
+                    $data[$type][] = array
+                    (
+                        'name' => $k,
+                        'update' => $this->getUpdate($v, $data['update'], $field),
+                        'field' => $field,
+                    );
+                } else {
+                    $field = array();
+                    $result = array();
+                    $result['name'] = $k;
+                    foreach ($v as $v1) {
+                        $result['layout'][] = $this->getUpdate($v1, $data['update'], $field);
+                    }
+                    $result['field'] = $field;
+                    $data[$type][] = $result;
+                }
+            }
+            $data['update'] = array();
+        }
+    }
+
+    private function layout(&$data)
+    {
+        $field = Dever::input('field');
+        $data['layout'] = array();
+        if (!$field && isset($this->config['layout'])) {
+            foreach ($this->config['layout'] as $k => $v) {
+                $field = array();
+                $data['layout'][] = $this->getUpdate($v, $data['update'], $field);
+            }
+            $data['update'] = array();
+        }
+    }
+
+    private function getUpdate($set, $update, &$field)
+    {
+        $result = array();
+        if (is_string($set)) {
+            $set = explode(',', $set);
+            foreach ($set as $k => $v) {
+                foreach ($update as $value) {
+                    if ($value['key'] == $v) {
+                        $result[] = $value;
+                        $field[] = $v;
+                    }
+                }
+            }
+        } else {
+            foreach ($set as $k => $v) {
+                foreach ($update as $value) {
+                    if ($value['key'] == $k) {
+                        $result[] = array('span' => $v, 'update' => array($value));
+                        $field[] = $k;
+                    }
+                }
+            }
+        }
+        return $result;
+    }
+
+    public function do_commit(){}
+    public function do()
+    {
+        $update = array();
+        $this->setting('update', $update, true, 'text');
+        if ($update) {
+            $data = array();
+            $input = Dever::input();
+            $id = false;
+            if (isset($input['id']) && $input['id'] > 0) {
+                $id = $input['id'];
+            }
+            foreach ($update as $k => $v) {
+                if (isset($input[$v['key']])) {
+                    if (isset($v['rule'])) {
+                        $this->checkRules($v, $input[$v['key']]);
+                    }
+                    $this->doData($data, $v['key'], $input[$v['key']]);
+                } elseif ($id) {
+                    $data[$v['key']] = '';
+                }
+            }
+            if (!$data) {
+                Dever::error('无效数据');
+            }
+            $this->check($id, $data);
+            $this->start($id, $data);
+            if ($id) {
+                $info = $this->db->find($id);
+                if (!$info) {
+                    Dever::error('无效数据');
+                }
+                $state = $this->db->update($info['id'], $data);
+                if ($state) {
+                    $id = $info['id'];
+                }
+            } else {
+                $id = $this->db->insert($data);
+            }
+            if (!$id) {
+                Dever::error('操作失败');
+            }
+            $this->end($id, $data);
+            return '操作成功';
+        }
+    }
+
+    private function doData(&$data, $key, $value)
+    {
+        if (is_array($value)) {
+            if (isset($value[0])) {
+                $value = ltrim(implode(',', $value), ',');
+            } else {
+                $value = Dever::json_ecode($value);
+            }
+        }
+        if ($value && isset($this->config['field'][$key]) && $handle = Dever::isset($this->config['field'][$key], 'handle')) {
+            $value = Dever::call($handle, $value);
+            if (is_array($value) && isset($value[$key])) {
+                foreach ($value as $k => $v) {
+                    $data[$k] = $v;
+                }
+                return;
+            }
+        }
+        $data[$key] = $value;
+    }
+
+    private function check($id, $data)
+    {
+        if (isset($this->config['check']) && $this->config['check']) {
+            $check = explode(',', $this->config['check']);
+            $where = array();
+            $name = array();
+            foreach ($check as $k => $v) {
+                if (isset($data[$v])) {
+                    if (isset($this->config['field'][$v]) && isset($this->config['field'][$v]['name'])) {
+                        $n = $this->config['field'][$v]['name'];
+                    } elseif (isset($this->db->config['struct'][$v])) {
+                        $n = $this->db->config['struct'][$v]['name'];
+                    } else {
+                        $n = $v;
+                    }
+                    $where[$v] = $data[$v];
+                    $name[] = $n;
+                }
+            }
+            if ($where) {
+                if ($id) {
+                    $where['id'] = array('!=', $id);
+                }
+                $info = $this->db->find($where);
+                if ($info) {
+                    $name = implode('、', $name);
+                    Dever::error($name . '已存在');
+                }
+            }
+        }
+    }
+
+    private function start($id, &$data)
+    {
+        if (isset($this->config['start']) && $this->config['start']) {
+            echo $this->config['start'];die;
+        }
+    }
+
+    private function end($id, $data)
+    {
+        if (isset($this->config['end']) && $this->config['end']) {
+            echo $this->config['end'];die;
+        }
+    }
+}

+ 5 - 0
index.php

@@ -0,0 +1,5 @@
+<?php
+define('DEVER_APP_NAME', 'manage');
+define('DEVER_APP_LANG', '管理平台');
+define('DEVER_APP_PATH', dirname(__FILE__) . DIRECTORY_SEPARATOR);
+include(DEVER_APP_PATH . '../boot.php');

+ 27 - 0
lib/Auth.php

@@ -0,0 +1,27 @@
+<?php namespace Manage\Lib;
+use Dever;
+use Dever\Helper\Secure;
+use Dever\Helper\Env;
+class Auth
+{
+    protected $login = true;
+    protected $uid;
+    protected $user;
+    public $data = array();
+    public function __construct()
+    {
+        $auth = Env::header('authorization');
+        if (!$auth) {
+            $info['uid'] = 1;
+            //Dever::error('请先登录');
+        } else {
+            $auth = ltrim($auth, 'Bearer ');
+            $info = Secure::checkLogin($auth, 86400*7);
+            if (!$info && $this->login) {
+                Dever::error('请先登录');
+            }
+        }
+        $this->uid = $info['uid'];
+        $this->user = Dever::db('admin')->find($this->uid);
+    }
+}

+ 17 - 0
lib/Common.php

@@ -0,0 +1,17 @@
+<?php namespace Manage\Lib;
+use Dever;
+use Dever\Helper\Str;
+class Common
+{
+    public function createPwd($password)
+    {
+        $data['salt'] = Str::salt(8);
+        $data['password'] = $this->hash($password, $data['salt']);
+        return $data;
+    }
+
+    public function hash($password, $salt)
+    {
+        return hash('sha256', $password . $salt);
+    }
+}

+ 53 - 0
lib/Menu.php

@@ -0,0 +1,53 @@
+<?php namespace Manage\Lib;
+use Dever;
+use Dever\Project;
+class Menu
+{
+    # 初始化菜单
+    public function init()
+    {
+        $app = Project::read();
+        foreach ($app as $k => $v) {
+            $base = $v['path'] . 'table/manage/core.php';
+            if (is_file($base)) {
+                $menu = include $base;
+                if ($menu) {
+                    $this->add($k, $menu);
+                }
+            }
+        }
+        return 'ok';
+    }
+    private function add($app, $menu)
+    {
+        foreach ($menu as $k => $v) {
+            $where = array();
+            $where['key'] = $app . '/' . $k;
+            $data = $where;
+            $data['name'] = $v['name'];
+            $data['icon'] = $v['icon'];
+            $data['sort'] = $v['sort'];
+            if (isset($v['badge'])) {
+                $data['badge'] = $v['badge'];
+            }
+            if (isset($v['link'])) {
+                $data['link'] = $v['link'];
+            }
+            if (isset($v['parent'])) {
+                if (!strpos($v['parent'], '/')) {
+                    $v['parent'] = $app . '/' . $v['parent'];
+                }
+                $data['parent_key'] = $v['parent'];
+            }
+            if (isset($v['show'])) {
+                $data['show'] = $v['show'];
+            }
+            Dever::db('menu')->up($where, $data);
+        }
+    }
+    public function getAll()
+    {
+        $data = Dever::db('menu')->select(array('parent_key' => '/'));
+        return $data;
+    }
+}

+ 191 - 0
lib/Page.php

@@ -0,0 +1,191 @@
+<?php namespace Manage\Lib;
+use Dever;
+# 通用页面
+class Page extends Auth
+{
+    protected $db;
+    protected $id = false;
+    protected $config = array();
+    public function __construct($key = '')
+    {
+        parent::__construct();
+        $this->id = Dever::input('id');
+        list($app, $table) = explode('/', ltrim(Dever::input('load'), '/'));
+        $this->db = Dever::db($table, $app);
+        if (empty($this->db->config['struct'])) {
+            Dever::error('无效信息');
+        }
+        $app = Dever::project($app);
+        $manage = $app['path'] . 'table/manage/'.$table.'.php';
+        if (is_file($manage)) {
+            $this->db->config['manage'] = include $manage;
+            if ($key) {
+                $this->config = $this->db->config['manage'][$key] ?? array();
+            }
+        }
+    }
+
+    protected function setting($key, &$data, $struct = true, $type = 'show', $disable = false)
+    {
+        if (empty($this->config[$key]) && $struct) {
+            $this->config[$key] = $this->db->config['struct'];
+        }
+        if (empty($this->config[$key])) {
+            return;
+        }
+        $setting = $this->config[$key];
+        if (is_string($setting)) {
+            $setting = explode(',', $setting);
+        }
+        $field = Dever::input('field');
+        $result = array();
+        foreach ($setting as $k => $v) {
+            if ($field) {
+                if (!Dever::check($field, $k)) {
+                    continue;
+                }
+            }
+            if (!is_array($v)) {
+                if (is_string($k) && isset($this->db->config['struct'][$k])) {
+                    $v = array('name' => $this->db->config['struct'][$k]['name'], 'type' => $v);
+                } else {
+                    $k = $v;
+                    $v = array();
+                }
+            } else {
+                if (isset($v['only'])) {
+                    if ($v['only'] == 'edit' && !$this->id) {
+                        continue;
+                    } elseif ($v['only'] == 'add' && $this->id) {
+                        continue;
+                    }
+                }
+            }
+            $result[] = $this->data($data, $k, $v, $type, $disable);
+        }
+        return $result;
+    }
+
+    private function data(&$data, $key, $value = array(), $type = 'show', $disable = false)
+    {
+        $value['key'] = $key;
+        $this->setName($value);
+        $this->setType($value, $type);
+        $this->setDisable($value, $disable);
+        if ($type != 'show') {
+            # 一般为更新页面需要的参数
+            $this->setForm($value);
+            $this->setRules($value);
+        }
+        $data[] = $value;
+        return $value['name'];
+    }
+
+    private function setName(&$value)
+    {
+        if ($value['key'] == 'id') {
+            $value['name'] = 'id';
+        } elseif ($value['key'] == 'cdate') {
+            $value['name'] = '创建时间';
+        } elseif (empty($value['name'])) {
+            $value['name'] = $this->db->config['struct'][$value['key']]['name'];
+        }
+    }
+
+    private function setType(&$value, $type)
+    {
+        if (empty($value['type'])) {
+            $value['type'] = $type;
+        }
+        if (strpos($value['type'], '(')) {
+            $value['type'] = $type;
+        }
+    }
+
+    private function setDisable(&$value, $disable)
+    {
+        if (isset($value['disable'])) {
+            $disable = $value['disable'];
+        }
+        $value['disable'] = $disable;
+    }
+
+    private function setForm(&$value)
+    {
+        $value['value'] = Dever::input('search')[$value['key']] ?? '';
+        if ($option = $this->db->value($value['key'])) {
+            if ($value['type'] == 'checkbox') {
+                $value['value'] = $value['value'] ? explode(',', $value['value']) : array();
+            }
+            $value['option'] = $option;
+            if ($value['type'] == 'text') {
+                $value['type'] = 'select';
+            }
+        }
+    }
+
+    private function setRules(&$value)
+    {
+        if (isset($value['rules']) && $value['rules'] && is_array($value['rules'])) {
+            foreach ($value['rules'] as $k => $v) {
+                if (isset($v['only'])) {
+                    if ($v['only'] == 'edit' && !$this->id) {
+                        unset($value['rules'][$k]);
+                        break;
+                    } elseif ($v['only'] == 'add' && $this->id) {
+                        unset($value['rules'][$k]);
+                        break;
+                    }
+                }
+            }
+            if (!isset($value['rules'][0])) {
+                $value['rules'] = array_values($value['rules']);
+            }
+        }
+    }
+
+    # 通用的规则验证 一般为更新数据时使用
+    protected function checkRules($set, $data)
+    {
+        if ($set['rules']) {
+            foreach ($set['rules'] as $k => $v) {
+                if (isset($v['required']) && $v['required'] && !$data && $data !== 0) {
+                    Dever::error($v['message']);
+                }
+                if (isset($v['pattern']) && $v['pattern'] && $data && !preg_match('/' . $v['pattern'] . '/', $data)) {
+                    Dever::error($v['message']);
+                }
+                if (isset($v['type']) && $v['type']) {
+                    if ($v['type'] == 'number' && !is_numeric($data)) {
+                        Dever::error($v['message']);
+                    } elseif ($v['type'] == 'array' && !is_array($data)) {
+                        Dever::error($v['message']);
+                    } elseif ($v['type'] == 'integer' && !is_int($data)) {
+                        Dever::error($v['message']);
+                    } elseif ($v['type'] == 'float' && !is_float($data)) {
+                        Dever::error($v['message']);
+                    } elseif ($v['type'] == 'string' && !is_string($data)) {
+                        Dever::error($v['message']);
+                    } elseif ($v['type'] == 'boolean' && !is_bool($data)) {
+                        Dever::error($v['message']);
+                    } elseif ($v['type'] == 'url' && !filter_var($data, FILTER_VALIDATE_URL)) {
+                        Dever::error($v['message']);
+                    } elseif ($v['type'] == 'email' && !filter_var($data, FILTER_VALIDATE_EMAIL)) {
+                        Dever::error($v['message']);
+                    } elseif ($v['type'] == 'enum' && isset($v['enum']) && !in_array($data, $v['enum'])) {
+                        Dever::error($v['message']);
+                    }
+                }
+                if (isset($v['len']) && $v['len'] && strlen($data) > $v['len']) {
+                    Dever::error($v['message']);
+                }
+                if (isset($v['min']) && $v['min'] && strlen($data) < $v['min']) {
+                    Dever::error($v['message']);
+                }
+                if (isset($v['max']) && $v['max'] && strlen($data) > $v['max']) {
+                    Dever::error($v['message']);
+                }
+            }
+        }
+    }
+}

+ 49 - 0
table/admin.php

@@ -0,0 +1,49 @@
+<?php
+return array
+(
+    'name' => '管理员',
+    'struct' => array
+    (
+        'name' => array
+        (
+            'name'      => '姓名',
+            'type'      => 'varchar(32)',
+        ),
+        'mobile' => array
+        (
+            'name'      => '手机号',
+            'type'      => 'varchar(11)',
+        ),
+        'password' => array
+        (
+            'name'      => '密码',
+            'type'      => 'varchar(64)',
+        ),
+        'salt' => array
+        (
+            'name'      => '密码salt',
+            'type'      => 'varchar(32)',
+        ),
+        'role' => array
+        (
+            'name'      => '角色',
+            'type'      => 'varchar(100)',
+            # 该字段的值 radio、select、checkbox有效,定义从哪个表获取数据
+            'value'    => 'role',
+            /*
+            'value'    => 'manage/role',//跟role一样,但可以调取别的app的方法
+            'value'    => 'Dever::load("manage/role.get")',//调用某个类的方法
+            'value'    => array(1 => 'a', 2 => 'b'),//直接设置可选项
+            */
+        ),
+        'avatar' => array
+        (
+            'name'      => '头像',
+            'type'      => 'varchar(300)',
+        ),
+    ),
+    'index' => array
+    (
+        'mobile' => 'mobile.unique',
+    ),
+);

+ 311 - 0
table/manage/admin.php

@@ -0,0 +1,311 @@
+<?php
+return array
+(
+    # 列表页配置
+    'list' => array
+    (
+        # 列表页描述
+        'desc' => 'test',
+        # 展示的字段
+        'field'      => 'id,name,mobile,role,cdate',
+        # 列表的高度,auto和100%或者具体数值,默认是auto
+        'height' => 'auto',
+        # 快速过滤字段,默认为name
+        'filter' => 'name',
+        'role' => array
+        (
+            'show' => 'Dever::db("role", "manage")->find({role})',
+        ),
+        # 列表页按钮,默认是快速新增和删除
+        'button' => array
+        (
+            '新增' => 'add',
+            '快速新增' => 'fastadd',
+            '删除' => 'del',
+            '更改角色' => array('oper', 'role'),
+            '链接跳转' => array('link', 'https://www.baidu.com/'),
+            '路由跳转' => array('route', 'manage/menu?test=1'),
+            '回收站' => 'recycler',
+        ),
+        # 列表页每条数据的按钮,默认是快速编辑和删除
+        'data_button' => array
+        (
+            '编辑' => 'edit',
+            # 只处理某个字段,多个用逗号隔开
+            //'编辑' => array('fastedit', 'name'),
+            //'编辑' => array('fastedit'),
+            '删除' => 'del',
+            '操作' => array('oper', 'role'),
+            '详情' => 'view',
+            //'链接跳转' => array('link', 'https://www.baidu.com/'),
+            # 第三个参数可以自定义图标:https://element-plus.org/zh-CN/component/icon.html#icon-collection
+            //'路由跳转' => array('route', 'manage/menu?test=1', 'ChatLineSquare'),
+        ),
+        # 列表页导入
+        'import' => array
+        (
+
+        ),
+        # 列表页导出
+        'export' => array
+        (
+
+        ),
+        # 搜索字段 fulltext 模糊查询
+        'search'    => array
+        (
+            'name',
+            'mobile',
+            'role' => 'group',
+        ),
+    ),
+    
+    # 更新页配置
+    'update' => array
+    (
+        # 更新页描述
+        'desc' => '',
+        # 自定义标签 支持分栏
+        /*
+        'tab' => array
+        (
+            # 不设置分栏
+            '基础设置' => 'name,mobile',
+            # 设置分栏
+            '基础设置' => array
+            (
+                array
+                (
+                    'name' => 12,
+                    'mobile' => 12,
+                ),
+            ),
+            '普通设置' => 'password',
+            '其他设置' => 'role',
+        ),
+        # 自定义步骤 支持分栏 设置后,tab将失效
+        'step' => array
+        (
+            '第一步' => 'name,mobile', 
+            # 设置分栏
+            '第一步' => array
+            (
+                array
+                (
+                    'name' => 12,
+                    'mobile' => 12,
+                ),
+            ),
+            '第二步' => 'password',
+            '提交' => 'role',
+        ),
+        # 自定义布局 24分栏布局,设置后,tab和step里的设置将失效
+        'layout' => array
+        (
+            array
+            (
+                'name' => 12,
+                'mobile' => 12,
+            ),
+            array
+            (
+                'password' => 12,
+                'role' => 12,
+            ),
+        ),*/
+        # 要更新的字段
+        'field'    => array
+        (
+            'name' => array
+            (
+                'type' => 'text',
+                'maxlength' => 30,
+                # 描述
+                'desc' => '',
+            ),
+            /* type的值
+            text:单行文本
+            set:
+            maxlength:最大输入长度
+            minlength:最小输入长度
+
+            password:文本密码框
+            textarea:多行文本
+            rows:多行文本的行数,默认为2
+            editor:编辑器
+
+            number:数字计数器
+            set:
+            step:计数器步数,默认为1
+            min:最小值
+            max:最大值
+            precision:精度,小数点几位
+            position:按钮位置:left、right,不填为两端
+
+            switch:开关
+            set:
+            open_color:开启颜色
+            close_color:关闭颜色
+            open_text:开启文字
+            close_text:关闭文字
+
+            slider:滑块
+            set:
+            max:最大值
+            step:步数
+            stops:是否显示间断点
+            input:是否显示输入框,true/false
+            range:是否范围选择,true/false
+            format:格式化展示数字,这里是一个计算公式,如{a}/100,{a}为当前值变量名
+
+            radio:单选
+            radio_button:单选按钮样式
+            set:
+            border:单选边框样式
+
+            checkbox:多选
+            checkbox_button:多选按钮样式
+            set:
+            border:多选边框样式
+            min:多选限制选择数目,最小选择数目
+            max:多选限制选择数目,最大选择数目
+
+            tree:树形选择器
+            area:地区选择器
+            select:选择器
+            set:
+            clearable:是否可清空选择,true/false
+            multiple:开启多选,true/false
+            filterable:开启搜索,true/false
+
+            rate:评分
+            set:
+            score:是否显示分数
+            text:是否显示文字
+
+            date:日期,不带具体时间
+            date_type:
+            dates:多个日期
+            year:只显示年
+            month:只显示月
+            week:只显示周
+            datetime:带有时间的日期
+            datetimerange:带有时间的日期范围
+            daterange:日期范围
+            monthrange:月份范围
+            set:
+            disable_func:禁止选择方法,需要根据不同的date_type实现不同的方法,默认为不能选择今天之后的日期:return time.getTime() > Date.now()
+            format:格式化展示,YYYY-MM-DD,参考:https://day.js.org/docs/en/display/format#list-of-all-available-formats
+            shortcuts:默认展示的日期,值如:
+            array
+            (
+                array
+                (
+                    'text' => '今天',
+                    'func' => 'return new Date()',
+                ),
+            ),
+            start_placeholder:开始日期文字描述
+            end_placeholder:结束日期文字描述
+            range_separator:日期范围文字描述
+            step:步数
+            start:开始日期
+            end:结束日期
+            default:默认日期
+
+            time:仅选择时间
+            step:步数
+            start:开始时间
+            end:结束时间
+
+            */
+            'mobile' => array
+            (
+                # 仅限编辑,值为add/edit,不填则所有有效
+                //'only'      => 'edit',
+                'name'      => '手机号',
+                'type'      => 'text',
+                'disable'   => false,//是否禁用
+                'placeholder' => '',//提示语
+                # 校验规则,如无rules,默认是必填, rules => false,就是选填
+                # 参考:https://github.com/yiminghe/async-validator
+                'rules'     => array
+                (
+                    # 规则1
+                    array
+                    (
+                        # 必填
+                        'required' => true,
+                        # 输入后触发
+                        'trigger' => 'blur',
+                        # 提示信息
+                        'message' => '请输入手机号',
+                    ),
+                    # 规则2
+                    array
+                    (
+                        # 最小字符
+                        //'min' => 3,
+                        # 最大字符
+                        //'max' => 5,
+                        # 长度
+                        'len' => 11,
+                        # 正则
+                        'pattern' => Dever::rule('mobile', ''),
+                        'trigger' => 'blur',
+                        # 提示信息
+                        'message' => '手机号错误',
+                        # 验证类型 date,array,number,boolean,integer,float,url,email,enum,string
+                        'type' => 'string',
+                    ),
+                ),
+            ),
+            'password' => array
+            (
+                'type' => 'password',
+                # 更新时的value,始终是空的,有值才更新
+                'value' => '',
+                # 对更新的值进行处理
+                'handle' => 'manage/common.createPwd',
+                'rules'     => array
+                (
+                    array
+                    (
+                        # 仅限新增时必填,值为add/edit,不填则所有有效
+                        'only' => 'add',
+                        'required' => true,
+                        'trigger' => 'blur',
+                        'message' => '请输入密码',
+                    ),
+                    array
+                    (
+                        'min' => 6,
+                        'max' => 18,
+                        'trigger' => 'blur',
+                        'message' => '密码长度不能超过18或者少于6个字符',
+                    ),
+                ),
+            ),
+            'role' => 'checkbox',
+            # 定义另外一个表或者方法里的字段,带有"."就是方法,调用manage/role里的upload配置,这里也可以自行配置
+            //'manage/role' => 'upload',
+            'avatar' => array
+            (
+                'type' => 'upload',
+                # 获取上传组件的配置
+                'upload' => 'pic',
+                # 显示的样式 可选值:'text' | 'picture' | 'picture-card'
+                'show' => 'picture-card',
+                # 是否支持多选
+                'multiple' => true,
+            ),
+        ),
+
+        # update提交之前的操作,需要验证哪些字段唯一,多个用逗号隔开
+        'check' => 'mobile',
+        # update提交之前的操作,多个用逗号隔开
+        'start' => '',
+        # update提交之后的操作,多个用逗号隔开
+        'end' => '',
+    ),
+);

+ 51 - 0
table/manage/core.php

@@ -0,0 +1,51 @@
+<?php
+# 后台核心配置 这里配置菜单即可
+# 图标 https://vue-admin-beautiful.com/admin-plus/#/vab/icon/remixIcon
+return array
+(
+    # 定义父级菜单
+    'set' => array
+    (
+        # 菜单名称
+        'name' => '配置',
+        # 菜单图标
+        'icon' => 'settings-line',
+        # 菜单排序 正序
+        'sort' => '100',
+    ),
+    # 定义子级菜单 key为表名,如果不是表名则为自定义菜单,link填写不同
+    'admin'      => array
+    (
+        # 上级菜单
+        'parent'    => 'set',
+        # 菜单名称
+        'name'      => '用户管理',
+        # 菜单图标
+        'icon'      => 'user-settings-line',
+        # 菜单排序 正序
+        'sort'      => '1',
+        # 菜单链接 可选项:table[可选项:table、list、photo]、update、stat
+        'link'      => 'table',
+        # 标签 这里需要设置获取标签的方法
+        'badge'     => 'test.badge',
+    ),
+
+    'menu'      => array
+    (
+        'parent'    => 'set',
+        'name'      => '菜单管理',
+        'icon'      => 'menu-line',
+        'sort'      => '2',
+        'link'      => 'table',
+    ),
+
+    'recycler'      => array
+    (
+        'parent'    => 'set',
+        'name'      => '回收站',
+        'icon'      => '',
+        'sort'      => '3',
+        'link'      => 'table',
+        'show'      => 2,
+    ),
+);

+ 16 - 0
table/manage/recycler.php

@@ -0,0 +1,16 @@
+<?php
+return array
+(
+    'list' => array
+    (
+        'field'      => 'id,table,table_id,content,cdate',
+        'search'    => array
+        (
+            'table' => 'hidden',
+        ),
+        'button' => array
+        (
+            '恢复' => 'recover',
+        ),
+    ),
+);

+ 51 - 0
table/menu.php

@@ -0,0 +1,51 @@
+<?php
+return array
+(
+    'name' => '菜单',
+    'order' => 'sort asc',
+    'struct' => array
+    (
+        'name' => array
+        (
+            'name'      => '名称',
+            'type'      => 'varchar(32)',
+        ),
+        'key' => array
+        (
+            'name'      => '标识',
+            'type'      => 'varchar(60)',
+        ),
+        'parent_key' => array
+        (
+            'name'      => '上级菜单',
+            'type'      => 'varchar(60)',
+            'default'   => '/',
+        ),
+        'link' => array
+        (
+            'name'      => '路径',
+            'type'      => 'varchar(2000)',
+        ),
+        'icon' => array
+        (
+            'name'      => '图标',
+            'type'      => 'varchar(150)',
+        ),
+        'badge' => array
+        (
+            'name'      => '标签',
+            'type'      => 'varchar(32)',
+        ),
+        'sort' => array
+        (
+            'name'      => '排序',
+            'type'      => 'int(11)',
+        ),
+        'show' => array
+        (
+            'name'      => '是否展示',
+            'type'      => 'tinyint(1)',
+            'default'   => 1,
+        ),
+    ),
+);

+ 23 - 0
table/recycler.php

@@ -0,0 +1,23 @@
+<?php
+return array
+(
+    'name' => '回收站',
+    'struct' => array
+    (
+        'table' => array
+        (
+            'name'      => '表名',
+            'type'      => 'varchar(200)',
+        ),
+        'table_id' => array
+        (
+            'name'      => '表id',
+            'type'      => 'int(11)',
+        ),
+        'content' => array
+        (
+            'name'      => '表内容',
+            'type'      => 'text(255)',
+        ),
+    ),
+);

+ 13 - 0
table/role.php

@@ -0,0 +1,13 @@
+<?php
+return array
+(
+    'name' => '角色',
+    'struct' => array
+    (
+        'name' => array
+        (
+            'name'      => '名称',
+            'type'      => 'varchar(32)',
+        ),
+    ),
+);