更新:迁移cms基本信息,初始化超宇项目
parent
10e948a980
commit
2024c80bbf
|
@ -0,0 +1,32 @@
|
|||
|
||||
ThinkPHP遵循Apache2开源协议发布,并提供免费使用。
|
||||
版权所有Copyright © 2006-2016 by ThinkPHP (http://thinkphp.cn)
|
||||
All rights reserved。
|
||||
ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。
|
||||
|
||||
Apache Licence是著名的非盈利开源组织Apache采用的协议。
|
||||
该协议和BSD类似,鼓励代码共享和尊重原作者的著作权,
|
||||
允许代码修改,再作为开源或商业软件发布。需要满足
|
||||
的条件:
|
||||
1. 需要给代码的用户一份Apache Licence ;
|
||||
2. 如果你修改了代码,需要在被修改的文件中说明;
|
||||
3. 在延伸的代码中(修改和有源代码衍生的代码中)需要
|
||||
带有原来代码中的协议,商标,专利声明和其他原来作者规
|
||||
定需要包含的说明;
|
||||
4. 如果再发布的产品中包含一个Notice文件,则在Notice文
|
||||
件中需要带有本协议内容。你可以在Notice中增加自己的
|
||||
许可,但不可以表现为对Apache Licence构成更改。
|
||||
具体的协议参考:http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,7 @@
|
|||
本CMS基于ThinkPHP 6.0.3开发
|
||||
|
||||
> 运行环境要求PHP7.1+。
|
||||
> MySql版本使用的是5.7.21
|
||||
> 富文本编辑器选择wangEditorv3.1.1 https://github.com/wangfupeng1988/wangEditor
|
||||
> 上传插件选择filepond4.7.1 https://github.com/pqina/filepond
|
||||
> 上传插件修改为使用layui组件库自带的
|
|
@ -0,0 +1,367 @@
|
|||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: 流年 <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
use think\exception\ClassNotFoundException;
|
||||
|
||||
// 应用公共文件
|
||||
|
||||
if (!function_exists('widget')) {
|
||||
/**
|
||||
* 渲染输出Widget
|
||||
* @param string $name Widget名称
|
||||
* @param array $data 传入的参数
|
||||
* @return mixed
|
||||
* milo 2019-05-08 从TP5.1代码中拿来修改的
|
||||
*/
|
||||
function widget($name, $data = [])
|
||||
{
|
||||
return action($name, $data, 'widget');
|
||||
}
|
||||
}
|
||||
if (!function_exists('action')) {
|
||||
/**
|
||||
* 调用模块的操作方法 参数格式 [模块/控制器/]操作
|
||||
* @param string $url 调用地址
|
||||
* @param string|array $vars 调用参数 支持字符串和数组
|
||||
* @param string $layer 要调用的控制层名称
|
||||
* @param bool $appendSuffix 是否添加类名后缀
|
||||
* @return mixed
|
||||
* milo 2019-05-08 从TP5.1代码中拿来修改的
|
||||
*/
|
||||
function action($url, $vars = [], $layer = 'controller', $appendSuffix = false)
|
||||
{
|
||||
$info = pathinfo($url);
|
||||
$action = $info['basename'];
|
||||
$module = '.' != $info['dirname'] ? $info['dirname'] : request()->controller();
|
||||
$class = controller($module, $layer);
|
||||
if (is_scalar($vars)) {
|
||||
if (strpos($vars, '=')) {
|
||||
parse_str($vars, $vars);
|
||||
} else {
|
||||
$vars = [$vars];
|
||||
}
|
||||
}
|
||||
return app()->invokeMethod([$class, $action . config('route.action_suffix')], $vars);
|
||||
}
|
||||
}
|
||||
if (!function_exists('controller')) {
|
||||
/**
|
||||
* 实例化(分层)控制器 格式:[模块名/]控制器名
|
||||
* @access public
|
||||
* @param string $name 资源地址
|
||||
* @param string $layer 控制层名称
|
||||
* @param bool $appendSuffix 是否添加类名后缀
|
||||
* @param string $empty 空控制器名称
|
||||
* @return object
|
||||
* @throws ClassNotFoundException
|
||||
*
|
||||
* milo 2019-05-08 从TP5.1代码中拿来修改的
|
||||
*/
|
||||
function controller($name, $layer = 'controller', $empty = '')
|
||||
{
|
||||
$class = parseClass($name, $layer);
|
||||
if (class_exists($class)) {
|
||||
return app()->make($class);
|
||||
} elseif ($empty && class_exists($emptyClass = app()->parseClass($layer, $empty))) {
|
||||
return app()->make($emptyClass);
|
||||
}
|
||||
|
||||
throw new ClassNotFoundException('class not exists:' . $class, $class);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('parseClass')) {
|
||||
/**
|
||||
* 解析模块和类名
|
||||
* @access protected
|
||||
* @param string $name 资源地址
|
||||
* @param string $layer 验证层名称
|
||||
* @param bool $appendSuffix 是否添加类名后缀
|
||||
* @return array
|
||||
*
|
||||
* milo 2019-05-08 从TP5.1代码中拿来修改的
|
||||
*/
|
||||
function parseClass($name, $layer)
|
||||
{
|
||||
if (false !== strpos($name, '\\')) {
|
||||
$class = $name;
|
||||
} else {
|
||||
if (strpos($name, '/')) {
|
||||
$names = explode('/', $name, 2);
|
||||
$name = $names[1];
|
||||
}
|
||||
$class = app()->parseClass($layer,$name);
|
||||
}
|
||||
return $class;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!function_exists('randomStr')) {
|
||||
/**
|
||||
* 获取随机字符串
|
||||
* @param int $type 0:数字(默认);1:全部;2:小写字母;3:大写字母;4:字母;
|
||||
* @param int $len 字符串长度
|
||||
* @return string
|
||||
*/
|
||||
function randomStr($type = 0,$len = 5)
|
||||
{
|
||||
$strPol = "0123456789";
|
||||
if($type == 1) {
|
||||
$strPol = "ABCDEFGHIJKLMOPQRSTUVWYZ0123456789abcdefghijklmopqrstuvwyz";
|
||||
} elseif ($type == 2) {
|
||||
$strPol = "abcdefghijklmopqrstuvwyz";
|
||||
} elseif ($type == 3) {
|
||||
$strPol = "ABCDEFGHIJKLMOPQRSTUVWYZ";
|
||||
} elseif ($type == 4) {
|
||||
$strPol = "ABCDEFGHIJKLMOPQRSTUVWYZabcdefghijklmopqrstuvwyz";
|
||||
}
|
||||
$max = strlen($strPol) - 1;
|
||||
$str = '';
|
||||
for ($i=0;$i<$len;$i++) {
|
||||
$str .= $strPol[rand(0,$max)];
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
}
|
||||
|
||||
if(!function_exists('isMobile')){
|
||||
//判断访问终端是否为移动端
|
||||
function isMobile()
|
||||
{
|
||||
// 如果有HTTP_X_WAP_PROFILE则一定是移动设备
|
||||
if (isset($_SERVER['HTTP_X_WAP_PROFILE'])) {
|
||||
return true;
|
||||
}
|
||||
// 如果via信息含有wap则一定是移动设备,部分服务商会屏蔽该信息
|
||||
if (isset($_SERVER['HTTP_VIA'])) {
|
||||
// 找不到为flase,否则为true
|
||||
return stristr($_SERVER['HTTP_VIA'], "wap") ? true : false;
|
||||
}
|
||||
// 脑残法,判断手机发送的客户端标志,兼容性有待提高。其中'MicroMessenger'是电脑微信
|
||||
if (isset($_SERVER['HTTP_USER_AGENT'])) {
|
||||
$clientkeywords = ['nokia','sony','ericsson','mot','samsung','htc','sgh','lg','sharp','sie-','philips','panasonic','alcatel','lenovo','iphone','ipod','blackberry','meizu','android','netfront','symbian','ucweb','windowsce','palm','operamini','operamobi','openwave','nexusone','cldc','midp','wap','mobile','MicroMessenger'];
|
||||
// 从HTTP_USER_AGENT中查找手机浏览器的关键字
|
||||
if (preg_match("/(" . implode('|', $clientkeywords) . ")/i", strtolower($_SERVER['HTTP_USER_AGENT']))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// 协议法,因为有可能不准确,放到最后判断
|
||||
if (isset ($_SERVER['HTTP_ACCEPT'])) {
|
||||
// 如果只支持wml并且不支持html那一定是移动设备
|
||||
// 如果支持wml和html但是wml在html之前则是移动设备
|
||||
if ((strpos($_SERVER['HTTP_ACCEPT'], 'vnd.wap.wml') !== false) && (strpos($_SERVER['HTTP_ACCEPT'], 'text/html') === false || (strpos($_SERVER['HTTP_ACCEPT'], 'vnd.wap.wml') < strpos($_SERVER['HTTP_ACCEPT'], 'text/html')))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
//根据栏目获取路径
|
||||
if(!function_exists('getUri')){
|
||||
function getUri($cate)
|
||||
{
|
||||
$url = '';
|
||||
if(!empty($cate)){
|
||||
if($cate['is_index']){
|
||||
$url = '/';
|
||||
}elseif(!empty($cate['url'])){
|
||||
$url = $cate['url'];
|
||||
}else{
|
||||
$url = url($cate['template'].'/index', ['category_id' => $cate['id']]);
|
||||
}
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
|
||||
//根据文件大小转换为文字
|
||||
if(!function_exists('sizeToStr')){
|
||||
function sizeToStr($size)
|
||||
{
|
||||
if(!is_numeric($size) || $size <= 0){
|
||||
return '';
|
||||
}
|
||||
$size = $size / 1024;
|
||||
if($size < 1024){
|
||||
return sprintf("%.2fK", $size);
|
||||
}
|
||||
$size = $size / 1024;
|
||||
if($size < 1024){
|
||||
return sprintf("%.2fM", $size);
|
||||
}
|
||||
$size = $size / 1024;
|
||||
return sprintf("%.2fG", $size);
|
||||
}
|
||||
}
|
||||
|
||||
//根据路径获取文件名
|
||||
if(!function_exists('getKeyByPath')){
|
||||
function getKeyByPath($path)
|
||||
{
|
||||
return substr($path, strrpos($path, '/')+1);
|
||||
}
|
||||
}
|
||||
|
||||
//富文本中提取图片路径
|
||||
if(!function_exists('getImageUrlFromText')){
|
||||
function getImageUrlFromText($content)
|
||||
{
|
||||
preg_match_all('/<img.*?src="(.*?)".*?>/is', $content, $imgs);
|
||||
$data = [];
|
||||
if(!empty($imgs) && !empty($imgs[1])){
|
||||
foreach($imgs[1] as $img){
|
||||
if(substr($img, 0, 4) != 'http'){
|
||||
$key = getKeyByPath($img);
|
||||
$data[$key] = $img;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
//富文本中提取视频路径
|
||||
if(!function_exists('getVideoUrlFromText')){
|
||||
function getVideoUrlFromText($content)
|
||||
{
|
||||
preg_match_all('/<video.*?src="(.*?)".*?>/is', $content, $videos);
|
||||
$data = [];
|
||||
if(!empty($videos) && !empty($videos[1])){
|
||||
foreach($videos[1] as $video){
|
||||
if(substr($video, 0, 4) != 'http'){
|
||||
$key = getKeyByPath($video);
|
||||
$data[$key] = $video;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
//获取目录下的所有文件
|
||||
if(!function_exists('getAllFilesByPath')){
|
||||
function getAllFilesByPath($path, $rootPath)
|
||||
{
|
||||
if(is_dir($path) && file_exists($path)){
|
||||
$items = scandir($path);
|
||||
$files = [];
|
||||
foreach($items as $item){
|
||||
if(substr($item, 0, 1) != '.' && strpos($item, '_') == false){
|
||||
$itemPath = $path . '/' . $item;
|
||||
if(is_file($itemPath)){
|
||||
$size = filesize($itemPath);
|
||||
$files[$item] = [
|
||||
'path' => str_replace($rootPath, '/', $itemPath),
|
||||
'realPath' => $itemPath,
|
||||
'size' => $size,
|
||||
'sizeStr' => sizeToStr($size),
|
||||
'suffix' => strtolower(substr($item, strrpos($item, '.')+1)) //后缀
|
||||
];
|
||||
}elseif(is_dir($itemPath)){
|
||||
$childFiles = getAllFilesByPath($itemPath, $rootPath);
|
||||
if(!empty($childFiles)){
|
||||
$files = array_merge($files, $childFiles);
|
||||
}else{
|
||||
rmdir($itemPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $files;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
//过滤get输入
|
||||
if(!function_exists('getFilter')){
|
||||
function getFilter($value){
|
||||
$getFilter = "'|(and|or)\b.+?(>|<|=|in|like)|\/\*.+?\*\/|<\s*script\b|\bEXEC\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\s+(TABLE|DATABASE)";
|
||||
$forArray = false;
|
||||
if(is_array($value)){
|
||||
$forFilter = implode($value);
|
||||
$forArray = true;
|
||||
}else{
|
||||
$forFilter = $value;
|
||||
}
|
||||
if (preg_match("/".$getFilter."/is", $forFilter) == 1){
|
||||
$value = $forArray ? [] : '';
|
||||
}
|
||||
return filterExp($value);
|
||||
}
|
||||
}
|
||||
|
||||
//过滤post录入
|
||||
if(!function_exists($postFilter)){
|
||||
function postFilter($value){
|
||||
$postFilter = "\b(and|or)\b.{1,6}?(=|>|<|\bin\b|\blike\b)|\/\*.+?\*\/|<\s*script\b|\bEXEC\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\s+(TABLE|DATABASE)";
|
||||
$forArray = false;
|
||||
if(is_array($value)){
|
||||
$forFilter = implode($value);
|
||||
$forArray = true;
|
||||
}else{
|
||||
$forFilter = $value;
|
||||
}
|
||||
if (preg_match("/".$postFilter."/is", $forFilter) == 1){
|
||||
$value = $forArray ? [] : '';
|
||||
}
|
||||
return filterExp($value);
|
||||
}
|
||||
}
|
||||
|
||||
//过滤cookie数据
|
||||
if(!function_exists('cookieFilter')){
|
||||
function cookieFilter($value){
|
||||
$cookieFilter = "\b(and|or)\b.{1,6}?(=|>|<|\bin\b|\blike\b)|\/\*.+?\*\/|<\s*script\b|\bEXEC\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\s+(TABLE|DATABASE)";
|
||||
$forArray = false;
|
||||
if(is_array($value)){
|
||||
$forFilter = implode($value);
|
||||
$forArray = true;
|
||||
}else{
|
||||
$forFilter = $value;
|
||||
}
|
||||
if (preg_match("/".$cookieFilter."/is", $forFilter) == 1){
|
||||
$value = $forArray ? [] : '';
|
||||
}
|
||||
return filterExp($value);
|
||||
}
|
||||
}
|
||||
|
||||
if(!function_exists('filterExp')){
|
||||
function filterExp($value){
|
||||
$filter = '/^EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT LIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOTIN|NOT IN|IN$/i';
|
||||
$forArray = false;
|
||||
if(is_array($value)){
|
||||
$forFilter = implode($value);
|
||||
$forArray = true;
|
||||
}else{
|
||||
$forFilter = $value;
|
||||
}
|
||||
if (preg_match($filter, $forFilter) == 1){
|
||||
$value = $forArray ? [] : '';
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
if(!function_exists('arrayHtmlFilter')){
|
||||
function arrayHtmlFilter($arr) {
|
||||
foreach ($arr as $k => $one) {
|
||||
if(is_array($one)) {
|
||||
$arr[$k] = arrayHtmlFilter($one);
|
||||
} else {
|
||||
$one = trim($one);
|
||||
$arr[$k] = strip_tags($one);
|
||||
}
|
||||
}
|
||||
return $arr;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
namespace app\controller;
|
||||
|
||||
use app\model\{Article as MArticle, Category};
|
||||
|
||||
class Article extends Base
|
||||
{
|
||||
//详情
|
||||
public function detail($id=0)
|
||||
{
|
||||
if($id <= 0){
|
||||
return $this->error('错误页面');
|
||||
}
|
||||
$article = MArticle::getById($id);
|
||||
if(empty($article)){
|
||||
return $this->error('无此文章');
|
||||
}
|
||||
MArticle::updateById($id, ['views' => $article['views'] + 1]);
|
||||
$category = Category::getById($article['category_id']);
|
||||
$prev = MArticle::getPrevArticleByIdAndCategoryId($id, $article['category_id']);
|
||||
$next = MArticle::getNextArticleByIdAndCategoryId($id, $article['category_id']);
|
||||
|
||||
$keywords = $article['seo_keywords'] ? $article['seo_keywords'] : $this->system['seo_keywords'];
|
||||
$description = $article['seo_description'] ? $article['seo_description'] : $this->system['seo_description'];
|
||||
$this->setSeo($article['title'], $keywords, $description);
|
||||
$this->data['article'] = $article;
|
||||
$this->data['category'] = $category;
|
||||
$this->data['categoryId'] = $category['id'];
|
||||
$this->data['prev'] = $prev;
|
||||
$this->data['next'] = $next;
|
||||
return $this->view($category['template_detail'] ?? '');
|
||||
}
|
||||
//列表页
|
||||
public function index()
|
||||
{
|
||||
$categoryId = input('param.category_id/d', 0);
|
||||
if($categoryId <= 0){
|
||||
return $this->error('错误页面');
|
||||
}
|
||||
$category = Category::getById($categoryId);
|
||||
if(empty($category)){
|
||||
return $this->error('错误页面');
|
||||
}
|
||||
$childCategory = Category::getChildrenByParentId($categoryId);
|
||||
$description = $category['description'] ? $category['description'] : $this->system['seo_description'];
|
||||
$this->setSeo($category['title'], $this->system['seo_keywords'], $description);
|
||||
$this->data['items'] = MArticle::getListPageByCategory($categoryId, $category['number'] ? $category['number'] : 20);
|
||||
$this->data['category'] = $category;
|
||||
$this->data['categoryId'] = $categoryId;
|
||||
return $this->view($category['template_list'] ?? '');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
namespace app\controller;
|
||||
|
||||
use app\controller\BaseController;
|
||||
use app\model\System;
|
||||
|
||||
/**
|
||||
* 控制器基础类
|
||||
*/
|
||||
class Base extends BaseController
|
||||
{
|
||||
//需要向模板传递的值
|
||||
protected $data = [];
|
||||
//系统配置信息
|
||||
protected $system = [];
|
||||
|
||||
// 初始化
|
||||
protected function initialize()
|
||||
{
|
||||
$this->middleware = ['csrf'];
|
||||
$this->system = System::getSystem();
|
||||
$this->data['system'] = $this->system;
|
||||
$this->setCsrfToken();
|
||||
}
|
||||
|
||||
//设置SEO信息
|
||||
protected function setSeo($title, $keywords, $description)
|
||||
{
|
||||
$this->data['seoTitle'] = $title;
|
||||
$this->data['seoKeywords'] = $keywords;
|
||||
$this->data['seoDescription'] = $description;
|
||||
}
|
||||
|
||||
//设置默认SEO信息
|
||||
protected function setDefaultSeo()
|
||||
{
|
||||
$this->data['seoTitle'] = $this->system['seo_title'];
|
||||
$this->data['seoKeywords'] = $this->system['seo_keywords'];
|
||||
$this->data['seoDescription'] = $this->system['seo_description'];
|
||||
}
|
||||
|
||||
//模板
|
||||
protected function view($template = '')
|
||||
{
|
||||
return view($template)->assign($this->data);
|
||||
}
|
||||
|
||||
protected function setCsrfToken()
|
||||
{
|
||||
$this->data['_token'] = session('_token') ?? '';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
<?php
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace app\controller;
|
||||
|
||||
use think\{App, Validate};
|
||||
use think\exception\ValidateException;
|
||||
|
||||
/**
|
||||
* 控制器基础类
|
||||
*/
|
||||
abstract class BaseController
|
||||
{
|
||||
/**
|
||||
* Request实例
|
||||
* @var \think\Request
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* 应用实例
|
||||
* @var \think\App
|
||||
*/
|
||||
protected $app;
|
||||
|
||||
/**
|
||||
* 是否批量验证
|
||||
* @var bool
|
||||
*/
|
||||
protected $batchValidate = false;
|
||||
|
||||
/**
|
||||
* 控制器中间件
|
||||
* @var array
|
||||
*/
|
||||
protected $middleware = [];
|
||||
|
||||
/**
|
||||
* 构造方法
|
||||
* @access public
|
||||
* @param App $app 应用对象
|
||||
*/
|
||||
public function __construct(App $app)
|
||||
{
|
||||
$this->app = $app;
|
||||
$this->request = $this->app->request;
|
||||
|
||||
// 控制器初始化
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
// 初始化
|
||||
protected function initialize()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证数据
|
||||
* @access protected
|
||||
* @param array $data 数据
|
||||
* @param string|array $validate 验证器名或者验证规则数组
|
||||
* @param array $message 提示信息
|
||||
* @param bool $batch 是否批量验证
|
||||
* @return array|string|true
|
||||
* @throws ValidateException
|
||||
*/
|
||||
protected function validate(array $data, $validate, array $message = [], bool $batch = false)
|
||||
{
|
||||
if (is_array($validate)) {
|
||||
$v = new Validate();
|
||||
$v->rule($validate);
|
||||
} else {
|
||||
if (strpos($validate, '.')) {
|
||||
// 支持场景
|
||||
list($validate, $scene) = explode('.', $validate);
|
||||
}
|
||||
$class = false !== strpos($validate, '\\') ? $validate : $this->app->parseClass('validate', $validate);
|
||||
$v = new $class();
|
||||
if (!empty($scene)) {
|
||||
$v->scene($scene);
|
||||
}
|
||||
}
|
||||
|
||||
$v->message($message);
|
||||
|
||||
// 是否批量验证
|
||||
if ($batch || $this->batchValidate) {
|
||||
$v->batch(true);
|
||||
}
|
||||
|
||||
return $v->failException(true)->check($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作成功跳转的快捷方法
|
||||
* @access protected
|
||||
* @param mixed $msg 提示信息
|
||||
* @param string $url 跳转的URL地址
|
||||
* @param mixed $data 返回的数据
|
||||
* @param integer $wait 跳转等待时间
|
||||
* @param array $header 发送的Header信息
|
||||
* @return void
|
||||
*/
|
||||
protected function success($msg = '', string $url = null, $data = '', int $wait = 3, array $header = [])
|
||||
{
|
||||
if (is_null($url) && isset($_SERVER["HTTP_REFERER"])) {
|
||||
$url = $_SERVER["HTTP_REFERER"];
|
||||
} elseif ($url) {
|
||||
$url = (strpos($url, '://') || 0 === strpos($url, '/')) ? $url : $this->app->route->buildUrl($url);
|
||||
}
|
||||
|
||||
$result = [
|
||||
'code' => 1,
|
||||
'msg' => $msg,
|
||||
'data' => $data,
|
||||
'url' => $url,
|
||||
'wait' => $wait,
|
||||
];
|
||||
return $this->redirect(url('error/jump',$result));
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作错误跳转的快捷方法
|
||||
* @access protected
|
||||
* @param mixed $msg 提示信息
|
||||
* @param string $url 跳转的URL地址
|
||||
* @param mixed $data 返回的数据
|
||||
* @param integer $wait 跳转等待时间
|
||||
* @param array $header 发送的Header信息
|
||||
* @return void
|
||||
*/
|
||||
protected function error($msg = '', string $url = null, $data = '', int $wait = 3)
|
||||
{
|
||||
if (is_null($url)) {
|
||||
$referer = $_SERVER['HTTP_REFERER'] ?? null;
|
||||
if (empty($referer)) {
|
||||
$url = $this->request->isAjax() ? '' : '/';
|
||||
} else {
|
||||
$url = $this->request->isAjax() ? '' : 'javascript:history.back(-1);';
|
||||
}
|
||||
} elseif ($url) {
|
||||
$url = (strpos($url, '://') || 0 === strpos($url, '/')) ? $url : $this->app->route->buildUrl($url);
|
||||
}
|
||||
$result = [
|
||||
'code' => 0,
|
||||
'msg' => $msg,
|
||||
'data' => $data,
|
||||
'url' => $url,
|
||||
'wait' => $wait,
|
||||
];
|
||||
|
||||
return $this->redirect(url('error/jump', $result));
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回封装后的API数据到客户端
|
||||
* 以json格式抛出异常
|
||||
* @access protected
|
||||
* @param mixed $data 要返回的数据
|
||||
* @param integer $code 返回的code
|
||||
* @param mixed $msg 提示信息
|
||||
* @param string $type 返回数据格式
|
||||
* @param array $header 发送的Header信息
|
||||
* @return void
|
||||
*/
|
||||
protected function json($code = 0, $msg = 'ok', $data= [])
|
||||
{
|
||||
$result = [
|
||||
'code' => $code,
|
||||
'msg' => $msg,
|
||||
'time' => time(),
|
||||
'data' => $data
|
||||
];
|
||||
return json($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* URL重定向
|
||||
* @access protected
|
||||
* @param string $url 跳转的URL表达式
|
||||
* @param array|integer $params 其它URL参数
|
||||
* @param integer $code http code
|
||||
* @param array $with 隐式传参
|
||||
* @return void
|
||||
*/
|
||||
protected function redirect($url)
|
||||
{
|
||||
if(!is_string($url)){
|
||||
$url = $url->__toString();
|
||||
}
|
||||
return redirect($url);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace app\controller;
|
||||
|
||||
|
||||
class Error extends BaseController
|
||||
{
|
||||
public function __call($method, $args)
|
||||
{
|
||||
if(request()->isAjax()) {
|
||||
return $this->json(404, 'error request!');
|
||||
} else {
|
||||
$referer = $_SERVER['HTTP_REFERER'] ?? null;
|
||||
if (empty($referer)) {
|
||||
$url = '/';
|
||||
} else {
|
||||
$domain = $this->request->domain();
|
||||
$urlInfo = parse_url($referer);
|
||||
$scheme = $urlInfo['scheme'] ?? '';
|
||||
$requestSrc = '';
|
||||
if (!empty($scheme)) {
|
||||
$requestSrc = $scheme.'://'.($urlInfo['host'] ?? '');
|
||||
}
|
||||
if($domain != $requestSrc) {
|
||||
$url = '/';
|
||||
} else {
|
||||
$url = 'javascript:history.back(-1);';
|
||||
}
|
||||
}
|
||||
$result = [
|
||||
'code' => 404,
|
||||
'msg' => '无效请求! 没有找到相关资源',
|
||||
'data' => [],
|
||||
'url' => $url,
|
||||
'wait' => 5,
|
||||
];
|
||||
return view('error/400')->assign($result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function jump()
|
||||
{
|
||||
$param = request()->param();
|
||||
return view()->assign($param);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
namespace app\controller;
|
||||
|
||||
use app\model\{Category, Block};
|
||||
|
||||
class Index extends Base
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$category = Category::getIndex();
|
||||
$categoryId = $category['id'] ?? 0;
|
||||
$this->data['categoryId'] = $categoryId;
|
||||
$this->setSeo($this->system['seo_title'], $this->system['seo_keywords'], $this->system['seo_description']);
|
||||
$this->data['blocks'] = Block::getByCategoryId($categoryId);
|
||||
return $this->view();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
namespace app\controller;
|
||||
|
||||
use app\model\{Category, Block};
|
||||
|
||||
class Page extends Base
|
||||
{
|
||||
public function index($categoryId)
|
||||
{
|
||||
$category = Category::getById($categoryId);
|
||||
if ($category) {
|
||||
$description = $category['description'] ? $category['description'] : $this->system['seo_description'];
|
||||
$this->setSeo($category['title'], $this->system['seo_keywords'], $description);
|
||||
} else {
|
||||
return $this->error('错误页面');
|
||||
}
|
||||
$childCategory = Category::getChildrenByParentId($categoryId);
|
||||
|
||||
$this->data['categoryId'] = $categoryId;
|
||||
$this->data['category'] = $category;
|
||||
$this->data['childCategory'] = $childCategory;
|
||||
$this->data['blocks'] = Block::getByCategoryId($categoryId);
|
||||
return $this->view($category['template_detail']);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,217 @@
|
|||
<?php
|
||||
namespace app\controller\manager;
|
||||
|
||||
use app\model\{Article as MArticle, Category, System, Log};
|
||||
use app\validate\Article as VArticle;
|
||||
use think\exception\ValidateException;
|
||||
|
||||
/**
|
||||
* 内容管理 - 文章管理
|
||||
*/
|
||||
class Article extends Base
|
||||
{
|
||||
//批量修改属性
|
||||
public function attribute()
|
||||
{
|
||||
if($this->request->isPost()){
|
||||
$ids = input('post.id/a');
|
||||
if(empty($ids) || !is_array($ids)) {
|
||||
return $this->json(2, '参数错误,请核对之后再操作!');
|
||||
}
|
||||
$data = [];
|
||||
foreach(['top', 'hot', 'recommend'] as $key){
|
||||
$val = input('post.'.$key, 0);
|
||||
if(in_array($val, [1, 2])){
|
||||
if($val == 1){
|
||||
$data[$key] = 1;
|
||||
}else{
|
||||
$data[$key] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!empty($data)){
|
||||
MArticle::whereIn('id', $ids)->update($data);
|
||||
Log::write('article', 'attribute', '批量修改了文章属性,涉及到的文章ID为:' . implode(',', $ids));
|
||||
}
|
||||
return $this->json();
|
||||
}
|
||||
return $this->json(1, '非法请求!');
|
||||
}
|
||||
//批量删除
|
||||
public function batchDel()
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
$ids = input('post.ids/a');
|
||||
if(empty($ids) || !is_array($ids)) {
|
||||
return $this->json(2, '参数错误,请核对之后再操作!');
|
||||
}
|
||||
$items = MArticle::getListByIds($ids);
|
||||
if(!empty($items)){
|
||||
$delIds = [];
|
||||
foreach($items as $item){
|
||||
$delIds[] = $item['id'];
|
||||
}
|
||||
MArticle::destroy($delIds);
|
||||
Log::write('article', 'betchDel', '批量删除了文章,涉及到的文章ID为:' . implode(',', $delIds));
|
||||
return $this->json();
|
||||
}else{
|
||||
return $this->json(3, '待删除文章列表为空');
|
||||
}
|
||||
}
|
||||
return $this->json(1, '非法请求!');
|
||||
}
|
||||
//删除
|
||||
public function del()
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
$id = input('post.id/d');
|
||||
if(is_numeric($id) && $id > 0) {
|
||||
$item = MArticle::getById($id);
|
||||
if(!empty($item)){
|
||||
MArticle::destroy($id);
|
||||
Log::write('article', 'del', '删除文章,ID:' . $id . ',标题:' . $item['title']);
|
||||
return $this->json();
|
||||
}
|
||||
return $this->json(3,'待删除文章不存在');
|
||||
}
|
||||
return $this->json(2, '参数错误,请核对之后再操作!');
|
||||
}
|
||||
return $this->json(1, '非法请求!');
|
||||
}
|
||||
|
||||
//排序
|
||||
public function sort()
|
||||
{
|
||||
if($this->request->isPost()){
|
||||
$id = input('post.id/d');
|
||||
$sort = input('post.sort');
|
||||
$num = input('post.num/d', 1);
|
||||
if($num <= 0){
|
||||
$num = 1;
|
||||
}
|
||||
if(!in_array($sort, ['up', 'down'], true)){
|
||||
return $this->json(2, '参数错误');
|
||||
}
|
||||
$item = MArticle::getById($id);
|
||||
if(empty($item)){
|
||||
return $this->json(3, '该文章信息不存在');
|
||||
}
|
||||
if($sort == 'up'){
|
||||
$where = "category_id='{$item['category_id']}' and sort > {$item['sort']}";
|
||||
$order = "sort asc";
|
||||
}else{
|
||||
$where = "category_id='{$item['category_id']}' and sort < {$item['sort']}";
|
||||
$order = "sort desc";
|
||||
}
|
||||
$forSortItems = MArticle::getListByWhereAndOrder($where, $order, $num);
|
||||
if(!empty($forSortItems)){
|
||||
$updateData = [];
|
||||
$forSortCount = count($forSortItems);
|
||||
for($i = 0; $i < $forSortCount; $i++){
|
||||
if($i == 0){
|
||||
$updateData[] = [
|
||||
'id' => $forSortItems[$i]['id'],
|
||||
'sort' => $item['sort']
|
||||
];
|
||||
}else{
|
||||
$updateData[] = [
|
||||
'id' => $forSortItems[$i]['id'],
|
||||
'sort' => $forSortItems[$i - 1]['sort']
|
||||
];
|
||||
}
|
||||
}
|
||||
$updateData[] = [
|
||||
'id' => $item['id'],
|
||||
'sort' => $forSortItems[$i - 1]['sort']
|
||||
];
|
||||
if(!empty($updateData)){
|
||||
$model = new MArticle();
|
||||
$model->saveAll($updateData);
|
||||
$sortStr = $sort == 'up' ? '上移' : '下调';
|
||||
Log::write('article', 'sort', "文章排序,ID:{$id} ,标题:{$item['title']},{$sortStr}了{$num}位");
|
||||
return $this->json();
|
||||
}
|
||||
}
|
||||
return $this->json(4, '无须调整排序!');
|
||||
}
|
||||
return $this->json(1, '非法请求!');
|
||||
}
|
||||
|
||||
//编辑
|
||||
public function edit()
|
||||
{
|
||||
if($this->request->isPost()){
|
||||
$item = input('post.item/a');
|
||||
$img = input('post.img');
|
||||
$id = input('post.id/d');
|
||||
$article = MArticle::getById($id);
|
||||
if (empty($article)) {
|
||||
return $this->json(1, '该文章不存在!');
|
||||
}
|
||||
if(!empty($img)){
|
||||
$item['src'] = $img;
|
||||
}
|
||||
try {
|
||||
validate(VArticle::class)->check($item);
|
||||
$auth = session('auth');
|
||||
$item['update_time'] = time();
|
||||
$item['updated'] = $auth['userName'];
|
||||
MArticle::updateById($id, $item);
|
||||
Log::write('article', 'edit', "文章编辑,ID:{$id} ,标题:{$item['title']}");
|
||||
return $this->json();
|
||||
} catch (ValidateException $e) {
|
||||
return $this->json(2, $e->getError());
|
||||
}
|
||||
}else{
|
||||
$id = input('param.id');
|
||||
$article = MArticle::getById($id);
|
||||
$category = Category::getById($article['category_id']);
|
||||
if($category['img_width'] && $category['img_height']){
|
||||
$imgSize = $category['img_width'] . '像素 X ' . $category['img_height'] . '像素';
|
||||
}else{
|
||||
$imgSize = System::getArticleImageSize();
|
||||
}
|
||||
$this->data['item'] = $article;
|
||||
$this->data['category'] = $category;
|
||||
$this->data['imgSize'] = $imgSize;
|
||||
return $this->view();
|
||||
}
|
||||
}
|
||||
|
||||
//添加
|
||||
public function add()
|
||||
{
|
||||
if($this->request->isPost()){
|
||||
$item = input('post.item/a');
|
||||
$img = input('post.img');
|
||||
if(!empty($img)){
|
||||
$item['src'] = $img;
|
||||
}
|
||||
try {
|
||||
validate(VArticle::class)->check($item);
|
||||
$content = $item['content'] ?? '';
|
||||
if(isset($item['content'])){
|
||||
unset($item['content']);
|
||||
}
|
||||
$item['content'] = $content;
|
||||
$article = MArticle::create($item);
|
||||
Log::write('article', 'add', "文章新增,ID:{$article->id} ,标题:{$item['title']}");
|
||||
return $this->json();
|
||||
} catch (ValidateException $e) {
|
||||
return $this->json(2, $e->getError());
|
||||
}
|
||||
}else{
|
||||
$categoryId = input('param.category_id');
|
||||
$category = Category::getById($categoryId);
|
||||
if(count($category) > 0 && $category['img_width'] && $category['img_height']){
|
||||
$imgSize = $category['img_width'] . '像素 X ' . $category['img_height'] . '像素';
|
||||
}else{
|
||||
$imgSize = System::getArticleImageSize();
|
||||
}
|
||||
|
||||
$this->data['category'] = $category;
|
||||
$this->data['imgSize'] = $imgSize;
|
||||
return $this->view();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
<?php
|
||||
namespace app\controller\manager;
|
||||
|
||||
use think\facade\{Db, Config, Env};
|
||||
use app\model\{Log as MLog};
|
||||
use app\service\Tool;
|
||||
|
||||
class Backup extends Base
|
||||
{
|
||||
/**
|
||||
* 因受mysql 配置参数net_buffer_length和max_allowed_packet大小的限制,因此insert语句改为单条插入
|
||||
* show VARIABLES like "%net_buffer_length%" 默认单条sql语句长度16k
|
||||
* show VARIABLES like "%max_allowed_packet%" 默认单个sql文件最大容量
|
||||
* sql 文件注释 -- + 空格之后填写注释内容
|
||||
* 因根目录文件夹权限限制,因此需要手动创建backup/data目录并赋予权限(www用户组)
|
||||
*/
|
||||
public function back()
|
||||
{
|
||||
ini_set('max_execution_time', 0);
|
||||
ini_set("memory_limit",-1);
|
||||
set_time_limit(0);
|
||||
|
||||
$path = Config::get('filesystem.disks.backup.root') . '/';
|
||||
$dataBase = Env::get('database.database');
|
||||
// 不要使用单引号,双引号中的变量可以解析,单引号就是绝对的字符串
|
||||
$eol = "\r\n";
|
||||
$eolB = "\r\n\r\n";
|
||||
$info = '-- ------------------------------'.$eol;
|
||||
$info .= '-- 日期: '.date('Y-m-d H:i:s',time()).$eol;
|
||||
$info .= '-- MySQL --Database - '.$dataBase.$eol;
|
||||
$info .= '-- ------------------------------'.$eol;
|
||||
|
||||
$info .= 'CREATE DATABASE IF NOT EXISTS `'.$dataBase.'` DEFAULT CHARACTER SET "utf8mb4" COLLATE "utf8mb4_general_ci";'.$eolB;
|
||||
$info .= 'USE `'.$dataBase.'`;'.$eol;
|
||||
|
||||
if(is_dir($path)) {
|
||||
if(!is_writable($path)) {
|
||||
chmod($path, 0755);
|
||||
}
|
||||
} else {
|
||||
mkdir($path, 0755, true);
|
||||
}
|
||||
$fileName = $path.$dataBase.'_'.date('YmdHis',time()).'.sql';
|
||||
if(file_exists($fileName)) {
|
||||
@unlink($fileName);
|
||||
}
|
||||
// 以追加的方式写入
|
||||
file_put_contents($fileName, $info, FILE_APPEND);
|
||||
$tables = Db::query('show tables');
|
||||
foreach ($tables as $table) {
|
||||
// 表结构
|
||||
$tableName = $table['Tables_in_'.$dataBase];
|
||||
$sqlTable = 'SHOW CREATE TABLE '.$tableName;
|
||||
$res = Db::query($sqlTable);
|
||||
$infoTable = '-- ------------------------------'.$eol;
|
||||
$infoTable .= '-- Table structure for `'.$tableName.'`'.$eol;
|
||||
$infoTable .= '-- ------------------------------'.$eolB;
|
||||
$infoTable .= 'DROP TABLE IF EXISTS `'.$tableName.'`;'.$eolB;
|
||||
$infoTable .= $res[0]['Create Table'].';'.$eolB;
|
||||
// 表数据
|
||||
$infoTable .= '-- ------------------------------'.$eol;
|
||||
$infoTable .= '-- Data for the table `'.$tableName.'`'.$eol;
|
||||
$infoTable .= '-- ------------------------------'.$eolB;
|
||||
file_put_contents($fileName, $infoTable, FILE_APPEND);
|
||||
|
||||
$sqlData = 'select * from '.$tableName;
|
||||
$data = Db::query($sqlData);
|
||||
$count = count($data);
|
||||
if ($count > 0) {
|
||||
$dataStr = 'INSERT INTO `'.$tableName.'` VALUE ';
|
||||
foreach ($data as $k => $item) {
|
||||
$valStr = '(';
|
||||
foreach ($item as $val) {
|
||||
// 字符串转义
|
||||
$val = addslashes($val);
|
||||
$valStr .= '"'.$val.'", ';
|
||||
}
|
||||
// 去除最后的逗号和空格
|
||||
$valStr = substr($valStr,0,strlen($valStr)-2);
|
||||
// 限制单条sql语句在16K以内(可根据mysql配置条件进行调整)
|
||||
$sqlLength = strlen($dataStr) + strlen($valStr);
|
||||
if($sqlLength >= (1024 * 16 - 10)) {
|
||||
$dataStr .= $valStr.');'.$eol;
|
||||
file_put_contents($fileName, $dataStr, FILE_APPEND);
|
||||
// 提前分段写入后需重置数据语句
|
||||
if ($k <= ($count-1)) {
|
||||
$dataStr = 'INSERT INTO `'.$tableName.'` VALUE ';
|
||||
} else {
|
||||
$dataStr = '';
|
||||
}
|
||||
} else {
|
||||
if ($k < ($count-1)) {
|
||||
$dataStr .= $valStr.'),'.$eol;
|
||||
} else {
|
||||
$dataStr .= $valStr.');'.$eolB;
|
||||
}
|
||||
}
|
||||
}
|
||||
file_put_contents($fileName, $dataStr, FILE_APPEND);
|
||||
}
|
||||
}
|
||||
clearstatcache();
|
||||
|
||||
$backups = explode('/', $fileName);
|
||||
$backupName = $backups[count($backups)-1];
|
||||
$src = Config::get('filesystem.disks.backup.url') . '/';
|
||||
return $this->json(0, '备份成功:' . $src . $backupName);
|
||||
}
|
||||
|
||||
|
||||
public function index()
|
||||
{
|
||||
$path = Config::get('filesystem.disks.backup.root') . '/';
|
||||
$src = Config::get('filesystem.disks.backup.url') . '/';
|
||||
$items = [];
|
||||
if(is_dir($path)) {
|
||||
if(!is_readable($path)) {
|
||||
chmod($path,0755);
|
||||
}
|
||||
$files = scandir($path);
|
||||
foreach ($files as $file) {
|
||||
if($file != '.' && $file != '..') {
|
||||
$ext = substr($file, -4);
|
||||
if ($ext == '.sql') {
|
||||
$creatTime = substr($file, -18, 14);
|
||||
$items[] = [
|
||||
'file' => $file,
|
||||
'path' => $src . $file,
|
||||
'time' =>is_numeric($creatTime) ? date('Y-m-d H:i:s', strtotime($creatTime)) : '',
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
clearstatcache();
|
||||
|
||||
$this->data['items'] = $items;
|
||||
$this->data['backupPath'] = str_replace('\\','/', $path);
|
||||
return $this->view();
|
||||
}
|
||||
|
||||
public function del()
|
||||
{
|
||||
if (request()->isPost()) {
|
||||
$filePath = input('post.id');
|
||||
Tool::delFile($filePath);
|
||||
MLog::write('backup', 'del', '删除了备份数据文件,文件路径为:' . $filePath);
|
||||
return $this->json();
|
||||
} else {
|
||||
return $this->json(1, '非法请求');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
namespace app\controller\manager;
|
||||
|
||||
use app\controller\BaseController;
|
||||
|
||||
/**
|
||||
* 控制器基础类
|
||||
*/
|
||||
class Base extends BaseController
|
||||
{
|
||||
protected $data = [];
|
||||
protected function initialize()
|
||||
{
|
||||
$this->middleware = [
|
||||
'csrf',
|
||||
'auth' => [
|
||||
'except' => ['manager.login/index']
|
||||
]
|
||||
];
|
||||
$this->setCsrfToken();
|
||||
$auth = session('auth');
|
||||
$this->data['member'] = $auth;
|
||||
$this->data['groupId'] = $auth['groupId'] ?? 0;
|
||||
}
|
||||
|
||||
//变量赋值到模板
|
||||
protected function view()
|
||||
{
|
||||
return view()->assign($this->data);
|
||||
}
|
||||
|
||||
protected function error($msg = '', string $url = null, $data = '', int $wait = 3)
|
||||
{
|
||||
if (is_null($url)) {
|
||||
$url = $this->request->isAjax() ? '' : 'javascript:history.back(-1);';
|
||||
} elseif ($url) {
|
||||
$url = (strpos($url, '://') || 0 === strpos($url, '/')) ? $url : $this->app->route->buildUrl($url);
|
||||
}
|
||||
$result = [
|
||||
'code' => 0,
|
||||
'msg' => $msg,
|
||||
'data' => $data,
|
||||
'url' => $url,
|
||||
'wait' => $wait,
|
||||
];
|
||||
|
||||
return $this->redirect(url('manager.error/jump', $result));
|
||||
}
|
||||
|
||||
protected function setCsrfToken()
|
||||
{
|
||||
$this->data['_token'] = session('_token') ?? '';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,235 @@
|
|||
<?php
|
||||
namespace app\controller\manager;
|
||||
|
||||
use app\model\{Category as MCategory, Model as MCModel, Log};
|
||||
use app\validate\Category as VCategory;
|
||||
use think\exception\ValidateException;
|
||||
use think\facade\Cache;
|
||||
|
||||
class Category extends Base
|
||||
{
|
||||
//栏目列表
|
||||
public function index()
|
||||
{
|
||||
$auth = session('auth');
|
||||
$authCates = array_filter(explode(',', $auth['cates']));
|
||||
if ($auth['groupId'] == 1) {
|
||||
$cates = MCategory::getList();
|
||||
} else {
|
||||
$cates = MCategory::getList(true, $authCates);
|
||||
}
|
||||
$items = MCategory::getCates($cates);
|
||||
|
||||
$powerAdd = false;
|
||||
$ruleNames = Cache::get('rule_names_'.$auth['groupId']);
|
||||
if (is_array($ruleNames) && in_array('category/add', $ruleNames, true)) {
|
||||
$powerAdd = true;
|
||||
}
|
||||
|
||||
$this->data['items'] = $items;
|
||||
$this->data['power_add'] = $powerAdd;
|
||||
return $this->view();
|
||||
}
|
||||
|
||||
//批量删除
|
||||
public function batchDel()
|
||||
{
|
||||
if($this->request->isPost()){
|
||||
$ids = input('post.ids/a');
|
||||
if(is_array($ids)) {
|
||||
$idsArr = $ids;
|
||||
} else {
|
||||
$idsArr = array_filter(explode(',', $ids));
|
||||
}
|
||||
if(count($idsArr) == 0) {
|
||||
return $this->json(1, '无效请求,参数错误!');
|
||||
}
|
||||
if (MCategory::hasChildren($idsArr)) {
|
||||
return $this->json(2, '需删除的栏目下存在下级栏目,不可删除。请检查核实后再操作!');
|
||||
}
|
||||
$categories = MCategory::getListByIds($idsArr);
|
||||
if(!empty($categories)){
|
||||
$hasIds = [];
|
||||
foreach($categories as $cate){
|
||||
$hasIds[] = $cate['id'];
|
||||
}
|
||||
MCategory::destroy($hasIds);
|
||||
Log::write('category', 'betchDel', '批量删除了栏目,涉及到的ID为:' . implode(',', $hasIds));
|
||||
return $this->json();
|
||||
}
|
||||
return $this->json(3, '删除失败!栏目不存在,请刷新页面后再试!');
|
||||
}
|
||||
return $this->json(1, '非法请求!');
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
public function del()
|
||||
{
|
||||
if($this->request->isPost()){
|
||||
$id = input('post.id/d');
|
||||
if(is_numeric($id) && $id > 0) {
|
||||
if(MCategory::hasChildren($id)){
|
||||
return $this->json(4, '此栏目有下级栏目,不可删除。请检查核实后再操作!');
|
||||
}
|
||||
$cate = MCategory::getById($id);
|
||||
if(!empty($cate)){
|
||||
MCategory::destroy($id);
|
||||
Log::write('category', 'del', '删除栏目,ID:' . $id . ',标题:' . $cate['title']);
|
||||
return $this->json();
|
||||
}
|
||||
return $this->json(3, '删除失败!栏目不存在,请刷新页面后再试!');
|
||||
} else {
|
||||
return $this->json(2, '无效请求,参数错误!');
|
||||
}
|
||||
}
|
||||
return $this->json(1, '非法请求!');
|
||||
}
|
||||
|
||||
//排序
|
||||
public function sort()
|
||||
{
|
||||
if($this->request->isPost()){
|
||||
$id = input('post.id/d');
|
||||
$sort = input('post.sort');
|
||||
$num = input('post.num/d', 1);
|
||||
if($num <= 0){
|
||||
$num = 1;
|
||||
}
|
||||
if(!in_array($sort, ['up', 'down'], true)){
|
||||
return $this->json(2, '参数错误');
|
||||
}
|
||||
$item = MCategory::getById($id);
|
||||
if(empty($item)) {
|
||||
return $this->json(3, '无此栏目!');
|
||||
}
|
||||
if($sort == 'up'){
|
||||
$where = "parent_id='{$item['parent_id']}' and sort < {$item['sort']}";
|
||||
$order = "sort desc";
|
||||
}else{
|
||||
$where = "parent_id='{$item['parent_id']}' and sort > {$item['sort']}";
|
||||
$order = "sort asc";
|
||||
}
|
||||
$forSortItems = MCategory::getListByWhereAndOrder($where, $order, $num);
|
||||
if(!empty($forSortItems)){
|
||||
$updateData = [];
|
||||
$forSortCount = count($forSortItems);
|
||||
for($i = 0; $i < $forSortCount; $i++){
|
||||
if($i == 0){
|
||||
$updateData[] = [
|
||||
'id' => $forSortItems[$i]['id'],
|
||||
'sort' => $item['sort']
|
||||
];
|
||||
}else{
|
||||
$updateData[] = [
|
||||
'id' => $forSortItems[$i]['id'],
|
||||
'sort' => $forSortItems[$i - 1]['sort']
|
||||
];
|
||||
}
|
||||
}
|
||||
$updateData[] = [
|
||||
'id' => $item['id'],
|
||||
'sort' => $forSortItems[$i - 1]['sort']
|
||||
];
|
||||
if(!empty($updateData)){
|
||||
$model = new MCategory();
|
||||
$model->saveAll($updateData);
|
||||
$sortStr = $sort == 'up' ? '上移' : '下调';
|
||||
Log::write('category', 'sort', "栏目排序,ID:{$id} ,标题:{$item['title']},{$sortStr}了{$num}位");
|
||||
return $this->json();
|
||||
}
|
||||
}
|
||||
return $this->json(4, '无须调整排序!');
|
||||
}
|
||||
return $this->json(1, '非法请求!');
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新栏目数据
|
||||
*/
|
||||
public function edit()
|
||||
{
|
||||
if($this->request->isPost()){
|
||||
$item = input('post.item/a');
|
||||
$id = input('post.id');
|
||||
$img = input('post.img');
|
||||
if (count($item) > 0 && (is_numeric($id) === true && $id > 0)) {
|
||||
try {
|
||||
validate(VCategory::class)->check($item);
|
||||
if(!empty($img)){
|
||||
$item['src'] = $img;
|
||||
}
|
||||
MCategory::updateById($id, $item);
|
||||
Log::write('category', 'edit', "栏目编辑,ID:{$id} ,标题:{$item['title']}");
|
||||
return $this->json();
|
||||
} catch (ValidateException $e) {
|
||||
return $this->json(2, $e->getError());
|
||||
}
|
||||
}
|
||||
return $this->json(1,'传入参数错误,请核对之后再操作!');
|
||||
} else {
|
||||
$id = input('param.id');
|
||||
if (is_numeric($id) === true && $id > 0) {
|
||||
$item = MCategory::getById($id);
|
||||
if(empty($item)){
|
||||
return $this->json(1,'参数错误,无此栏目!');
|
||||
}
|
||||
|
||||
$this->data['item'] = $item;
|
||||
$this->data['parent'] = MCategory::getById($item['parent_id']);
|
||||
$this->data['models'] = MCModel::getList();
|
||||
return $this->view();
|
||||
}
|
||||
return $this->json(1,'参数错误,请核对之后再操作!');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 栏目添加
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
if($this->request->isPost()){
|
||||
$item = input('post.item/a');
|
||||
$img = input('post.img');
|
||||
if (is_array($item) === true && count($item) > 0) {
|
||||
if (!empty($img)) {
|
||||
$item['src'] = $img;
|
||||
}
|
||||
try {
|
||||
validate(VCategory::class)->check($item);
|
||||
if(!isset($item['number']) || $item['number'] <= 0){
|
||||
$item['number'] = 20;
|
||||
}
|
||||
if($item['parent_id'] == 0){
|
||||
$item['path'] = ',0,';
|
||||
}else{
|
||||
$parent = MCategory::getById($item['parent_id']);
|
||||
if(empty($parent)){
|
||||
$item['path'] = ',0,';
|
||||
}else{
|
||||
$item['path'] = $parent['path'] . $parent['id'] . ',';
|
||||
}
|
||||
}
|
||||
$category = MCategory::create($item);
|
||||
Log::write('category', 'add', "栏目新增,ID:{$category->id} ,标题:{$item['title']}");
|
||||
return $this->json();
|
||||
} catch (ValidateException $e) {
|
||||
return $this->json(2,$e->getError());
|
||||
}
|
||||
}
|
||||
return $this->json(1, '传入值错误,请核对之后再操作!');
|
||||
} else {
|
||||
$parentId = input('param.id')??input('param.parent_id');
|
||||
if(empty($parentId)){
|
||||
$parent = [];
|
||||
}else{
|
||||
$parent = MCategory::getById($parentId);
|
||||
}
|
||||
$this->data['parent'] = $parent;
|
||||
$this->data['models'] = MCModel::getList();
|
||||
return $this->view();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
<?php
|
||||
namespace app\controller\manager;
|
||||
|
||||
use app\model\{Category, Article, Block, Log, History};
|
||||
|
||||
class Content extends Base
|
||||
{
|
||||
//文章
|
||||
public function article()
|
||||
{
|
||||
$categoryId = input('param.category_id');
|
||||
$keyword = input('param.keyword');
|
||||
$param = input('param.param/a', []);
|
||||
$category = Category::getById($categoryId);
|
||||
if(empty($category)){
|
||||
return $this->error('无此栏目');
|
||||
}
|
||||
$list = Article::getList($categoryId, 20, $keyword, $param);
|
||||
$this->data['list'] = $list;
|
||||
$this->data['category'] = $category;
|
||||
$this->data['keyword'] = $keyword;
|
||||
$this->data['param'] = $param;
|
||||
return $this->view();
|
||||
}
|
||||
|
||||
//做页面跳转
|
||||
public function index()
|
||||
{
|
||||
$items = Category::getList();
|
||||
if(!empty($items)){
|
||||
$items = Category::getCates($items);
|
||||
}
|
||||
|
||||
if(!empty($items)){
|
||||
$first = array_shift($items);
|
||||
if(isset($first['children'])){
|
||||
$childrenFirst = array_shift($first['children']);
|
||||
$url = url('manager.content/'.$childrenFirst['manager'],['category_id' => $childrenFirst['id']]);
|
||||
}else{
|
||||
$url = url('manager.content/'.$first['manager'],['category_id' => $first['id']]);
|
||||
}
|
||||
if(!empty($url)){
|
||||
return $this->redirect($url);
|
||||
}
|
||||
}else{
|
||||
return $this->redirect(url('manager.category/add'));
|
||||
}
|
||||
}
|
||||
//单页
|
||||
public function page()
|
||||
{
|
||||
if($this->request->isAjax()){
|
||||
$blocks = input('post.block/a'); //所有文本信息
|
||||
$texts = input('post.text/a'); //所有富文本信息
|
||||
$codes = input('post.code/a'); //所有代码信息
|
||||
$categoryId = input('post.category_id/d');
|
||||
$category = Category::getById($categoryId);
|
||||
unset($_POST['block']);
|
||||
unset($_POST['text']);
|
||||
unset($_POST['file']);
|
||||
unset($_POST['code']);
|
||||
$imgs = []; //图片信息
|
||||
$videos = []; //视频信息
|
||||
$groups = []; //组图信息
|
||||
$groupIds = input('post.groupIds/a', []);
|
||||
foreach($_POST as $key => $val){
|
||||
if(strpos($key, '_') !== FALSE){
|
||||
$keys = explode('_',$key);
|
||||
if($keys[1] == 'img'){ //图片
|
||||
$imgs[$keys[2]] = $val;
|
||||
}elseif($keys[1] == 'video'){ //视频
|
||||
$videos[$keys[2]][$keys[0]] = $val;
|
||||
}elseif($keys[1] == 'group'){ //组图
|
||||
$groups[$keys[2]] = $val;
|
||||
}
|
||||
}
|
||||
}
|
||||
$data = [];
|
||||
if(!empty($blocks)){
|
||||
foreach($blocks as $key => $block){
|
||||
$data[] = [
|
||||
'id' => $key,
|
||||
'value' => $block
|
||||
];
|
||||
}
|
||||
}
|
||||
if(!empty($texts)){
|
||||
foreach($texts as $key => $text){
|
||||
$data[] = [
|
||||
'id' => $key,
|
||||
'value' => $text
|
||||
];
|
||||
}
|
||||
}
|
||||
if(!empty($codes)){
|
||||
foreach($codes as $key => $code){
|
||||
$data[] = [
|
||||
'id' => $key,
|
||||
'value' => $code
|
||||
];
|
||||
}
|
||||
}
|
||||
if(!empty($imgs)){
|
||||
foreach($imgs as $key => $img){
|
||||
$data[] = [
|
||||
'id' => $key,
|
||||
'value' => $img
|
||||
];
|
||||
}
|
||||
}
|
||||
if(!empty($videos)){
|
||||
foreach($videos as $key => $video){
|
||||
$data[] = [
|
||||
'id' => $key,
|
||||
'img' => $video['img'],
|
||||
'value' => $video['video']
|
||||
];
|
||||
}
|
||||
}
|
||||
if(!empty($groupIds)){
|
||||
foreach($groupIds as $key => $groupId){
|
||||
$group = $groups[$groupId] ?? [];
|
||||
$data[] = [
|
||||
'id' => $groupId,
|
||||
'value' => json_encode($group)
|
||||
];
|
||||
}
|
||||
}
|
||||
$block = new Block;
|
||||
$block->saveAll($data);
|
||||
Log::write('content', 'page', "单页编辑,栏目ID:{$category['id']} ,标题:{$category['title']}");
|
||||
return $this->json();
|
||||
}else{
|
||||
$categoryId = input('param.category_id');
|
||||
$children = Category::getChildrenByParentId($categoryId);
|
||||
if(empty($children)){
|
||||
$category = Category::getById($categoryId);
|
||||
}else{
|
||||
$child = Category::getChildrenByParentId($children[0]['id']);
|
||||
if(empty($child)){
|
||||
$category = $children[0];
|
||||
}else{
|
||||
$category = $child[0];
|
||||
}
|
||||
}
|
||||
if(empty($category)){
|
||||
return $this->redirect(url('manager.content/index'));
|
||||
}
|
||||
$blocks = Block::getByCategoryId($category['id']);
|
||||
$this->data['categoryId'] = $categoryId;
|
||||
$this->data['category'] = $category;
|
||||
$this->data['blocks'] = $blocks;
|
||||
$this->data['groupId'] = session('auth.groupId');
|
||||
$this->data['types'] = Block::getTypes();
|
||||
return $this->view();
|
||||
}
|
||||
}
|
||||
|
||||
// 发展历程
|
||||
public function history()
|
||||
{
|
||||
$categoryId = input('param.category_id/d', 0);
|
||||
$category = Category::getById($categoryId);
|
||||
if(empty($category)){
|
||||
return $this->redirect(url('manager.content/index'));
|
||||
}
|
||||
|
||||
$this->data['categoryId'] = $categoryId;
|
||||
$this->data['category'] = $category;
|
||||
$this->data['items'] = History::getPaginateList($categoryId, 20, false);
|
||||
return $this->view();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
namespace app\controller\manager;
|
||||
|
||||
class Error
|
||||
{
|
||||
public function jump()
|
||||
{
|
||||
$param = request()->param();
|
||||
return view()->assign($param);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
<?php
|
||||
namespace app\controller\manager;
|
||||
|
||||
use app\model\{File as MFile, Article, Block, Category, Link, Slide, Log};
|
||||
use app\service\Tool;
|
||||
|
||||
class File extends Base
|
||||
{
|
||||
//删除磁盘上的文件
|
||||
public function delPath()
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
$paths = input('post.paths/a');
|
||||
if(!empty($paths)){
|
||||
foreach($paths as $path){
|
||||
Tool::delFile($path);
|
||||
}
|
||||
Log::write('file', 'delPath', '批量删除了磁盘文件,涉及到的文件路径为:' . implode(',', $paths));
|
||||
return $this->json();
|
||||
}
|
||||
return $this->json(2, '待删除文件列表为空');
|
||||
}
|
||||
return $this->json(1, '非法请求!');
|
||||
}
|
||||
//删除文件记录
|
||||
public function del()
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
$ids = input('post.ids/a');
|
||||
if(empty($ids) || !is_array($ids)) {
|
||||
return $this->json(2, '参数错误,请核对之后再操作!');
|
||||
}
|
||||
$items = MFile::getListByIds($ids);
|
||||
if(!empty($items)){
|
||||
$delIds = [];
|
||||
foreach($items as $item){
|
||||
$delIds[] = $item['id'];
|
||||
if($item['type'] == MFile::IMG){
|
||||
Tool::delFile($item['src'], 1);
|
||||
}else{
|
||||
Tool::delFile($item['src']);
|
||||
}
|
||||
}
|
||||
MFile::destroy($delIds);
|
||||
Log::write('file', 'del', '批量删除了文件,涉及到的文件ID为:' . implode(',', $delIds));
|
||||
return $this->json();
|
||||
}else{
|
||||
return $this->json(3, '待删除文件列表为空');
|
||||
}
|
||||
}
|
||||
return $this->json(1, '非法请求!');
|
||||
}
|
||||
/**
|
||||
* 未使用文件列表,
|
||||
* 1. 遍历数据库中使用的图片视频及文件路径
|
||||
* 2. 遍历上传目录中的文件
|
||||
* 3. 数据对比,找出存在目录中的文件&不在数据库中的文件
|
||||
* 4. 页面上显示查找出来的文件
|
||||
*/
|
||||
public function unuse()
|
||||
{
|
||||
$filesInUse = $this->getAllFilesInUse(); //数据库中在使用的文件
|
||||
$rootPath = app()->getRootPath();
|
||||
$uploadPath = $rootPath . 'storage';
|
||||
$uploadedFiles = getAllFilesByPath($uploadPath, $rootPath); //磁盘上上传的文件
|
||||
$files = MFile::getAll();
|
||||
$dbUploadedFiles = []; //数据库中上传的文件
|
||||
foreach($files as $file){
|
||||
$src = trim($file['src']);
|
||||
if(!empty($src)){
|
||||
$key = getKeyByPath($src);
|
||||
$dbUploadedFiles[$key] = $file;
|
||||
}
|
||||
}
|
||||
|
||||
$uploadedNotInUseFiles = array_diff_key($uploadedFiles, $filesInUse); //磁盘上上传未使用的文件
|
||||
$dbUploadedNotInUseFiles = array_diff_key($dbUploadedFiles, $filesInUse); //数据库中上传未使用的文件
|
||||
$bothNotInUseFiles = array_intersect_key($uploadedNotInUseFiles, $dbUploadedNotInUseFiles); //磁盘和数据库中,两边都未使用
|
||||
$this->data['uploadedNotInUseFiles'] = $uploadedNotInUseFiles;
|
||||
$this->data['dbUploadedNotInUseFiles'] = $dbUploadedNotInUseFiles;
|
||||
$this->data['bothNotInUseFilesKey'] = array_keys($bothNotInUseFiles);
|
||||
return $this->view();
|
||||
}
|
||||
|
||||
//获取所有在使用的文件
|
||||
private function getAllFilesInUse()
|
||||
{
|
||||
$files = [];
|
||||
$blockFiles = Block::getFilesInUse();
|
||||
if(!empty($blockFiles)){
|
||||
$files = array_merge($files, $blockFiles);
|
||||
}
|
||||
$slideFiles = Slide::getFilesInUse();
|
||||
if(!empty($slideFiles)){
|
||||
$files = array_merge($files, $slideFiles);
|
||||
}
|
||||
$linkFiles = Link::getFilesInUse();
|
||||
if(!empty($linkFiles)){
|
||||
$files = array_merge($files, $linkFiles);
|
||||
}
|
||||
$categoryFiles = Category::getFilesInUse();
|
||||
if(!empty($categoryFiles)){
|
||||
$files = array_merge($files, $categoryFiles);
|
||||
}
|
||||
$articleFiles = Article::getFilesInUse();
|
||||
if(!empty($articleFiles)){
|
||||
$files = array_merge($files, $articleFiles);
|
||||
}
|
||||
return $files;
|
||||
}
|
||||
|
||||
|
||||
//ajax获取文件列表
|
||||
public function ajaxList()
|
||||
{
|
||||
if($this->request->isAjax()){
|
||||
$page = input('param.page/d', 1);
|
||||
$size = input('param.size/d', 20);
|
||||
if(!is_integer($page) || $page < 1){
|
||||
$page = 1;
|
||||
}
|
||||
if (!is_integer($size) || $size < 1) {
|
||||
$size = 20;
|
||||
}
|
||||
$type = input('param.type', '');
|
||||
if(!in_array($type, array_keys(MFile::getTypes()))){
|
||||
$type = '';
|
||||
}
|
||||
$items = MFile::getList($type, $page, $size);
|
||||
return $this->json(0, 'ok', $items);
|
||||
}
|
||||
return $this->json(1, '无此操作');
|
||||
}
|
||||
//列表
|
||||
public function index()
|
||||
{
|
||||
$items = MFile::getListPage();
|
||||
$this->data['items'] = $items;
|
||||
$this->data['types'] = MFile::getTypes();
|
||||
return $this->view();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
<?php
|
||||
namespace app\controller\manager;
|
||||
|
||||
use app\model\{AuthGroup, AuthRule, Log};
|
||||
use app\validate\AuthGroup as VAuthGroup;
|
||||
use think\exception\ValidateException;
|
||||
|
||||
/**
|
||||
* 角色管理控制器
|
||||
*/
|
||||
class Group extends Base
|
||||
{
|
||||
/**
|
||||
* 角色、分组删除
|
||||
*/
|
||||
public function del()
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
$id = input('post.id/d');
|
||||
if (is_numeric($id) === true && $id > 0) {
|
||||
$item = AuthGroup::getById($id);
|
||||
if(!empty($item)){
|
||||
AuthGroup::destroy($id);
|
||||
Log::write('group', 'del', '删除角色,ID:' . $id . ',名称:' . $item['title']);
|
||||
return $this->json();
|
||||
}
|
||||
}
|
||||
return $this->json(2, '传入参数错误,请核对之后再操作!');
|
||||
}
|
||||
return $this->json(1, '非法请求!');
|
||||
}
|
||||
/**
|
||||
* 角色、分组权限分配
|
||||
*/
|
||||
public function rule()
|
||||
{
|
||||
if($this->request->isPost()){
|
||||
$rules = input('post.rules/a');
|
||||
$groupId = input('post.group_id/d');
|
||||
if (is_array($rules) && (is_numeric($groupId) === true && $groupId > 0)) {
|
||||
$group = AuthGroup::getById($groupId);
|
||||
if(empty($group)){
|
||||
return $this->json(2, '无此角色信息,请核对之后再操作!');
|
||||
}
|
||||
AuthGroup::updateRules($groupId, $rules);
|
||||
// 重置该角色对应的权限缓存
|
||||
AuthGroup::resetGroupRulesCache($groupId);
|
||||
Log::write('group', 'rule', '角色分配权限,ID:' . $groupId . ',名称:' . $group['title']);
|
||||
return $this->json();
|
||||
}else{
|
||||
return $this->json(3, '传入参数错误,请核对之后再操作!');
|
||||
}
|
||||
} else {
|
||||
$groupId = input('param.group_id/d');
|
||||
$group = AuthGroup::getById($groupId);
|
||||
if(!empty($group)){
|
||||
$rules = AuthRule::getListTree();
|
||||
$this->data['group_id'] = $groupId;
|
||||
$this->data['group'] = $group;
|
||||
$this->data['rules'] = $rules;
|
||||
return $this->view();
|
||||
}else{
|
||||
return $this->json(1, '无此角色信息,请核对之后再操作!');
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 角色、分组添加
|
||||
* @param int $status 1:正常;0:禁止
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
if($this->request->isPost()){
|
||||
$title = trim(input('post.title'));
|
||||
$status = input('post.status/d');
|
||||
if (!empty($title) && (is_numeric($status) === true) && ($status == 1 || $status == 0)) {
|
||||
$item = [
|
||||
'title' => $title,
|
||||
'status' => $status
|
||||
];
|
||||
try {
|
||||
validate(VAuthGroup::class)->check($item);
|
||||
$group = AuthGroup::create($item);
|
||||
Log::write('group', 'add', "角色新增,ID:{$group->id} ,标题:{$group->title}");
|
||||
return $this->json();
|
||||
} catch (ValidateException $e) {
|
||||
return $this->json(2, $e->getError());
|
||||
}
|
||||
}
|
||||
return $this->json(1, '传入参数错误,请核对之后再操作!');
|
||||
}else{
|
||||
return $this->view();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 角色、分组编辑
|
||||
*/
|
||||
public function edit()
|
||||
{
|
||||
if($this->request->isPost()){
|
||||
$title = trim(input('post.title'));
|
||||
$status = input('post.status/d');
|
||||
$id = input('post.id/d');
|
||||
if (!empty($title) && ($status == 1 || $status == 0) && (is_numeric($id) === true && $id > 0)) {
|
||||
$item = [
|
||||
'title' => $title,
|
||||
'status' => $status
|
||||
];
|
||||
try {
|
||||
validate(VAuthGroup::class)->check($item);
|
||||
AuthGroup::updateById($id, $item);
|
||||
Log::write('group', 'edit', "角色编辑,ID:{$id} ,标题:{$item['title']}");
|
||||
return $this->json();
|
||||
} catch (ValidateException $e) {
|
||||
return $this->json(2, $e->getError());
|
||||
}
|
||||
}
|
||||
return $this->json(1, '传入参数错误,请核对之后再操作!');
|
||||
}else{
|
||||
$id = input('param.id/d');
|
||||
if (is_numeric($id) === true && $id > 0) {
|
||||
$item = AuthGroup::getById($id);
|
||||
$this->data['item'] = $item;
|
||||
return $this->view();
|
||||
}
|
||||
return $this->json(1, '传入参数错误,请核对之后再操作!');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 所有角色分组信息
|
||||
* @return void
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$list = AuthGroup::select()->toArray();
|
||||
$this->data['list'] = $list;
|
||||
return $this->view();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,345 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace app\controller\manager;
|
||||
|
||||
use app\model\{HistoryInfo as MHistoryInfo, History as MHistory, Category as MCategory, Log as MLog, System};
|
||||
use think\exception\ValidateException;
|
||||
use app\validate\{History as VHistory, HistoryInfo as VHistoryInfo};
|
||||
use think\facade\Db;
|
||||
|
||||
/**
|
||||
* 发展历程
|
||||
* Class History
|
||||
* @package app\controller\manager
|
||||
*/
|
||||
class History extends Base
|
||||
{
|
||||
public function add()
|
||||
{
|
||||
if(request()->isPost()) {
|
||||
$params = input('post.item/a', []);
|
||||
$params = arrayHtmlFilter($params);
|
||||
try {
|
||||
validate(VHistory::class)->check($params);
|
||||
$data = [
|
||||
'title' => $params['title'],
|
||||
'visible' => $params['visible'],
|
||||
'category_id' => $params['category_id'],
|
||||
];
|
||||
$newItem = MHistory::create($data);
|
||||
MLog::write('history', 'add', '新增发展历程,ID:'.$newItem->id);
|
||||
} catch (ValidateException $e) {
|
||||
return $this->json(1, $e->getError());
|
||||
}
|
||||
return $this->json();
|
||||
} else {
|
||||
$categoryId = input('param.category_id/d', 0);
|
||||
$category = MCategory::getById($categoryId);
|
||||
$this->data['category'] = $category;
|
||||
return $this->view();
|
||||
}
|
||||
}
|
||||
|
||||
public function edit()
|
||||
{
|
||||
$id = input('param.id/d', 0);
|
||||
$item = MHistory::getById($id);
|
||||
if(count($item) == 0) {
|
||||
return $this->json(1, '该历程信息不存在');
|
||||
}
|
||||
if(request()->isPost()) {
|
||||
$params = input('post.item/a', []);
|
||||
$params = arrayHtmlFilter($params);
|
||||
try {
|
||||
validate(VHistory::class)->check($params);
|
||||
$data = [
|
||||
'title' => $params['title'],
|
||||
'visible' => $params['visible'],
|
||||
];
|
||||
MHistory::updateById($id, $data);
|
||||
MLog::write('history', 'edit', '修改发展历程,ID:'.$id);
|
||||
} catch (ValidateException $e) {
|
||||
return $this->json(2, $e->getError());
|
||||
}
|
||||
return $this->json();
|
||||
} else {
|
||||
$this->data['item'] = $item;
|
||||
return $this->view();
|
||||
}
|
||||
}
|
||||
|
||||
public function sort()
|
||||
{
|
||||
if(request()->isPost()) {
|
||||
$id = input('post.id/d');
|
||||
$sort = input('post.sort');
|
||||
$num = input('post.num/d', 1);
|
||||
if($num <= 0){
|
||||
$num = 1;
|
||||
}
|
||||
if(!in_array($sort, ['up', 'down'], true)){
|
||||
return $this->json(2, '参数错误');
|
||||
}
|
||||
$item = MHistory::getById($id);
|
||||
if(empty($item)){
|
||||
return $this->json(3, '该历程信息不存在');
|
||||
}
|
||||
if($sort == 'up'){
|
||||
$where = "category_id='{$item['category_id']}' and sort < {$item['sort']}";
|
||||
$order = "sort desc";
|
||||
}else{
|
||||
$where = "category_id='{$item['category_id']}' and sort > {$item['sort']}";
|
||||
$order = "sort asc";
|
||||
}
|
||||
$forSortItems = MHistory::getListByWhereAndOrder($where, $order, $num);
|
||||
if(!empty($forSortItems)){
|
||||
$updateData = [];
|
||||
$forSortCount = count($forSortItems);
|
||||
for($i = 0; $i < $forSortCount; $i++){
|
||||
if($i == 0){
|
||||
$updateData[] = [
|
||||
'id' => $forSortItems[$i]['id'],
|
||||
'sort' => $item['sort']
|
||||
];
|
||||
}else{
|
||||
$updateData[] = [
|
||||
'id' => $forSortItems[$i]['id'],
|
||||
'sort' => $forSortItems[$i - 1]['sort']
|
||||
];
|
||||
}
|
||||
}
|
||||
$updateData[] = [
|
||||
'id' => $item['id'],
|
||||
'sort' => $forSortItems[$i - 1]['sort']
|
||||
];
|
||||
if(!empty($updateData)){
|
||||
$model = new MHistory();
|
||||
$model->saveAll($updateData);
|
||||
$sortStr = $sort == 'up' ? '上移' : '下调';
|
||||
MLog::write('history', 'sort', "发展历程排序,ID:{$id} ,{$sortStr}了{$num}位");
|
||||
return $this->json();
|
||||
}
|
||||
}
|
||||
return $this->json(4, '无须调整排序!');
|
||||
}
|
||||
return $this->json(1, '无此操作');
|
||||
}
|
||||
|
||||
// 删除历程和历程相关的事例
|
||||
public function del()
|
||||
{
|
||||
if(request()->isPost()) {
|
||||
$historyId = input('param.id/d', 0);
|
||||
$item = MHistory::getById($historyId);
|
||||
if(count($item) == 0) {
|
||||
return $this->json(2, '该历程信息不存在');
|
||||
}
|
||||
Db::startTrans();
|
||||
try {
|
||||
MHistory::destroy($historyId);
|
||||
$hasInfo = MHistoryInfo::countByHistoryId($historyId);
|
||||
if($hasInfo > 0) {
|
||||
MHistoryInfo::delByHistoryId($historyId);
|
||||
}
|
||||
MLog::write('history','del', '删除历程,ID:'.$historyId);
|
||||
Db::commit();
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return $this->json(3, '删除失败,'.$e->getMessage());
|
||||
}
|
||||
return $this->json();
|
||||
}
|
||||
return $this->json(1, '无此操作');
|
||||
}
|
||||
|
||||
|
||||
public function info()
|
||||
{
|
||||
$historyId = input('param.history_id/d', 0);
|
||||
$history = MHistory::getById($historyId);
|
||||
$infoItems = [];
|
||||
$categoryId = $history['category_id'] ?? 0;
|
||||
if(count($history) > 0) {
|
||||
$infoItems = MHistoryInfo::getByHistoryId($historyId);
|
||||
}
|
||||
$this->data['history'] = $history;
|
||||
$this->data['categoryId'] = $categoryId;
|
||||
$this->data['items'] = $infoItems;
|
||||
return $this->view();
|
||||
}
|
||||
|
||||
// 新增发展历程详情
|
||||
public function addInfo()
|
||||
{
|
||||
$historyId = input('param.history_id/d', 0);
|
||||
$history = MHistory::getById($historyId);
|
||||
if(count($history) == 0) {
|
||||
return $this->json(1, '该历程信息不存在');
|
||||
}
|
||||
if(request()->isPost()) {
|
||||
$params = input('post.item/a', []);
|
||||
$params = arrayHtmlFilter($params);
|
||||
$imgs = input('post.img/a');
|
||||
if (!empty($imgs) && is_array($imgs)) {
|
||||
$imgs = json_encode($imgs);
|
||||
} else {
|
||||
$imgs = '';
|
||||
}
|
||||
try {
|
||||
validate(VHistoryInfo::class)->check($params);
|
||||
$data = [
|
||||
'title' => $params['title'],
|
||||
'visible' => $params['visible'],
|
||||
'history_id' => $historyId,
|
||||
'imgs' => $imgs,
|
||||
];
|
||||
$newItem = MHistoryInfo::create($data);
|
||||
MLog::write('history', 'addInfo', '新增发展历程事例,ID:'.$newItem->id);
|
||||
} catch (ValidateException $e) {
|
||||
return $this->json(2, $e->getError());
|
||||
}
|
||||
return $this->json();
|
||||
} else {
|
||||
$imgSize = '';
|
||||
$category = MCategory::getById($history['category_id']);
|
||||
if(count($category) > 0 && $category['img_width'] && $category['img_height']){
|
||||
$imgSize = $category['img_width'] . '像素 X ' . $category['img_height'] . '像素';
|
||||
}
|
||||
|
||||
$this->data['historyId'] = $historyId;
|
||||
$this->data['history'] = $history;
|
||||
$this->data['imgSize'] = $imgSize;
|
||||
return $this->view();
|
||||
}
|
||||
}
|
||||
|
||||
// 编辑发展历程详情
|
||||
public function editInfo()
|
||||
{
|
||||
$id = input('param.id/d', 0);
|
||||
$item = MHistoryInfo::getById($id);
|
||||
if(count($item) == 0) {
|
||||
return $this->json(1, '该历程事例信息不存在');
|
||||
}
|
||||
|
||||
if(request()->isPost()) {
|
||||
$params = input('post.item/a', []);
|
||||
$params = arrayHtmlFilter($params);
|
||||
$imgs = input('post.img/a');
|
||||
if (!empty($imgs) && is_array($imgs)) {
|
||||
$imgs = json_encode($imgs);
|
||||
} else {
|
||||
$imgs = '';
|
||||
}
|
||||
try {
|
||||
validate(VHistoryInfo::class)->check($params);
|
||||
$data = [
|
||||
'title' => $params['title'],
|
||||
'visible' => $params['visible'],
|
||||
'imgs' => $imgs,
|
||||
];
|
||||
MHistoryInfo::updateById($id, $data);
|
||||
MLog::write('history', 'editInfo', '修改发展历程事例,ID:'.$id);
|
||||
} catch (ValidateException $e) {
|
||||
return $this->json(2, $e->getError());
|
||||
}
|
||||
return $this->json();
|
||||
} else {
|
||||
$history = MHistory::getById($item['history_id']);
|
||||
$imgSize = '';
|
||||
if(count($history) > 0) {
|
||||
$category = MCategory::getById($history['category_id']);
|
||||
if(count($category) > 0 && $category['img_width'] && $category['img_height']){
|
||||
$imgSize = $category['img_width'] . '像素 X ' . $category['img_height'] . '像素';
|
||||
}
|
||||
}
|
||||
|
||||
$this->data['item'] = $item;
|
||||
$this->data['imgSize'] = $imgSize;
|
||||
return $this->view();
|
||||
}
|
||||
}
|
||||
|
||||
public function sortInfo()
|
||||
{
|
||||
if(request()->isPost()) {
|
||||
$id = input('post.id/d');
|
||||
$sort = input('post.sort');
|
||||
$num = input('post.num/d', 1);
|
||||
if($num <= 0){
|
||||
$num = 1;
|
||||
}
|
||||
if(!in_array($sort, ['up', 'down'], true)){
|
||||
return $this->json(2, '参数错误');
|
||||
}
|
||||
$item = MHistoryInfo::getById($id);
|
||||
if(empty($item)){
|
||||
return $this->json(3, '该历程事例信息不存在');
|
||||
}
|
||||
if($sort == 'up'){
|
||||
$where = "history_id='{$item['history_id']}' and sort < {$item['sort']}";
|
||||
$order = "sort desc";
|
||||
}else{
|
||||
$where = "history_id='{$item['history_id']}' and sort > {$item['sort']}";
|
||||
$order = "sort asc";
|
||||
}
|
||||
$forSortItems = MHistoryInfo::getListByWhereAndOrder($where, $order, $num);
|
||||
if(!empty($forSortItems)){
|
||||
$updateData = [];
|
||||
$forSortCount = count($forSortItems);
|
||||
for($i = 0; $i < $forSortCount; $i++){
|
||||
if($i == 0){
|
||||
$updateData[] = [
|
||||
'id' => $forSortItems[$i]['id'],
|
||||
'sort' => $item['sort']
|
||||
];
|
||||
}else{
|
||||
$updateData[] = [
|
||||
'id' => $forSortItems[$i]['id'],
|
||||
'sort' => $forSortItems[$i - 1]['sort']
|
||||
];
|
||||
}
|
||||
}
|
||||
$updateData[] = [
|
||||
'id' => $item['id'],
|
||||
'sort' => $forSortItems[$i - 1]['sort']
|
||||
];
|
||||
if(!empty($updateData)){
|
||||
$model = new MHistoryInfo();
|
||||
$model->saveAll($updateData);
|
||||
$sortStr = $sort == 'up' ? '上移' : '下调';
|
||||
MLog::write('history', 'sortInfo', "发展历程事例排序,ID:{$id} ,{$sortStr}了{$num}位");
|
||||
return $this->json();
|
||||
}
|
||||
}
|
||||
return $this->json(4, '无须调整排序!');
|
||||
}
|
||||
return $this->json(1, '无此操作');
|
||||
}
|
||||
|
||||
public function delInfo()
|
||||
{
|
||||
if(request()->isPost()) {
|
||||
$infoIds = [];
|
||||
$ids = input('post.ids', []);
|
||||
$id = input('post.id', 0);
|
||||
if(!empty($ids)) {
|
||||
if(is_array($ids)) {
|
||||
$infoIds = $ids;
|
||||
} else {
|
||||
$infoIds = explode(',', $ids);
|
||||
}
|
||||
} elseif($id > 0) {
|
||||
$infoIds[] = $id;
|
||||
}
|
||||
if(count($infoIds) > 0) {
|
||||
MHistoryInfo::destroy($infoIds);
|
||||
MLog::write('history','delInfo', '删除历程事例,IDs:'.implode(',', $infoIds));
|
||||
return $this->json();
|
||||
}
|
||||
return $this->json(2, '参数错误');
|
||||
}
|
||||
return $this->json(1, '无此操作');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
namespace app\controller\manager;
|
||||
|
||||
class Index extends Base
|
||||
{
|
||||
//后台首页
|
||||
public function index()
|
||||
{
|
||||
return $this->redirect(url('manager.safe/index'));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,177 @@
|
|||
<?php
|
||||
namespace app\controller\manager;
|
||||
|
||||
use app\model\{Link as MLink, System, Log};
|
||||
use app\validate\Link as VLink;
|
||||
use think\exception\ValidateException;
|
||||
|
||||
class Link extends Base
|
||||
{
|
||||
//批量删除
|
||||
public function batchDel()
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
$ids = input('post.ids/a');
|
||||
if(empty($ids) || !is_array($ids)) {
|
||||
return $this->json(2, '参数错误,请核对之后再操作!');
|
||||
}
|
||||
$items = MLink::getListByIds($ids);
|
||||
if(!empty($items)){
|
||||
$delIds = [];
|
||||
foreach($items as $item){
|
||||
$delIds[] = $item['id'];
|
||||
}
|
||||
MLink::destroy($delIds);
|
||||
Log::write('link', 'betchDel', '批量删除了友情链接,涉及到的ID为:' . implode(',', $delIds));
|
||||
return $this->json();
|
||||
}else{
|
||||
return $this->json(3, '待删除友情链接为空');
|
||||
}
|
||||
}
|
||||
return $this->json(1, '非法请求!');
|
||||
}
|
||||
//删除
|
||||
public function del()
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
$id = input('post.id/d');
|
||||
if(is_numeric($id) && $id > 0) {
|
||||
$item = MLink::getById($id);
|
||||
if(!empty($item)){
|
||||
MLink::destroy($id);
|
||||
Log::write('link', 'del', '删除友情链接,ID:' . $id . ',标题:' . $item['title']);
|
||||
return $this->json();
|
||||
}
|
||||
return $this->json(3, '待删除友情链接不存在');
|
||||
}
|
||||
return $this->json(2, '参数错误,请核对之后再操作!');
|
||||
}
|
||||
return $this->json(1, '非法请求!');
|
||||
}
|
||||
|
||||
//排序
|
||||
public function sort()
|
||||
{
|
||||
if($this->request->isPost()){
|
||||
$id = input('post.id/d');
|
||||
$sort = input('post.sort');
|
||||
$num = input('post.num/d', 1);
|
||||
if($num <= 0){
|
||||
$num = 1;
|
||||
}
|
||||
if(!in_array($sort, ['up', 'down'], true)){
|
||||
return $this->json(2, '参数错误');
|
||||
}
|
||||
$item = MLink::getById($id);
|
||||
if(empty($item)){
|
||||
return $this->json(3, '该友情链接信息不存在!');
|
||||
}
|
||||
if($sort == 'up'){
|
||||
$where = "sort < {$item['sort']}";
|
||||
$order = "sort desc";
|
||||
}else{
|
||||
$where = "sort > {$item['sort']}";
|
||||
$order = "sort asc";
|
||||
}
|
||||
$forSortItems = MLink::getListByWhereAndOrder($where, $order, $num);
|
||||
if(!empty($forSortItems)){
|
||||
$updateData = [];
|
||||
$forSortCount = count($forSortItems);
|
||||
for($i = 0; $i < $forSortCount; $i++){
|
||||
if($i == 0){
|
||||
$updateData[] = [
|
||||
'id' => $forSortItems[$i]['id'],
|
||||
'sort' => $item['sort']
|
||||
];
|
||||
}else{
|
||||
$updateData[] = [
|
||||
'id' => $forSortItems[$i]['id'],
|
||||
'sort' => $forSortItems[$i - 1]['sort']
|
||||
];
|
||||
}
|
||||
}
|
||||
$updateData[] = [
|
||||
'id' => $item['id'],
|
||||
'sort' => $forSortItems[$i - 1]['sort']
|
||||
];
|
||||
if(!empty($updateData)){
|
||||
$model = new MLink();
|
||||
$model->saveAll($updateData);
|
||||
$sortStr = $sort == 'up' ? '上移' : '下调';
|
||||
Log::write('link', 'sort', "友情链接排序,ID:{$id} ,标题:{$item['title']},{$sortStr}了{$num}位");
|
||||
return $this->json();
|
||||
}
|
||||
}
|
||||
return $this->json(4, '无须调整排序!');
|
||||
}
|
||||
return $this->json(1, '非法请求!');
|
||||
}
|
||||
|
||||
//编辑
|
||||
public function edit()
|
||||
{
|
||||
if($this->request->isPost()){
|
||||
$item = input('post.item/a');
|
||||
$id = input('post.id/d');
|
||||
$img = input('post.img');
|
||||
if(is_numeric($id) && $id > 0) {
|
||||
$link = MLink::getById($id);
|
||||
if(empty($link)) {
|
||||
return $this->json(2, '该友情链接信息不存在!');
|
||||
}
|
||||
if(!empty($img)){
|
||||
$item['src'] = $img;
|
||||
}
|
||||
try {
|
||||
validate(VLink::class)->check($item);
|
||||
MLink::updateById($id, $item);
|
||||
Log::write('link', 'edit', "友情链接编辑,ID:{$id} ,标题:{$item['title']}");
|
||||
return $this->json();
|
||||
} catch (ValidateException $e) {
|
||||
return $this->json(3, $e->getError());
|
||||
}
|
||||
}
|
||||
return $this->json(1, '参数错误,请核对之后再操作!');
|
||||
} else {
|
||||
$id = input('param.id/d');
|
||||
$item = MLink::getById($id);
|
||||
$imgSize = System::getLinkImageSize();
|
||||
|
||||
$this->data['item'] = $item;
|
||||
$this->data['img_size'] = $imgSize;
|
||||
return $this->view();
|
||||
}
|
||||
}
|
||||
|
||||
//添加
|
||||
public function add()
|
||||
{
|
||||
if($this->request->isPost()){
|
||||
$item = input('post.item/a');
|
||||
$img = input('post.img');
|
||||
|
||||
if(!empty($img)){
|
||||
$item['src'] = $img;
|
||||
}
|
||||
try {
|
||||
validate(VLink::class)->check($item);
|
||||
$link = MLink::create($item);
|
||||
Log::write('link', 'add', "友情链接新增,ID:{$link->id} ,标题:{$item['title']}");
|
||||
return $this->json();
|
||||
} catch (ValidateException $e) {
|
||||
return $this->json(2, $e->getError());
|
||||
}
|
||||
} else {
|
||||
$imgSize = System::getLinkImageSize();
|
||||
$this->data['img_size'] = $imgSize;
|
||||
return $this->view();
|
||||
}
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
$items = MLink::getList();
|
||||
$this->data['items'] = $items;
|
||||
return $this->view();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
namespace app\controller\manager;
|
||||
|
||||
use app\model\{Member, AuthRule, LoginLog};
|
||||
use app\controller\BaseController;
|
||||
|
||||
class Login extends BaseController
|
||||
{
|
||||
protected $middleware = ['csrf'];
|
||||
|
||||
/**
|
||||
* user lgoin
|
||||
* use ajax post push
|
||||
*
|
||||
* @return void | JSON
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
if(request()->isPost()){
|
||||
$username = trim(input('param.username'));
|
||||
$password = trim(input('param.password'));
|
||||
$loginUrl = url('manager.login/index');
|
||||
if(empty($username) || empty($password)){
|
||||
session('loginError','用户名和密码不能为空');
|
||||
return $this->redirect($loginUrl);
|
||||
}
|
||||
|
||||
$member = Member::getByUserName($username);
|
||||
if(empty($member)){
|
||||
session('loginError','用户名错误');
|
||||
return $this->redirect($loginUrl);
|
||||
}
|
||||
if($member['password'] != md5($password)){
|
||||
session('loginError','用户密码错误');
|
||||
return $this->redirect($loginUrl);
|
||||
|
||||
}
|
||||
$rulesList = AuthRule::userRolesList($member['group_id']);
|
||||
$rulesIdStr = '';
|
||||
if (!empty($rulesList)) {
|
||||
$rulesId = $rulesList['allRulesId'];
|
||||
$rulesIdStr = implode(',', $rulesId);
|
||||
}
|
||||
|
||||
$authSession = [
|
||||
'userId' => $member['id'],
|
||||
'userName' => $member['username'],
|
||||
'groupId' => $member['group_id'],
|
||||
'rules' => $rulesIdStr,
|
||||
'cates' => $member['cates']
|
||||
];
|
||||
|
||||
//记录最后登陆时间
|
||||
$ip = request()->ip();
|
||||
$time = time();
|
||||
Member::updateById($member['id'], [
|
||||
'login_time' => $time,
|
||||
'login_ip' => $ip
|
||||
]);
|
||||
LoginLog::create([
|
||||
'member_id' => $member['id'],
|
||||
'name' => $member['username'],
|
||||
'ip' => $ip,
|
||||
'create_time' => $time
|
||||
]);
|
||||
session('auth', $authSession);
|
||||
return redirect(url('manager.index/index'));
|
||||
}
|
||||
|
||||
$viewData = [];
|
||||
if(session('?loginError')) {
|
||||
$viewData['error'] = session('loginError');
|
||||
}
|
||||
session('loginError', null);
|
||||
$viewData['_token'] = $this->request->csrfToken;
|
||||
return view()->assign($viewData);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
namespace app\controller\manager;
|
||||
|
||||
use app\controller\BaseController;
|
||||
|
||||
class Logout extends BaseController
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
session(null);
|
||||
return redirect(url('manager.login/index'));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
<?php
|
||||
namespace app\controller\manager;
|
||||
|
||||
use app\model\{Category, AuthGroup, Member as MMember, Log};
|
||||
use Exception;
|
||||
use think\facade\Db;
|
||||
|
||||
class Member extends Base
|
||||
{
|
||||
/**
|
||||
* 删除管理用户
|
||||
*/
|
||||
public function del()
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
$id = input('post.id/d');
|
||||
if (is_numeric($id) === true && $id > 0) {
|
||||
$item = MMember::getByID($id);
|
||||
if(!empty($item)){
|
||||
MMember::destroy($id);
|
||||
Log::write('member', 'del', "管理员删除,ID:{$id}, 管理员:{$item['username']}");
|
||||
return $this->json();
|
||||
}
|
||||
}
|
||||
return $this->json(2, '参数错误,请核对之后再操作!');
|
||||
}
|
||||
return $this->json(1, '非法请求!');
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改管理用户信息
|
||||
* 由于try语法中抛出的异常类型与$this->json()抛出的异常类型不一致,因此需要利用$errorMsg 来判断返回情况
|
||||
*/
|
||||
public function edit()
|
||||
{
|
||||
if($this->request->isPost()){
|
||||
$id = input('post.id/d');
|
||||
$username = trim(input('post.username'));
|
||||
$password = trim(input('post.password'));
|
||||
$groupId = input('post.group_id/d');
|
||||
if ((is_numeric($id) === true && $id > 0) && ((is_numeric($groupId) === true && $groupId > 0) && !empty($username))) {
|
||||
$member = MMember::getByUserName($username);
|
||||
if(!empty($member) && $member['id'] != $id){
|
||||
return $this->json(2, '该用户名已被使用!');
|
||||
}
|
||||
$errorMsg = '';
|
||||
Db::startTrans();
|
||||
try {
|
||||
$member = MMember::getById($id);
|
||||
$item = [
|
||||
'username' => $username,
|
||||
'group_id' => $groupId
|
||||
];
|
||||
//角色权限重新赋值
|
||||
$group = AuthGroup::getById($groupId);
|
||||
$item['rules'] = $group['rules'];
|
||||
|
||||
if(!empty($password)){
|
||||
$item['password'] = md5($password);
|
||||
}
|
||||
MMember::updateById($id, $item);
|
||||
Log::write('member', 'edit', "管理员编辑,ID:{$id}, 管理员:{$item['username']}");
|
||||
Db::commit();
|
||||
} catch (Exception $e) {
|
||||
Db::rollback();
|
||||
$errorMsg = '用户信息修改失败!'.$e->getMessage();
|
||||
}
|
||||
if (empty($errorMsg)) {
|
||||
return $this->json();
|
||||
}
|
||||
return $this->json(3, $errorMsg);
|
||||
}
|
||||
return $this->json(1, '参数错误,请核对之后再操作!');
|
||||
}else{
|
||||
$id = input('param.id/d');
|
||||
if (is_numeric($id) === true && $id > 0) {
|
||||
$member = MMember::getByID($id);
|
||||
$item = [
|
||||
'id' => $member['id'],
|
||||
'username' => $member['username'],
|
||||
'group_id' => $member['group_id']
|
||||
];
|
||||
$auth = session('auth');
|
||||
$groups = AuthGroup::getListById($auth['groupId']);
|
||||
$this->data['groups'] = $groups;
|
||||
$this->data['item'] = $item;
|
||||
return $this->view();
|
||||
}
|
||||
return $this->json(1, '参数错误,请核对之后再操作!');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增管理用户
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
if($this->request->isPost()){
|
||||
$groupId = input('post.group_id/d');
|
||||
$username = trim(input('post.username'));
|
||||
$password = trim(input('post.password'));
|
||||
if ((is_numeric($groupId) === true && $groupId > 0) && ($username != "" && $password != "")) {
|
||||
$member = MMember::getByUserName($username);
|
||||
if(!empty($member)){
|
||||
return $this->json(2, '该用户名已被使用!');
|
||||
}
|
||||
$group = AuthGroup::getById($groupId);
|
||||
$newMember = MMember::create([
|
||||
'username' => $username,
|
||||
'group_id' => $groupId,
|
||||
'password' => md5($password),
|
||||
'rules' => $group['rules'],
|
||||
'cates' => '',
|
||||
'login_time' => 0,
|
||||
]);
|
||||
Log::write('member', 'add', "管理员新增,ID:{$newMember->id}, 管理员:{$newMember['username']}");
|
||||
return $this->json();
|
||||
}
|
||||
return $this->json(1, '参数错误,请核对之后再操作!');
|
||||
}
|
||||
|
||||
$auth = session('auth');
|
||||
$groups = AuthGroup::getListById($auth['groupId']);
|
||||
$this->data['groups'] = $groups;
|
||||
return $this->view();
|
||||
}
|
||||
|
||||
/**
|
||||
* 栏目菜单分配
|
||||
*/
|
||||
public function menuAlloter()
|
||||
{
|
||||
if(request()->isPost()) {
|
||||
$cates = input('post.cates/a');
|
||||
$id = input('post.id/d');
|
||||
if (is_array($cates) && (is_numeric($id) === true && $id > 0)) {
|
||||
$member = MMember::getById($id);
|
||||
if(empty($member)){
|
||||
return $this->json(2, '无此用户信息,请核对之后再操作!');
|
||||
}
|
||||
MMember::updateCates($id, $cates);
|
||||
Log::write('member', 'menuAlloter', "管理员栏目分配,ID:{$id}, 管理员:{$member['username']}");
|
||||
return $this->json();
|
||||
}else{
|
||||
return $this->json(3, '传入参数错误,请核对之后再操作!');
|
||||
}
|
||||
} else {
|
||||
$id = input('param.id/d');
|
||||
if (is_numeric($id) && $id > 0) {
|
||||
$member = MMember::getById($id);
|
||||
if (empty($member)) {
|
||||
return $this->json(2, '该管理员信息不存在,请核对之后再操作!');
|
||||
}
|
||||
$cates = Category::getListTree(false);
|
||||
$memberCates = array_filter(explode(',', $member['cates']));
|
||||
|
||||
$this->data['id'] = $id;
|
||||
$this->data['member'] = $member;
|
||||
$this->data['memberCates'] = $memberCates;
|
||||
$this->data['cates'] = $cates;
|
||||
return $this->view();
|
||||
}
|
||||
return $this->json(1, '参数错误,请核对之后再操作!',$id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 所有用户列表
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$auth = session('auth');
|
||||
if ($auth['groupId'] == 1) {
|
||||
$items = MMember::getList(40);
|
||||
} else {
|
||||
$items = MMember::getListByGroup($auth['groupId'], 40);
|
||||
}
|
||||
$this->data['items'] = $items;
|
||||
return $this->view();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,184 @@
|
|||
<?php
|
||||
namespace app\controller\manager;
|
||||
|
||||
use app\model\{Model as MModel, Log};
|
||||
use app\validate\Model as VModel;
|
||||
use think\exception\ValidateException;
|
||||
|
||||
class Model extends Base
|
||||
{
|
||||
//批量删除模型
|
||||
public function batchDel()
|
||||
{
|
||||
if($this->request->isPost()){
|
||||
$ids = input('post.ids/a');
|
||||
if(is_array($ids)) {
|
||||
$idsArr = $ids;
|
||||
} else {
|
||||
$idsArr = array_filter(explode(',', $ids));
|
||||
}
|
||||
if(count($idsArr) == 0) {
|
||||
return $this->json(1, '无效请求,参数错误!');
|
||||
}
|
||||
$items = MModel::getListByIds($idsArr);
|
||||
if(!empty($items)){
|
||||
$delIds = [];
|
||||
foreach($items as $item){
|
||||
$delIds[] = $item['id'];
|
||||
}
|
||||
MModel::destroy($delIds);
|
||||
Log::write('model', 'batchDel', "模型批量删除,ID:" . implode(',', $ids));
|
||||
return $this->json();
|
||||
}
|
||||
return $this->json(2, '无效请求,参数错误!');
|
||||
}
|
||||
return $this->json(1, '非法请求!');
|
||||
}
|
||||
|
||||
//删除单个模型
|
||||
public function del()
|
||||
{
|
||||
if($this->request->isPost()){
|
||||
$id = input('post.id/d');
|
||||
if(is_numeric($id) && $id > 0) {
|
||||
$item = MModel::getById($id);
|
||||
if(!empty($item)) {
|
||||
MModel::destroy($id);
|
||||
Log::write('model', 'del', "模型删除,ID:{$id}, 标题:{$item['name']}");
|
||||
return $this->json();
|
||||
}
|
||||
return $this->json(3, '删除失败!该模型不存在,请刷新页面后再试!');
|
||||
}
|
||||
return $this->json(2, '无效请求,参数错误!');
|
||||
}
|
||||
return $this->json(1, '非法请求!');
|
||||
}
|
||||
|
||||
//编辑模型
|
||||
public function edit()
|
||||
{
|
||||
if($this->request->isPost()){
|
||||
$item = [];
|
||||
$item['name'] = input('post.name');
|
||||
$item['template'] = input('post.template');
|
||||
$item['manager'] = input('post.manager');
|
||||
$id = input('post.id/d');
|
||||
if(is_numeric($id) && $id > 0) {
|
||||
$model = MModel::getById($id);
|
||||
if(empty($model)){
|
||||
return $this->json(2, '无此模型数据!');
|
||||
}
|
||||
try {
|
||||
validate(VModel::class)->check($item);
|
||||
MModel::updateById($id, $item);
|
||||
Log::write('model', 'edit', "模型编辑,ID:{$id}, 标题:{$item['name']}");
|
||||
return $this->json();
|
||||
} catch (ValidateException $e) {
|
||||
return $this->json(3, $e->getError());
|
||||
}
|
||||
}
|
||||
return $this->json(1, '无效请求,参数错误!');
|
||||
}else{
|
||||
$id = input('param.id/d');
|
||||
if (is_numeric($id) && $id > 0) {
|
||||
$item = MModel::getById($id);
|
||||
$this->data['item'] = $item;
|
||||
return $this->view();
|
||||
}
|
||||
return $this->json(1,'传入参数错误,请核对之后再操作!');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加模型
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
if($this->request->isPost()){
|
||||
$item = [];
|
||||
$item['name'] = input('post.name');
|
||||
$item['template'] = input('post.template');
|
||||
$item['manager'] = input('post.manager');
|
||||
try {
|
||||
validate(VModel::class)->check($item);
|
||||
$model = MModel::create($item);
|
||||
Log::write('model', 'add', "模型新增,ID:{$model->id}, 标题:{$item['name']}");
|
||||
return $this->json();
|
||||
} catch (ValidateException $e) {
|
||||
return $this->json(2, $e->getError());
|
||||
}
|
||||
}else {
|
||||
return $this->view();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 模型列表
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$items = MModel::getList();
|
||||
$this->data['items'] = $items;
|
||||
return $this->view();
|
||||
}
|
||||
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
public function sort()
|
||||
{
|
||||
if($this->request->isPost()){
|
||||
$id = input('post.id/d');
|
||||
$sort = input('post.sort');
|
||||
$num = input('post.num/d', 1);
|
||||
if($num <= 0){
|
||||
$num = 1;
|
||||
}
|
||||
if(!in_array($sort, ['up', 'down'], true)){
|
||||
return $this->json(2, '参数错误');
|
||||
}
|
||||
$item = MModel::getById($id);
|
||||
if(empty($item)) {
|
||||
return $this->json(3, '无此模型!');
|
||||
}
|
||||
if($sort == 'up'){
|
||||
$where = "sort < {$item['sort']}";
|
||||
$order = "sort desc";
|
||||
}else{
|
||||
$where = "sort > {$item['sort']}";
|
||||
$order = "sort asc";
|
||||
}
|
||||
$forSortItems = MModel::getListByWhereAndOrder($where, $order, $num);
|
||||
if(!empty($forSortItems)){
|
||||
$updateData = [];
|
||||
$forSortCount = count($forSortItems);
|
||||
for($i = 0; $i < $forSortCount; $i++){
|
||||
if($i == 0){
|
||||
$updateData[] = [
|
||||
'id' => $forSortItems[$i]['id'],
|
||||
'sort' => $item['sort']
|
||||
];
|
||||
}else{
|
||||
$updateData[] = [
|
||||
'id' => $forSortItems[$i]['id'],
|
||||
'sort' => $forSortItems[$i - 1]['sort']
|
||||
];
|
||||
}
|
||||
}
|
||||
$updateData[] = [
|
||||
'id' => $item['id'],
|
||||
'sort' => $forSortItems[$i - 1]['sort']
|
||||
];
|
||||
if(!empty($updateData)){
|
||||
$model = new MModel();
|
||||
$model->saveAll($updateData);
|
||||
$sortStr = $sort == 'up' ? '上移' : '下调';
|
||||
Log::write('model', 'sort', "模型排序,ID:{$id} ,标题:{$item['name']},{$sortStr}了{$num}位");
|
||||
return $this->json();
|
||||
}
|
||||
}
|
||||
return $this->json(4, '无须调整排序!');
|
||||
}
|
||||
return $this->json(1, '非法请求!');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,461 @@
|
|||
<?php
|
||||
namespace app\controller\manager;
|
||||
|
||||
use app\model\{Category, Block, Log};
|
||||
use app\validate\Block as VBlock;
|
||||
use think\exception\ValidateException;
|
||||
|
||||
class Page extends Base
|
||||
{
|
||||
//源码,代码
|
||||
public function code()
|
||||
{
|
||||
if($this->request->isPost()){
|
||||
$item = input('post.item/a');
|
||||
$categoryId = input('post.category_id/d');
|
||||
$id = input('post.id/d');
|
||||
try{
|
||||
validate(VBlock::class)->check($item);
|
||||
if(empty($item['value'])){
|
||||
return $this->json(2, '内容不可为空!');
|
||||
}
|
||||
$block = Block::getByKeyword($item['keyword'], $categoryId);
|
||||
if($id){
|
||||
if(!empty($block) && $block['id'] != $id){
|
||||
return $this->json(3, '键值已存在,请更改键值');
|
||||
}
|
||||
Block::updateById($id, $item);
|
||||
Log::write('page', 'code', "单页代码编辑,ID:{$id}, 键值:{$item['keyword']}");
|
||||
}else{
|
||||
if($categoryId <= 0){
|
||||
return $this->json(4, '栏目参数错误!');
|
||||
}
|
||||
if(!empty($block)){
|
||||
return $this->json(3, '键值已存在,请更改键值');
|
||||
}
|
||||
$item['category_id'] = $categoryId;
|
||||
$item['type'] = Block::CODE;
|
||||
$block = Block::create($item);
|
||||
Log::write('page', 'code', "单页代码新增,ID:{$block->id}, 键值:{$item['keyword']}");
|
||||
}
|
||||
return $this->json();
|
||||
} catch (ValidateException $e){
|
||||
return $this->json(1, $e->getError());
|
||||
}
|
||||
}else{
|
||||
$id = input('param.id/d');
|
||||
if($id <= 0){ //添加
|
||||
$categoryId = input('param.category_id/d');
|
||||
$category = Category::getById($categoryId);
|
||||
if(empty($category)){
|
||||
$url = url('manager.content/index')->__toString();
|
||||
return $this->error('无此栏目', $url);
|
||||
}
|
||||
}else{ //修改
|
||||
$item = Block::getById($id);
|
||||
if(empty($item)){
|
||||
return $this->error('无此代码块!');
|
||||
}
|
||||
$categoryId = $item['category_id'];
|
||||
$this->data['item'] = $item;
|
||||
}
|
||||
$this->data['categoryId'] = $categoryId;
|
||||
return $this->view();
|
||||
}
|
||||
}
|
||||
//排序
|
||||
public function sort()
|
||||
{
|
||||
if($this->request->isPost()){
|
||||
$id = input('post.id/d');
|
||||
$sort = input('post.sort');
|
||||
$num = input('post.num/d', 1);
|
||||
if($num <= 0){
|
||||
$num = 1;
|
||||
}
|
||||
if(!in_array($sort, ['up', 'down'], true)){
|
||||
return $this->json(2, '参数错误');
|
||||
}
|
||||
$item = Block::getById($id);
|
||||
if(empty($item)){
|
||||
return $this->json(3, '无此块信息');
|
||||
}
|
||||
if($sort == 'up'){
|
||||
$where = "category_id='{$item['category_id']}' and sort < {$item['sort']}";
|
||||
$order = "sort desc";
|
||||
}else{
|
||||
$where = "category_id='{$item['category_id']}' and sort > {$item['sort']}";
|
||||
$order = "sort asc";
|
||||
}
|
||||
$forSortItems = Block::getListByWhereAndOrder($where, $order, $num);
|
||||
if(!empty($forSortItems)){
|
||||
$updateData = [];
|
||||
$forSortCount = count($forSortItems);
|
||||
for($i = 0; $i < $forSortCount; $i++){
|
||||
if($i == 0){
|
||||
$updateData[] = [
|
||||
'id' => $forSortItems[$i]['id'],
|
||||
'sort' => $item['sort']
|
||||
];
|
||||
}else{
|
||||
$updateData[] = [
|
||||
'id' => $forSortItems[$i]['id'],
|
||||
'sort' => $forSortItems[$i - 1]['sort']
|
||||
];
|
||||
}
|
||||
}
|
||||
$updateData[] = [
|
||||
'id' => $item['id'],
|
||||
'sort' => $forSortItems[$i - 1]['sort']
|
||||
];
|
||||
if(!empty($updateData)){
|
||||
$model = new Block();
|
||||
$model->saveAll($updateData);
|
||||
$sortStr = $sort == 'up' ? '上移' : '下调';
|
||||
Log::write('page', 'sort', "单页区块排序,ID:{$id} ,键值:{$item['keyword']},{$sortStr}了{$num}位");
|
||||
return $this->json();
|
||||
}
|
||||
}
|
||||
return $this->json(4, '无须调整排序!');
|
||||
}
|
||||
return $this->error('无此操作');
|
||||
}
|
||||
|
||||
//删除
|
||||
public function delblock()
|
||||
{
|
||||
if ($this->request->isAjax()) {
|
||||
$id = input('post.id/d');
|
||||
$item = Block::getById($id);
|
||||
if(!empty($item)){
|
||||
Block::destroy($id);
|
||||
Log::write('page', 'delblock', "单页区块删除,ID:{$id} ,键值:{$item['keyword']}");
|
||||
return $this->json();
|
||||
}
|
||||
return $this->json(1, 'fail');
|
||||
}
|
||||
return $this->error('无此操作');
|
||||
}
|
||||
//图片
|
||||
public function img()
|
||||
{
|
||||
if($this->request->isPost()){
|
||||
$item = input('post.item/a');
|
||||
$img = trim(input('post.img'));
|
||||
$categoryId = input('post.category_id/d');
|
||||
if (!empty($img) && $img == 'null') {
|
||||
$img = '';
|
||||
}
|
||||
$id = input('post.id/d');
|
||||
try{
|
||||
validate(VBlock::class)->check($item);
|
||||
$block = Block::getByKeyword($item['keyword'], $categoryId);
|
||||
if($id){
|
||||
if(!empty($block) && $block['id'] != $id){
|
||||
return $this->json(4, '键值已存在,请更改键值');
|
||||
}
|
||||
if(!empty($img)){
|
||||
$item['value'] = $img;
|
||||
}
|
||||
Block::updateById($id, $item);
|
||||
Log::write('page', 'img', "单页图片编辑,ID:{$id} ,键值:{$item['keyword']}");
|
||||
}else{
|
||||
if(!empty($block)){
|
||||
return $this->json(4, '键值已存在,请更改键值');
|
||||
}
|
||||
if($categoryId <= 0){
|
||||
return $this->json(2, '栏目参数错误!!');
|
||||
}
|
||||
if(empty($img)){
|
||||
return $this->json(3, '图片不可为空');
|
||||
}
|
||||
$item['value'] = $img;
|
||||
$item['type'] = Block::IMG;
|
||||
$item['category_id'] = $categoryId;
|
||||
$block = Block::create($item);
|
||||
Log::write('page', 'img', "单页图片新增,ID:{$block->id} ,键值:{$item['keyword']}");
|
||||
}
|
||||
return $this->json();
|
||||
} catch (ValidateException $e){
|
||||
return $this->json(1, $e->getError());
|
||||
}
|
||||
}else{
|
||||
$id = input('param.id/d');
|
||||
if($id <= 0){ //添加
|
||||
$categoryId = input('param.category_id/d');
|
||||
$category = Category::getById($categoryId);
|
||||
if(empty($category)){
|
||||
$url = url('manager.content/index')->__toString();
|
||||
return $this->error('无此栏目', $url);
|
||||
}
|
||||
}else{ //修改
|
||||
$item = Block::getById($id);
|
||||
if(empty($item)){
|
||||
return $this->error('无此图片!');
|
||||
}
|
||||
$categoryId = $item['category_id'];
|
||||
$this->data['item'] = $item;
|
||||
}
|
||||
if(isset($item) && $item['width'] && $item['height']){
|
||||
$imgSize = $item['width'].'px X '.$item['height'].'px';
|
||||
}else{
|
||||
$imgSize = '';
|
||||
}
|
||||
$this->data['categoryId'] = $categoryId;
|
||||
$this->data['imgSize'] = $imgSize;
|
||||
$this->data['groupId'] = session('auth.groupId');
|
||||
return $this->view();
|
||||
}
|
||||
}
|
||||
//文字块
|
||||
public function block()
|
||||
{
|
||||
if($this->request->isPost()){
|
||||
$item = input('post.item/a');
|
||||
$categoryId = input('post.category_id/d');
|
||||
$id = input('post.id/d');
|
||||
try{
|
||||
validate(VBlock::class)->check($item);
|
||||
if(empty($item['value'])){
|
||||
return $this->json(1, '内容不可为空!');
|
||||
}
|
||||
$block = Block::getByKeyword($item['keyword'], $categoryId);
|
||||
if($id){
|
||||
if(!empty($block) && $block['id'] != $id){
|
||||
return $this->json(4, '键值已存在,请更改键值');
|
||||
}
|
||||
Block::updateById($id, $item);
|
||||
Log::write('page', 'block', "单页文字块编辑,ID:{$id} ,键值:{$item['keyword']}");
|
||||
}else{
|
||||
if($categoryId <= 0){
|
||||
return $this->json(2, '栏目参数错误!');
|
||||
}
|
||||
if(!empty($block)){
|
||||
return $this->json(4, '键值已存在,请更改键值');
|
||||
}
|
||||
$item['category_id'] = $categoryId;
|
||||
$block = Block::create($item);
|
||||
Log::write('page', 'block', "单页文字块新增,ID:{$block->id} ,键值:{$item['keyword']}");
|
||||
}
|
||||
return $this->json();
|
||||
} catch (ValidateException $e){
|
||||
return $this->json(1, $e->getError());
|
||||
}
|
||||
}else{
|
||||
$id = input('param.id/d');
|
||||
if($id <= 0){ //添加
|
||||
$categoryId = input('param.category_id/d');
|
||||
$category = Category::getById($categoryId);
|
||||
if(empty($category)){
|
||||
$url = url('manager.content/index')->__toString();
|
||||
return $this->error('无此栏目', $url);
|
||||
}
|
||||
}else{ //修改
|
||||
$item = Block::getById($id);
|
||||
if(empty($item)){
|
||||
return $this->error('无此文字块!');
|
||||
}
|
||||
$categoryId = $item['category_id'];
|
||||
$this->data['item'] = $item;
|
||||
}
|
||||
$this->data['categoryId'] = $categoryId;
|
||||
return $this->view();
|
||||
}
|
||||
}
|
||||
//富文本内容
|
||||
public function text()
|
||||
{
|
||||
if($this->request->isPost()){
|
||||
$item = input('post.item/a');
|
||||
$categoryId = input('post.category_id/d');
|
||||
try{
|
||||
validate(VBlock::class)->check($item);
|
||||
if(empty($item['value'])){
|
||||
return $this->json(1, '内容不可为空!');
|
||||
}
|
||||
$block = Block::getByKeyword($item['keyword'], $categoryId);
|
||||
$id = input('post.id/d');
|
||||
if($id){
|
||||
if(!empty($block) && $block['id'] != $id){
|
||||
return $this->json(4, '键值已存在,请更改');
|
||||
}
|
||||
Block::updateById($id, $item);
|
||||
Log::write('page', 'text', "单页富文本编辑,ID:{$id} ,键值:{$item['keyword']}");
|
||||
}else{
|
||||
if($categoryId <= 0){
|
||||
return $this->json(2, '栏目参数错误!');
|
||||
}
|
||||
if(!empty($block)){
|
||||
return $this->json(4, '键值已存在,请更改键值');
|
||||
}
|
||||
$item['category_id'] = $categoryId;
|
||||
$item['type'] = Block::TEXT;
|
||||
$block = Block::create($item);
|
||||
Log::write('page', 'text', "单页富文本新增,ID:{$block->id} ,键值:{$item['keyword']}");
|
||||
}
|
||||
return $this->json();
|
||||
} catch (ValidateException $e){
|
||||
return $this->json(1, $e->getError());
|
||||
}
|
||||
}else{
|
||||
$id = input('param.id/d');
|
||||
if($id <= 0){ //添加
|
||||
$categoryId = input('param.category_id/d');
|
||||
$category = Category::getById($categoryId);
|
||||
if(empty($category)){
|
||||
$url = url('manager.content/index')->__toString();
|
||||
return $this->error('无此栏目', $url);
|
||||
}
|
||||
}else{ //修改
|
||||
$item = Block::getById($id);
|
||||
if(empty($item)){
|
||||
return $this->error('无此富文本!');
|
||||
}
|
||||
$categoryId = $item['category_id'];
|
||||
$this->data['item'] = $item;
|
||||
}
|
||||
$this->data['categoryId'] = $categoryId;
|
||||
return $this->view();
|
||||
}
|
||||
}
|
||||
//组图
|
||||
public function group()
|
||||
{
|
||||
if($this->request->isPost()){
|
||||
$item = input('post.item/a');
|
||||
$imgs = input('post.img/a');
|
||||
$categoryId = input('post.category_id/d');
|
||||
if (!empty($imgs) && is_array($imgs)) {
|
||||
$item['value'] = json_encode($imgs);
|
||||
} else {
|
||||
$item['value'] = '';
|
||||
}
|
||||
try{
|
||||
validate(VBlock::class)->check($item);
|
||||
$block = Block::getByKeyword($item['keyword'], $categoryId);
|
||||
$id = input('post.id/d');
|
||||
if($id){
|
||||
if(!empty($block) && $block['id'] != $id){
|
||||
return $this->json(4, '键值已存在,请更改');
|
||||
}
|
||||
Block::updateById($id, $item);
|
||||
Log::write('page', 'group', "单页组图编辑,ID:{$id} ,键值:{$item['keyword']}");
|
||||
}else{
|
||||
if($categoryId <= 0){
|
||||
return $this->json(2, '栏目参数错误!');
|
||||
}
|
||||
if(!empty($block)){
|
||||
return $this->json(4, '键值已存在,请更改键值');
|
||||
}
|
||||
$item['category_id'] = $categoryId;
|
||||
$item['type'] = Block::GROUP;
|
||||
$block = Block::create($item);
|
||||
Log::write('page', 'group', "单页组图新增,ID:{$block->id} ,键值:{$item['keyword']}");
|
||||
}
|
||||
return $this->json();
|
||||
} catch (ValidateException $e){
|
||||
return $this->json(1, $e->getError());
|
||||
}
|
||||
}else{
|
||||
$id = input('param.id/d');
|
||||
if($id <= 0){ //添加
|
||||
$categoryId = input('param.category_id/d');
|
||||
$category = Category::getById($categoryId);
|
||||
if(empty($category)){
|
||||
$url = url('manager.content/index')->__toString();
|
||||
return $this->error('无此栏目', $url);
|
||||
}
|
||||
}else{ //修改
|
||||
$item = Block::getById($id);
|
||||
if(empty($item)){
|
||||
return $this->error('无此组图!');
|
||||
}
|
||||
$categoryId = $item['category_id'];
|
||||
$this->data['item'] = $item;
|
||||
}
|
||||
$this->data['categoryId'] = $categoryId;
|
||||
if(isset($item) && $item['width'] && $item['height']){
|
||||
$imgSize = $item['width'].'px X '.$item['height'].'px';
|
||||
}else{
|
||||
$imgSize = '';
|
||||
}
|
||||
$this->data['imgSize'] = $imgSize;
|
||||
$this->data['groupId'] = session('auth.groupId');
|
||||
return $this->view();
|
||||
}
|
||||
}
|
||||
//视频
|
||||
public function video()
|
||||
{
|
||||
if($this->request->isPost()){
|
||||
$item = input('post.item/a');
|
||||
$img = trim(input('post.img'));
|
||||
$video = trim(input('post.video'));
|
||||
$categoryId = input('post.category_id/d');
|
||||
|
||||
try{
|
||||
validate(VBlock::class)->check($item);
|
||||
$block = Block::getByKeyword($item['keyword'], $categoryId);
|
||||
$id = input('post.id/d');
|
||||
if($id){
|
||||
if(!empty($block) && $block['id'] != $id){
|
||||
return $this->json(4, '键值已存在,请更改');
|
||||
}
|
||||
if(!empty($img)){
|
||||
$item['img'] = $img;
|
||||
}
|
||||
if(!empty($video)){
|
||||
$item['value'] = $video;
|
||||
}
|
||||
Block::updateById($id, $item);
|
||||
Log::write('page', 'video', "单页视频编辑,ID:{$id} ,键值:{$item['keyword']}");
|
||||
}else{
|
||||
if($categoryId <= 0){
|
||||
return $this->json(2, '栏目参数错误!');
|
||||
}
|
||||
if(!empty($block)){
|
||||
return $this->json(3, '键值已存在,请更改键值');
|
||||
}
|
||||
if(empty($video)){
|
||||
return $this->json(3, '视频不可为空');
|
||||
}
|
||||
$item['category_id'] = $categoryId;
|
||||
$item['type'] = Block::VIDEO;
|
||||
$item['value'] = $video;
|
||||
$item['img'] = $img;
|
||||
$block = Block::create($item);
|
||||
Log::write('page', 'video', "单页视频新增,ID:{$block->id} ,键值:{$item['keyword']}");
|
||||
}
|
||||
return $this->json();
|
||||
} catch (ValidateException $e){
|
||||
return $this->json(1, $e->getError());
|
||||
}
|
||||
}else{
|
||||
$id = input('param.id/d');
|
||||
if($id <= 0){ //添加
|
||||
$categoryId = input('param.category_id/d');
|
||||
$category = Category::getById($categoryId);
|
||||
if(empty($category)){
|
||||
$url = url('manager.content/index')->__toString();
|
||||
return $this->error('无此栏目', $url);
|
||||
}
|
||||
}else{ //修改
|
||||
$item = Block::getById($id);
|
||||
if(empty($item)){
|
||||
return $this->error('无此视频!');
|
||||
}
|
||||
$categoryId = $item['category_id'];
|
||||
$this->data['item'] = $item;
|
||||
}
|
||||
$this->data['categoryId'] = $categoryId;
|
||||
if(isset($item) && $item['width'] && $item['height']){
|
||||
$imgSize = $item['width'].'px X '.$item['height'].'px';
|
||||
}else{
|
||||
$imgSize = '';
|
||||
}
|
||||
$this->data['imgSize'] = $imgSize;
|
||||
$this->data['groupId'] = session('auth.groupId');
|
||||
return $this->view();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,180 @@
|
|||
<?php
|
||||
namespace app\controller\manager;
|
||||
|
||||
use app\model\{AuthRule, AuthGroup, Log};
|
||||
use app\validate\AuthRule as VAuthRule;
|
||||
use think\exception\ValidateException;
|
||||
|
||||
class Rule extends Base
|
||||
{
|
||||
/**
|
||||
* 权限排序
|
||||
* 暂不允许父级变更
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function sort()
|
||||
{
|
||||
if ($this->request->isAjax()) {
|
||||
$id = input('post.id');
|
||||
$sort = input('post.sort');
|
||||
$num = input('post.num/d', 1);
|
||||
if($num <= 0){
|
||||
$num = 1;
|
||||
}
|
||||
if(!in_array($sort, ['up', 'down'], true)){
|
||||
return $this->json(2, '参数错误');
|
||||
}
|
||||
$item = AuthRule::getById($id);
|
||||
if(empty($item)){
|
||||
return $this->json(3, '权限不存在');
|
||||
}
|
||||
if($sort == 'up'){
|
||||
$where = "parent_id = {$item['parent_id']} and sort < {$item['sort']}";
|
||||
$order = "sort desc";
|
||||
}else{
|
||||
$where = "parent_id = {$item['parent_id']} and sort > {$item['sort']}";
|
||||
$order = "sort asc";
|
||||
}
|
||||
$forSortItems = AuthRule::getListByWhereAndOrder($where, $order, $num);
|
||||
if(!empty($forSortItems)){
|
||||
$updateData = [];
|
||||
$forSortCount = count($forSortItems);
|
||||
for($i = 0; $i < $forSortCount; $i++){
|
||||
if($i == 0){
|
||||
$updateData[] = [
|
||||
'id' => $forSortItems[$i]['id'],
|
||||
'sort' => $item['sort']
|
||||
];
|
||||
}else{
|
||||
$updateData[] = [
|
||||
'id' => $forSortItems[$i]['id'],
|
||||
'sort' => $forSortItems[$i - 1]['sort']
|
||||
];
|
||||
}
|
||||
}
|
||||
$updateData[] = [
|
||||
'id' => $item['id'],
|
||||
'sort' => $forSortItems[$i - 1]['sort']
|
||||
];
|
||||
if(!empty($updateData)){
|
||||
$model = new AuthRule();
|
||||
$model->saveAll($updateData);
|
||||
$sortStr = $sort == 'up' ? '上移' : '下调';
|
||||
Log::write('rule', 'sort', "权限排序,ID:{$id} ,标题:{$item['title']},{$sortStr}了{$num}位");
|
||||
return $this->json();
|
||||
}
|
||||
}
|
||||
return $this->json(4, '无须调整排序!');
|
||||
}
|
||||
return $this->json(1, '非法请求!');
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限删除
|
||||
*/
|
||||
public function del()
|
||||
{
|
||||
if ($this->request->isAjax()) {
|
||||
$id = input('post.id/d');
|
||||
$item = AuthRule::getById($id);
|
||||
if(empty($item)){
|
||||
return $this->json(1, '无此权限');
|
||||
}
|
||||
$children = AuthRule::getListByParentId($id);
|
||||
if(!empty($children)){
|
||||
return $this->json(2, '当前权限有下级权限,不可删除');
|
||||
}
|
||||
AuthRule::destroy($id);
|
||||
AuthGroup::resetGroupRulesCache();
|
||||
Log::write('rule', 'del', "权限删除,ID:{$id}, 标题:{$item['title']}");
|
||||
return $this->json();
|
||||
}
|
||||
return $this->json(1, '非法请求!');
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限修改
|
||||
*/
|
||||
public function edit()
|
||||
{
|
||||
if($this->request->isPost()){
|
||||
$item = input('post.item/a');
|
||||
$id = input('post.id');
|
||||
$rule = AuthRule::getById($id);
|
||||
if(empty($rule)){
|
||||
return $this->json(1, '请选择正确的权限');
|
||||
}
|
||||
$rule2 = AuthRule::getByName($item['name']);
|
||||
if(!empty($rule2) && $rule2['id'] != $id){
|
||||
return $this->json(2, '已存在相同权限['.$item['name'].']');
|
||||
}
|
||||
try {
|
||||
validate(VAuthRule::class)->check($item);
|
||||
AuthRule::updateById($id, $item);
|
||||
AuthGroup::resetGroupRulesCache();
|
||||
Log::write('rule', 'edit', "权限编辑,ID:{$id}, 标题:{$item['title']}");
|
||||
return $this->json();
|
||||
} catch (ValidateException $e) {
|
||||
return $this->json(3, $e->getError());
|
||||
}
|
||||
}
|
||||
$id = input('param.id/d');
|
||||
$rule = AuthRule::getById($id);
|
||||
if(empty($rule)){
|
||||
return $this->json(1,'无此权限信息,请核对之后再操作!');
|
||||
}else{
|
||||
$this->data['item'] = $rule;
|
||||
if($rule['parent_id'] > 0){
|
||||
$parent = AuthRule::getById($rule['parent_id']);
|
||||
$this->data['parent'] = $parent;
|
||||
}
|
||||
return $this->view();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限添加
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
if($this->request->isPost()){
|
||||
$item = input('post.item/a');
|
||||
try {
|
||||
validate(VAuthRule::class)->check($item);
|
||||
$rule = AuthRule::getByName($item['name']);
|
||||
if(!empty($rule)){
|
||||
return $this->json(1, '已存在相同权限');
|
||||
}
|
||||
$rule = AuthRule::create($item);
|
||||
//基本权限的话需要重置所有已有角色权限缓存
|
||||
if ($item['is_base'] > 0) {
|
||||
AuthGroup::resetGroupRulesCache();
|
||||
} else {
|
||||
AuthGroup::resetGroupRulesCache(1);
|
||||
}
|
||||
Log::write('rule', 'add', "权限新增,ID:{$rule->id}, 标题:{$item['title']}");
|
||||
return $this->json();
|
||||
} catch (ValidateException $e) {
|
||||
return $this->json(2, $e->getError());
|
||||
}
|
||||
}
|
||||
$parentId = input('param.parent_id/d',0);
|
||||
if($parentId > 0){
|
||||
$parent = AuthRule::getById($parentId);
|
||||
$this->data['parent'] = $parent;
|
||||
}
|
||||
$this->data['parentId'] = $parentId;
|
||||
return $this->view();
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限列表(全部)
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$list = AuthRule::getListTree();
|
||||
$this->data['items'] = $list;
|
||||
return $this->view();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
namespace app\controller\manager;
|
||||
|
||||
use app\model\{Member, Log};
|
||||
|
||||
class Safe extends Base
|
||||
{
|
||||
/**
|
||||
* 安全设置
|
||||
* @return Safe
|
||||
*/
|
||||
|
||||
public function index()
|
||||
{
|
||||
$auth = session('auth');
|
||||
if($this->request->isPost()){
|
||||
if ($auth) {
|
||||
$authId = $auth['userId'];
|
||||
$oldPassword = trim(input('post.password_old'));
|
||||
$password = trim(input('post.password'));
|
||||
$passwordAgain = trim(input('post.password_again'));
|
||||
$name = trim(input('post.name'));
|
||||
|
||||
$user = Member::getByID($authId);
|
||||
if (empty($user)) {
|
||||
return $this->json(1, '登录失效,请重新登录后再试!');
|
||||
}
|
||||
if (empty($name)) {
|
||||
return $this->json(2, '用户名不能为空!');
|
||||
}
|
||||
$hasUser = Member::getByUserName($name);
|
||||
if (!empty($hasUser) && $hasUser['id'] != $authId) {
|
||||
return $this->json(3, '该用户名已被其他用户使用,请更换!');
|
||||
}
|
||||
if (empty($password) || empty($oldPassword)) {
|
||||
return $this->json(4, '用户密码不能为空!');
|
||||
}
|
||||
if ($password != $passwordAgain) {
|
||||
return $this->json(5, '新密码两次输入不一致!');
|
||||
}
|
||||
if (mb_strlen($password) < 6 || mb_strlen($password) > 30) {
|
||||
return $this->json(6, '新密码长度格式不正确,请输入6~30位密码!');
|
||||
}
|
||||
if ($user['password'] != md5($oldPassword)) {
|
||||
return $this->json(7,'原密码不正确');
|
||||
}
|
||||
$data['password'] = md5($password);
|
||||
Member::updateById($authId, $data);
|
||||
Log::write('safe', 'index', "安全设置,ID:{$authId}, 管理员:{$name}");
|
||||
session('auth', null);
|
||||
//cache('rules_'.$authId, null); //当前看代码,这个是无用代码;先注释掉,如果在使用过程中不会用到,再删除。
|
||||
cache('group_rules_'.$authId, null);
|
||||
cache('rule_names_'.$authId, null);
|
||||
return $this->json(0, '修改成功,请重新登录!');
|
||||
} else {
|
||||
return $this->json(1, '登录失效,请重新登录后再试!');
|
||||
}
|
||||
}else{
|
||||
$this->data['item'] = $auth;
|
||||
return $this->view();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
<?php
|
||||
namespace app\controller\manager;
|
||||
|
||||
use app\model\{Slide as MSlide, System, Log};
|
||||
use app\validate\Slide as VSlide;
|
||||
use think\exception\ValidateException;
|
||||
|
||||
class Slide extends Base
|
||||
{
|
||||
//批量删除
|
||||
public function batchDel()
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
$ids = input('post.ids/a');
|
||||
if(empty($ids) || !is_array($ids)) {
|
||||
return $this->json(2, '参数错误,请核对之后再操作!');
|
||||
}
|
||||
$items = MSlide::getListByIds($ids);
|
||||
if(!empty($items)){
|
||||
$delIds = [];
|
||||
foreach($items as $item){
|
||||
$delIds[] = $item['id'];
|
||||
}
|
||||
MSlide::destroy($delIds);
|
||||
Log::write('link', 'betchDel', '批量删除了友情链接,涉及到的ID为:' . implode(',', $delIds));
|
||||
return $this->json();
|
||||
}else{
|
||||
return $this->json(3, '待删除友情链接为空');
|
||||
}
|
||||
}
|
||||
return $this->json(1, '非法请求!');
|
||||
}
|
||||
//可以删除一个,可以批量删除,TODO 需要调整
|
||||
public function del()
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
$id = input('post.id/d');
|
||||
if(is_numeric($id) && $id > 0) {
|
||||
$item = MSlide::getById($id);
|
||||
if(!empty($item)){
|
||||
MSlide::destroy($id);
|
||||
Log::write('link', 'del', '删除轮播图,ID:' . $id . ',标题:' . $item['title']);
|
||||
return $this->json();
|
||||
}
|
||||
return $this->json(3, '待删除轮播图不存在');
|
||||
}
|
||||
return $this->json(2, '参数错误,请核对之后再操作!');
|
||||
}
|
||||
return $this->json(1, '非法请求!');
|
||||
}
|
||||
//排序
|
||||
public function sort()
|
||||
{
|
||||
if($this->request->isPost()){
|
||||
$id = input('post.id/d');
|
||||
$sort = input('post.sort');
|
||||
$num = input('post.num/d', 1);
|
||||
if($num <= 0){
|
||||
$num = 1;
|
||||
}
|
||||
if(!in_array($sort, ['up', 'down'], true)){
|
||||
return $this->json(2, '参数错误');
|
||||
}
|
||||
$item = MSlide::getById($id);
|
||||
if(empty($item)){
|
||||
return $this->json(3, '无此轮播图');
|
||||
}
|
||||
if($sort == 'up'){
|
||||
$where = "sort < {$item['sort']}";
|
||||
$order = "sort desc";
|
||||
}else{
|
||||
$where = "sort > {$item['sort']}";
|
||||
$order = "sort asc";
|
||||
}
|
||||
$forSortItems = MSlide::getListByWhereAndOrder($where, $order, $num);
|
||||
if(!empty($forSortItems)){
|
||||
$updateData = [];
|
||||
$forSortCount = count($forSortItems);
|
||||
for($i = 0; $i < $forSortCount; $i++){
|
||||
if($i == 0){
|
||||
$updateData[] = [
|
||||
'id' => $forSortItems[$i]['id'],
|
||||
'sort' => $item['sort']
|
||||
];
|
||||
}else{
|
||||
$updateData[] = [
|
||||
'id' => $forSortItems[$i]['id'],
|
||||
'sort' => $forSortItems[$i - 1]['sort']
|
||||
];
|
||||
}
|
||||
}
|
||||
$updateData[] = [
|
||||
'id' => $item['id'],
|
||||
'sort' => $forSortItems[$i - 1]['sort']
|
||||
];
|
||||
if(!empty($updateData)){
|
||||
$model = new MSlide();
|
||||
$model->saveAll($updateData);
|
||||
$sortStr = $sort == 'up' ? '上移' : '下调';
|
||||
Log::write('slide', 'sort', "轮播图排序,ID:{$id} ,标题:{$item['title']},{$sortStr}了{$num}位");
|
||||
return $this->json();
|
||||
}
|
||||
}
|
||||
return $this->json(4, '无须调整排序!');
|
||||
}
|
||||
return $this->json(1, '非法请求!');
|
||||
}
|
||||
|
||||
//编辑
|
||||
public function edit()
|
||||
{
|
||||
if($this->request->isPost()){
|
||||
$item = input('post.item');
|
||||
$img = input('post.img_pc');
|
||||
$imgMobile = input('post.img_mobile');
|
||||
$id = input('post.id/d');
|
||||
|
||||
if(is_numeric($id) && $id > 0) {
|
||||
$slide = MSlide::getById($id);
|
||||
if(empty($slide)) {
|
||||
return $this->json(2, 'id参数错误,没有相关轮播图信息记录!');
|
||||
}
|
||||
if(!empty($imgMobile)){
|
||||
$item['src_mobile'] = $imgMobile;
|
||||
}
|
||||
if(!empty($img)){
|
||||
$item['src'] = $img;
|
||||
}
|
||||
try {
|
||||
validate(VSlide::class)->check($item);
|
||||
MSlide::updateById($id, $item);
|
||||
Log::write('slide', 'edit', "轮播图编辑,ID:{$id} ,标题:{$item['title']}");
|
||||
return $this->json();
|
||||
} catch (ValidateException $e) {
|
||||
return $this->json(3, $e->getError());
|
||||
}
|
||||
}
|
||||
return $this->json(1, '参数错误,请核对之后再操作!');
|
||||
}else{
|
||||
$id = input('param.id/d');
|
||||
$item = MSlide::getById($id);
|
||||
$imgSize = System::getSlideImageSize();
|
||||
$this->data['item'] = $item;
|
||||
$this->data['img_size'] = $imgSize;
|
||||
return $this->view();
|
||||
}
|
||||
}
|
||||
|
||||
//添加
|
||||
public function add()
|
||||
{
|
||||
if($this->request->isPost()){
|
||||
$item = input('post.item');
|
||||
$img = input('post.img_pc');
|
||||
$imgMobile = input('post.img_mobile');
|
||||
|
||||
if (empty($item)) {
|
||||
return $this->json(1, '参数错误,请核对之后再操作!');
|
||||
}
|
||||
if(!empty($img)){
|
||||
$item['src'] = $img;
|
||||
}
|
||||
if(!empty($imgMobile)){
|
||||
$item['src_mobile'] = $imgMobile;
|
||||
}
|
||||
try {
|
||||
validate(VSlide::class)->check($item);
|
||||
$slide = MSlide::create($item);
|
||||
Log::write('slide', 'add', "轮播图新增,ID:{$slide->id} ,标题:{$item['title']}");
|
||||
return $this->json();
|
||||
} catch (ValidateException $e) {
|
||||
return $this->json(2,$e->getError());
|
||||
}
|
||||
}else{
|
||||
$this->data['img_size'] = System::getSlideImageSize();
|
||||
return $this->view();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表
|
||||
* 暂定只有首页轮播图,后期可以根据需求进行分组管理
|
||||
* @return Slide
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$items = MSlide::getList();
|
||||
$this->data['items'] = $items;
|
||||
return $this->view();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
namespace app\controller\manager;
|
||||
|
||||
use app\service\Tool;
|
||||
use app\model\{System as MSystem, Log};
|
||||
use app\service\Image;
|
||||
use think\facade\Cache;
|
||||
|
||||
class System extends Base
|
||||
{
|
||||
/**
|
||||
* 获取当前系统设置
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
$item = input('post.item/a');
|
||||
$img = input('post.img');
|
||||
$system = MSystem::getSystem();
|
||||
if (empty($system)) {
|
||||
if(!empty($img)){
|
||||
$item['mark_img'] = $img;
|
||||
}
|
||||
$system = MSystem::create($item);
|
||||
Log::write('system', 'index', "系统设置,ID:{$system->id}");
|
||||
} else {
|
||||
if (!empty($img)) {
|
||||
Image::delImg($system['mark_img']);
|
||||
$item['mark_img'] = $img;
|
||||
}
|
||||
MSystem::update($item, ['id' => $system['id']]);
|
||||
Log::write('system', 'index', "系统设置,ID:{$system['id']}");
|
||||
}
|
||||
return $this->json();
|
||||
} else {
|
||||
$item = MSystem::getSystem();
|
||||
$positions = Image::getMarkPosition();
|
||||
|
||||
$this->data['item'] = $item;
|
||||
$this->data['positions'] = $positions;
|
||||
return $this->view();
|
||||
}
|
||||
}
|
||||
|
||||
public function other()
|
||||
{
|
||||
return $this->view();
|
||||
}
|
||||
|
||||
public function clearCache()
|
||||
{
|
||||
Cache::clear();
|
||||
$cachePath = app()->getRuntimePath().'cache';
|
||||
$tempPath = app()->getRuntimePath().'temp';
|
||||
Tool::removeByPath($cachePath);
|
||||
Tool::removeByPath($tempPath);
|
||||
clearstatcache();
|
||||
return $this->json();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
<?php
|
||||
namespace app\controller\manager;
|
||||
|
||||
use app\service\Image;
|
||||
use app\model\{System, File};
|
||||
use app\validate\Upload as VUpload;
|
||||
use think\facade\{Filesystem, Config, Lang};
|
||||
use think\Image as TImage;
|
||||
use app\controller\BaseController;
|
||||
|
||||
class Upload extends BaseController
|
||||
{
|
||||
private $isCompress = true;
|
||||
private $validate;
|
||||
private $uploadPath;
|
||||
private $uploadPathIsWritable = 0;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$system = System::getSystem();
|
||||
if (!empty($system)) {
|
||||
$this->isCompress = $system['compress']??true;
|
||||
}
|
||||
$this->validate = new VUpload();
|
||||
$this->uploadPath = Config::get('filesystem.disks.local.url');
|
||||
if(is_writable(app()->getRootPath() . 'public' . $this->uploadPath)){
|
||||
$this->uploadPathIsWritable = 1;
|
||||
}
|
||||
}
|
||||
|
||||
//视频上传
|
||||
public function video()
|
||||
{
|
||||
if(!$this->uploadPathIsWritable){
|
||||
return $this->json(1, '上传文件夹需要写入权限');
|
||||
}
|
||||
$video = request()->file('video');
|
||||
if($this->validate->checkVideo($video)){
|
||||
$src = Filesystem::disk('video')->putFile(date('Ymd'), $video, 'uniqid');
|
||||
$src = $this->uploadPath . '/' . $src;
|
||||
$return['src'] = $src;
|
||||
File::add($video, $src, 'video'); //加入上传文件表
|
||||
return $this->json(0, 'ok', $return);
|
||||
}else{
|
||||
$errorMsg = Lang::get($this->validate->getError());
|
||||
return $this->json(1, $errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
//文件上传(通用)
|
||||
public function file()
|
||||
{
|
||||
$file = request()->file('file');
|
||||
if($this->validate->checkFile($file)){
|
||||
try{
|
||||
if(!$this->uploadPathIsWritable){
|
||||
throw new \Exception('上传文件夹需要写入权限');
|
||||
}
|
||||
$src = Filesystem::putFile(date('Ymd'), $file, 'uniqid');
|
||||
$src = $this->uploadPath . '/' . $src;
|
||||
$return['src'] = $src;
|
||||
$return['name'] = $file->getOriginalName();
|
||||
File::add($file, $src, 'file'); //加入上传文件表
|
||||
} catch (\Exception $e) {
|
||||
return $this->json(1, $e->getMessage());
|
||||
}
|
||||
return $this->json(0,'ok',$return);
|
||||
}else{
|
||||
$errorMsg = Lang::get($this->validate->getError());
|
||||
return $this->json(1, $errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
//图片上传(通用)
|
||||
public function image()
|
||||
{
|
||||
$image = request()->file('image');
|
||||
if($this->validate->checkImage($image)){
|
||||
try{
|
||||
if(!$this->uploadPathIsWritable){
|
||||
throw new \Exception('上传文件夹需要写入权限');
|
||||
}
|
||||
$src = Filesystem::putFile(date('Ymd'), $image, 'uniqid');
|
||||
$src = $this->uploadPath . '/' . $src;
|
||||
$suffix = strtolower($image->getOriginalExtension());
|
||||
if($suffix == 'gif'){
|
||||
$return['thumb_src'] = $src; //TODO获取GIF缩略图
|
||||
}else{
|
||||
$return['thumb_src'] = Image::getThumb($src, 100, 100, TImage::THUMB_SCALING); //上传返回缩略图宽度为100
|
||||
}
|
||||
$return['src'] = $src;
|
||||
if($this->isCompress){
|
||||
Image::resize($src);
|
||||
}
|
||||
File::add($image, $src); //加入上传文件表
|
||||
} catch (\Exception $e) {
|
||||
return $this->json(1, $e->getMessage());
|
||||
}
|
||||
return $this->json(0, 'ok', $return);
|
||||
}else{
|
||||
$errorMsg = Lang::get($this->validate->getError());
|
||||
return $this->json(1, $errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
//富文本编辑器商城图片
|
||||
public function wangImage()
|
||||
{
|
||||
|
||||
$imageArr = request()->file('wang_img'); // 该方式,前端js上传方法中字段名称必须以数组形式传参 如 wang_img[] = 值
|
||||
$errno = 0;
|
||||
$data = [];
|
||||
if(!$this->uploadPathIsWritable){
|
||||
$errno = 1;
|
||||
$data[] = '上传文件夹需要写入权限';
|
||||
}else{
|
||||
foreach ($imageArr as $image) {
|
||||
if($this->validate->checkImage($image)){
|
||||
$src = Filesystem::putFile(date('Ymd'), $image, 'uniqid');
|
||||
$src = $this->uploadPath . '/' . $src;
|
||||
$data[] = $src;
|
||||
if($this->isCompress){
|
||||
Image::resize($src);
|
||||
}
|
||||
File::add($image, $src); //加入上传文件表
|
||||
}else{
|
||||
$errno = 1;
|
||||
$data = [];
|
||||
$data[] = Lang::get($this->validate->getError());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$return['errno'] = $errno;
|
||||
$return['data'] = $data;
|
||||
return json($return);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace app\controller\manager;
|
||||
|
||||
|
||||
/**
|
||||
* 微信公众号配置
|
||||
* Class Wechat
|
||||
* @package app\controller\manager
|
||||
*/
|
||||
class Wechat extends Base
|
||||
{
|
||||
// 公众号设置
|
||||
public function base()
|
||||
{
|
||||
// TODO
|
||||
return 'TODO ...';
|
||||
}
|
||||
|
||||
// 公众号设置
|
||||
public function menu()
|
||||
{
|
||||
// TODO
|
||||
return 'TODO ...';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
// 事件定义文件
|
||||
return [
|
||||
'bind' => [
|
||||
],
|
||||
|
||||
'listen' => [
|
||||
'AppInit' => [],
|
||||
'HttpRun' => [],
|
||||
'HttpEnd' => [],
|
||||
'LogLevel' => [],
|
||||
'LogWrite' => [],
|
||||
],
|
||||
|
||||
'subscribe' => [
|
||||
],
|
||||
];
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
// 全局中间件定义文件
|
||||
return [
|
||||
// 全局请求缓存
|
||||
// \think\middleware\CheckRequestCache::class,
|
||||
// 多语言加载
|
||||
//\think\middleware\LoadLangPack::class,
|
||||
// Session初始化
|
||||
\think\middleware\SessionInit::class,
|
||||
//csrf_token 全局校验
|
||||
\app\middleware\Csrf::class,
|
||||
];
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
namespace app\middleware;
|
||||
|
||||
use Closure;
|
||||
use app\model\AuthRule;
|
||||
use think\facade\Cache;
|
||||
|
||||
class Auth
|
||||
{
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
$auth = session('auth');
|
||||
if(!$auth){
|
||||
return redirect(url('manager.login/index'));
|
||||
}
|
||||
// 角色权限
|
||||
$rules = Cache::get('group_rules_'.$auth['groupId']);
|
||||
$ruleNames = Cache::get('rule_names_'.$auth['groupId']);
|
||||
//如果是超级管理员,不用验证权限,给予所有权限
|
||||
if(empty($rules)){
|
||||
$ruleNames = [];
|
||||
if($auth['groupId'] == 1){
|
||||
$rules = AuthRule::getListTree(0);
|
||||
}else{
|
||||
// 角色权限 + 基本权限
|
||||
$rules = AuthRule::getAuthListByRuleIDs($auth['groupId']);
|
||||
}
|
||||
foreach($rules as &$rule){
|
||||
if(!stripos($rule['name'],'/')){
|
||||
$rule['name'] = $rule['name'].'/index';
|
||||
}
|
||||
$ruleNames[] = strtolower($rule['name']);
|
||||
if(isset($rule['children']) && !empty($rule['children'])){
|
||||
foreach($rule['children'] as &$child){
|
||||
if(!stripos($child['name'],'/')){
|
||||
$child['name'] = $child['name'].'/index';
|
||||
}
|
||||
$ruleNames[] = strtolower($child['name']);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// 对角色赋予权限缓存,角色权限更新时需要同步更新缓存
|
||||
Cache::set('group_rules_'.$auth['groupId'], $rules);
|
||||
Cache::set('rule_names_'.$auth['groupId'], $ruleNames);
|
||||
}
|
||||
if($auth['groupId'] == 1){
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
$controller = strtolower(request()->controller());
|
||||
$controller = str_replace('manager.', '', $controller);
|
||||
$action = request()->action();
|
||||
$name = strtolower($controller.'/'.$action);
|
||||
if(!empty($ruleNames) && in_array($name, $ruleNames, true)){
|
||||
return $next($request);
|
||||
}
|
||||
if(request()->isAjax()){
|
||||
return json(['code' => 1,'msg' => '没有权限']);
|
||||
}else{
|
||||
exit('无操作权限') ;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
namespace app\middleware;
|
||||
|
||||
use Closure;
|
||||
|
||||
/**
|
||||
* CSRF校验
|
||||
*/
|
||||
class Csrf
|
||||
{
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
$csrfToken = session('_token');
|
||||
if (empty($csrfToken)) {
|
||||
$csrfToken = randomStr(1, 32);
|
||||
session('_token', $csrfToken);
|
||||
}
|
||||
$paramToken = input('param._token', '');
|
||||
if (!empty($paramToken)) {
|
||||
if ($paramToken != $csrfToken) {
|
||||
return $this->csrfError($request);
|
||||
}
|
||||
} elseif($request->isPost() || $request->isAjax()) {
|
||||
if (empty($paramToken) || $paramToken != $csrfToken) {
|
||||
return $this->csrfError($request);
|
||||
}
|
||||
//HTTP_REFERER 认证
|
||||
$referer = $_SERVER['HTTP_REFERER'] ?? null;
|
||||
if(!empty($referer)) {
|
||||
$domain = $request->domain();
|
||||
$urlInfo = parse_url($referer);
|
||||
$scheme = $urlInfo['scheme'] ?? '';
|
||||
$requestSrc = '';
|
||||
if (!empty($scheme)) {
|
||||
$requestSrc = $scheme.'://'.($urlInfo['host'] ?? '');
|
||||
}
|
||||
if($domain != $requestSrc) {
|
||||
return $this->csrfError($request);
|
||||
}
|
||||
}
|
||||
}
|
||||
$request->csrfToken = $csrfToken;
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
protected function csrfError($request, $msg = '非法请求, 用户身份认证失败!')
|
||||
{
|
||||
if($request->isAjax()) {
|
||||
return json(['code'=>401, 'msg'=>$msg],200);
|
||||
} else {
|
||||
$referer = $_SERVER['HTTP_REFERER'] ?? null;
|
||||
if (empty($referer)) {
|
||||
$url = '/';
|
||||
} else {
|
||||
$domain = $request->domain();
|
||||
$urlInfo = parse_url($referer);
|
||||
$scheme = $urlInfo['scheme'] ?? '';
|
||||
$requestSrc = '';
|
||||
if (!empty($scheme)) {
|
||||
$requestSrc = $scheme.'://'.($urlInfo['host'] ?? '');
|
||||
}
|
||||
if($domain != $requestSrc) {
|
||||
$url = '/';
|
||||
} else {
|
||||
$url = 'javascript:history.back(-1);';
|
||||
}
|
||||
}
|
||||
$errorData = [
|
||||
'code'=> 401,
|
||||
'msg' => $msg,
|
||||
'data' => [],
|
||||
'wait' => 5,
|
||||
'url' => $url
|
||||
];
|
||||
return view('error/400', $errorData);
|
||||
// 返回401视图 response type has html、json、jsonp、xml、file、view、redirect
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
<?php
|
||||
namespace app\model;
|
||||
|
||||
class Article extends Base
|
||||
{
|
||||
//获取最高访问的文章列表
|
||||
public static function getMostVisited($limit = 5)
|
||||
{
|
||||
if($limit <= 0){
|
||||
$limit = 5;
|
||||
}
|
||||
return self::order('views', 'desc')
|
||||
->limit($limit)
|
||||
->select()
|
||||
->toArray();
|
||||
}
|
||||
//获取栏目下最新记录
|
||||
public static function getLatestByCategory($categoryId, $limit = 5)
|
||||
{
|
||||
if(empty($categoryId)){
|
||||
return [];
|
||||
}
|
||||
if($limit <= 0){
|
||||
$limit = 5;
|
||||
}
|
||||
return self::where('category_id', $categoryId)
|
||||
->order('create_time', 'desc')
|
||||
->limit($limit)
|
||||
->select()
|
||||
->toArray();
|
||||
}
|
||||
//根据文章ID和栏目ID获取下一篇文章
|
||||
public static function getNextArticleByIdAndCategoryId($id, $categoryId)
|
||||
{
|
||||
return self::where('id','<',$id)
|
||||
->where('category_id', $categoryId)
|
||||
->where('status', 1)
|
||||
->order('sort desc')
|
||||
->findOrEmpty()
|
||||
->toArray();
|
||||
}
|
||||
//根据文章ID和栏目ID获取上一篇文章
|
||||
public static function getPrevArticleByIdAndCategoryId($id, $categoryId)
|
||||
{
|
||||
return self::where('id','>',$id)
|
||||
->where('category_id',$categoryId)
|
||||
->where('status', 1)
|
||||
->order('sort asc')
|
||||
->findOrEmpty()
|
||||
->toArray();
|
||||
}
|
||||
//根据栏目ID获取文章列表
|
||||
public static function getListByCategory($categoryId, $limit = 10)
|
||||
{
|
||||
return self::where('category_id', $categoryId)
|
||||
->where('status', 1)
|
||||
->order("sort desc")
|
||||
->limit($limit)
|
||||
->select()
|
||||
->toArray();
|
||||
}
|
||||
//根据栏目ID获取文章分页列表
|
||||
public static function getListPageByCategory($categoryId, $per = 20, $keyword = '')
|
||||
{
|
||||
$where = [
|
||||
['category_id', '=', $categoryId],
|
||||
['status', '=', 1],
|
||||
];
|
||||
$param['category_id'] = $categoryId;
|
||||
if($keyword!=''){
|
||||
$where[] = ['title', 'like', '%'.$keyword.'%'];
|
||||
$param['keyword'] = $keyword;
|
||||
}
|
||||
$paginate = [
|
||||
'list_rows' => $per,
|
||||
'query' => $param
|
||||
];
|
||||
return self::where($where)
|
||||
->order("sort desc")
|
||||
->paginate($paginate,false);
|
||||
}
|
||||
|
||||
public static function onAfterInsert($article)
|
||||
{
|
||||
$article->sort = $article->id;
|
||||
$article->create_time = $article->update_time = time();
|
||||
$auth = session('auth');
|
||||
$article->created = $article->updated = $auth['userName'];
|
||||
$article->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文章列表
|
||||
* @param $categoryId 分类ID
|
||||
* @param int $per 每页数量
|
||||
* @param string $keyword 关键词
|
||||
* @param array $param 文章类型:置顶、热门、推荐 ['top','hot','recommend']
|
||||
* @param int $status 文章状态,-1表示不限制
|
||||
* @return \think\Paginator
|
||||
* @throws \think\db\exception\DbException
|
||||
*/
|
||||
public static function getList($categoryId, $per = 20, $keyword = '', $param = [], $status = -1)
|
||||
{
|
||||
$whereMap = [];
|
||||
$pageParam = [];
|
||||
if(is_numeric($categoryId) && $categoryId > 0) {
|
||||
$whereMap[] = ['category_id', '=', $categoryId];
|
||||
$pageParam['category_id'] = $categoryId;
|
||||
}
|
||||
if(!empty($keyword)){
|
||||
$whereMap[] = ['title', 'like', '%'.$keyword.'%'];
|
||||
$pageParam['keyword'] = $keyword;
|
||||
}
|
||||
if (is_array($param) && count($param) > 0) {
|
||||
$pageParam['param'] = $param;
|
||||
foreach ($param as $vo) {
|
||||
if(in_array($vo, ['top','hot','recommend'], true)) {
|
||||
$whereMap[] = ["{$vo}", '=', 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
$paginate = [
|
||||
'list_rows' => $per,
|
||||
'query' => $pageParam
|
||||
];
|
||||
return self::when(count($whereMap) > 0, function($query) use ($whereMap) {
|
||||
$query->where($whereMap);
|
||||
})
|
||||
->order("sort desc")
|
||||
->paginate($paginate,false);
|
||||
}
|
||||
|
||||
//获取文章涉及到的图片
|
||||
public static function getFilesInUse()
|
||||
{
|
||||
$items = self::select()->toArray();
|
||||
$data = [];
|
||||
foreach($items as $item){
|
||||
$src = trim($item['src']);
|
||||
if(!empty($src)){
|
||||
$key = getKeyByPath($src);
|
||||
$data[$key] = $src;
|
||||
}
|
||||
$imgs = getImageUrlFromText($item['content']);
|
||||
if(!empty($imgs)){
|
||||
$data = array_merge($data, $imgs);
|
||||
}
|
||||
$videos = getVideoUrlFromText($item['content']);
|
||||
if(!empty($videos)){
|
||||
$data = array_merge($data, $videos);
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
namespace app\model;
|
||||
|
||||
use think\facade\Cache;
|
||||
|
||||
class AuthGroup extends Base
|
||||
{
|
||||
public static function updateRules($groupId, $rules)
|
||||
{
|
||||
$rules = implode(',', $rules);
|
||||
$data = ['rules' => $rules];
|
||||
self::updateById($groupId, $data);
|
||||
Member::where('group_id', $groupId)
|
||||
->update($data);
|
||||
}
|
||||
|
||||
//根据ID获取角色列表,ID为1是超级管理员
|
||||
public static function getListById($groupId = 1)
|
||||
{
|
||||
if($groupId < 1){
|
||||
return [];
|
||||
}
|
||||
$group = self::getById($groupId);
|
||||
if(empty($group)){
|
||||
return [];
|
||||
}
|
||||
if($groupId == 1){
|
||||
return self::select()
|
||||
->toArray();
|
||||
}else{
|
||||
return self::where('id','<>','1')
|
||||
->select()
|
||||
->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置角色权限缓存
|
||||
* @param int $groupId 指定重置的角色ID,若不指定则重置所有角色
|
||||
*/
|
||||
public static function resetGroupRulesCache($groupId = 0)
|
||||
{
|
||||
if(is_numeric($groupId) && $groupId > 0) {
|
||||
Cache::set('group_rules_'.$groupId, null);
|
||||
Cache::set('rule_names_'.$groupId, null);
|
||||
} else {
|
||||
$groupIds = self::column('id');
|
||||
foreach ($groupIds as $id){
|
||||
Cache::set('group_rules_'.$id, null);
|
||||
Cache::set('rule_names_'.$id, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,271 @@
|
|||
<?php
|
||||
namespace app\model;
|
||||
|
||||
class AuthRule extends Base
|
||||
{
|
||||
//根据权限IDs获取权限列表
|
||||
public static function getListByRuleIDs($ruleIds)
|
||||
{
|
||||
$items = self::wherein('id',$ruleIds)
|
||||
//->where('status', 1)
|
||||
->order('sort', 'asc')
|
||||
->select()
|
||||
->toArray();
|
||||
return self::getChildren($items);
|
||||
}
|
||||
|
||||
//角色权限 + 基本权限
|
||||
public static function getAuthListByRuleIDs($groupId)
|
||||
{
|
||||
$order = ['sort'=>'asc'];
|
||||
if ($groupId == 1) {
|
||||
$items = self::order($order)->select()->toArray();
|
||||
} else {
|
||||
$group = AuthGroup::getById($groupId);
|
||||
$rulesArr = [];
|
||||
if ($group && !empty($group['rules'])) {
|
||||
$rulesArr = array_filter(explode(',', $group['rules']));
|
||||
}
|
||||
if (count($rulesArr) >0) {
|
||||
$items = self::wherein('id', $rulesArr)->whereOr('is_base', 1)->order($order)->select()->toArray();
|
||||
} else {
|
||||
$items = self::where('is_base', 1)->order($order)->select()->toArray();
|
||||
}
|
||||
}
|
||||
$levelItems = self::getChildren($items);
|
||||
$levelIds = self::getChildrenIds($items);
|
||||
// 独立写入不在连续分层中的权限,连续分层权限可用于菜单显示
|
||||
foreach ($items as $item) {
|
||||
if(!in_array($item['id'], $levelIds)) {
|
||||
$levelItems[] = $item;
|
||||
}
|
||||
}
|
||||
return $levelItems;
|
||||
}
|
||||
|
||||
//根据上级ID获取下级权限列表
|
||||
public static function getListTree($forMenu = 0)
|
||||
{
|
||||
$items = self::getList($forMenu);
|
||||
return self::getChildren($items);
|
||||
}
|
||||
|
||||
//根据上级ID获取下级权限列表
|
||||
public static function getListByParentId($parentId)
|
||||
{
|
||||
return self::where('parent_id', $parentId)
|
||||
->order('sort', 'asc')
|
||||
->select()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $items
|
||||
* @param integer $parent_id
|
||||
* @return array
|
||||
*/
|
||||
public static function getChildren($items, $parentId = 0)
|
||||
{
|
||||
$data = [];
|
||||
foreach($items as $item){
|
||||
if($item['parent_id'] == $parentId){
|
||||
$childern = self::getChildren($items, $item['id']);
|
||||
if(!empty($childern)){
|
||||
$item['hasChildren'] = true;
|
||||
$item['children'] = $childern;
|
||||
}
|
||||
$data[] = $item;
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
public static function getChildrenIds($items, $parentId = 0)
|
||||
{
|
||||
$data = [];
|
||||
foreach($items as $item){
|
||||
if($item['parent_id'] == $parentId){
|
||||
$childrenIds = self::getChildrenIds($items, $item['id']);
|
||||
if(!empty($childrenIds)){
|
||||
$data = array_merge($data, $childrenIds);
|
||||
}
|
||||
$data[] = $item['id'];
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param integer $forMenu 大于0表示获取可显示的权限
|
||||
* @return void
|
||||
*/
|
||||
private static function getList($forMenu = 0)
|
||||
{
|
||||
if($forMenu){
|
||||
return self::where('status', 1)->order('sort', 'asc')->select()->toArray();
|
||||
}else{
|
||||
return self::order('sort', 'asc')->select()->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
//获取可显示权限
|
||||
public static function getTopListForDisplay()
|
||||
{
|
||||
return self::where('status', 1)
|
||||
->where('parent_id', 0)
|
||||
->order('sort', 'asc')
|
||||
->select()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
//根据两个名字获取权限
|
||||
public static function getByTwoName($name, $otherName)
|
||||
{
|
||||
return self::whereOr('name',$name)
|
||||
->whereOr('name',$otherName)
|
||||
->findOrEmpty()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
//根据name获取权限
|
||||
public static function getByName($name)
|
||||
{
|
||||
return self::where('name', $name)
|
||||
->findOrEmpty()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public static function onAfterInsert($rule)
|
||||
{
|
||||
$rule->sort = $rule->id;
|
||||
$rule->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* 该情况适合无限级菜单分组显示 (当前系统仅支持2级权限,因此推荐使用getListTree 方法)
|
||||
* @param int $forMenu 是否只获取可显示菜单权限
|
||||
*/
|
||||
public static function getListTreeSort($forMenu = 0)
|
||||
{
|
||||
$items = self::getList($forMenu);
|
||||
return self::groupSort($items, 0, 1, true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分组排序(不拆分为子集)
|
||||
*
|
||||
* @param $items 数据源
|
||||
* @param integer $pid
|
||||
* @param integer $level
|
||||
* @param bool $clear 是否释放局部变量(外部调用时必须先释放,避免数据被累加;内部不用,需要累加)
|
||||
* @param bool $isArr 传入的$items是否为数组,默认否(数据集)
|
||||
* @param string $levelSpare 分层符
|
||||
* @return void
|
||||
*/
|
||||
public static function groupSort($items,$pid = 0, $level = 1, $clear = true, $isArr = false, $levelSpare = '_')
|
||||
{
|
||||
static $data = [];
|
||||
if ($clear) {
|
||||
$data = [];
|
||||
static $data = [];
|
||||
}
|
||||
if(!empty($levelSpare) && $level > 1) {
|
||||
$levelSpare = str_repeat($levelSpare, $level-1);
|
||||
}
|
||||
if (count($items) > 0) {
|
||||
if ($isArr) {
|
||||
foreach ($items as $key => $item) {
|
||||
if ($item['parent_id'] == $pid) {
|
||||
$item['level'] = $level;
|
||||
$item['title'] = $levelSpare.$item['title'];
|
||||
$data[] = $item;
|
||||
self::groupSort($items, $item['id'], $level+1, false, $isArr);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach ($items as $key => $item) {
|
||||
if ($item->parent_id == $pid) {
|
||||
$item->level = $level;
|
||||
$item->title = $levelSpare.$item->title;
|
||||
$data[] = $item;
|
||||
self::groupSort($items, $item->id, $level+1, false, $isArr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 树形排序 (拆分子集)
|
||||
*
|
||||
* @param Collection $items 数据集(非数组)
|
||||
* @param integer $pid
|
||||
* @param integer $level
|
||||
* @return void
|
||||
*/
|
||||
public static function treeSort($items,$pid = 0, $level = 1)
|
||||
{
|
||||
$data = [];
|
||||
if (count($items) > 0) {
|
||||
foreach ($items as $key => $item) {
|
||||
if ($item->parent_id == $pid) {
|
||||
$item->level = $level;
|
||||
$children = self::treeSort($items, $item->id, $level+1);
|
||||
$item->children = $children;
|
||||
$data[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询用户权限(登陆时更具角色更新为最新权限)
|
||||
* 可显示权限和所有权限 角色权限 + 基本权限
|
||||
*
|
||||
* @param boolean $groupId 角色ID,超级管理员(对应group_id = 1)
|
||||
* @return void
|
||||
*/
|
||||
public static function userRolesList($groupId = 1)
|
||||
{
|
||||
$userRoles = [];
|
||||
$items = null;
|
||||
$order = ['sort'=>'asc'];
|
||||
if ($groupId == 1) {
|
||||
$items = self::order($order)->select();
|
||||
} else {
|
||||
$group = AuthGroup::getById($groupId);
|
||||
$rulesArr = [];
|
||||
if ($group && !empty($group['rules'])) {
|
||||
$rulesArr = array_filter(explode(',', $group['rules']));
|
||||
}
|
||||
if (count($rulesArr) >0) {
|
||||
$items = self::wherein('id', $rulesArr)->whereOr('is_base', 1)->order($order)->select();
|
||||
} else {
|
||||
$items = self::where('is_base', 1)->order($order)->select();
|
||||
}
|
||||
}
|
||||
if (empty($items) || $items->isEmpty()) {
|
||||
return $userRoles;
|
||||
}
|
||||
|
||||
$allRulesId = [];
|
||||
$displayRules = [];
|
||||
$displayRulesId = [];
|
||||
foreach ($items as $key => $item) {
|
||||
$allRulesId[] = $item->id;
|
||||
if ($item->status > 0) {
|
||||
$displayRules[] = $item;
|
||||
$displayRulesId[] = $item->id;
|
||||
}
|
||||
}
|
||||
|
||||
$userRoles['allRules'] = $items;
|
||||
$userRoles['allRulesId'] = $allRulesId;
|
||||
$userRoles['displayRules'] = $displayRules;
|
||||
$userRoles['displayRulesId'] = $displayRulesId;
|
||||
return $userRoles;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
namespace app\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class Base extends Model
|
||||
{
|
||||
protected $autoWriteTimestamp = false;
|
||||
//根据Id列表获取列表
|
||||
public static function getListByIds($ids)
|
||||
{
|
||||
if(count($ids) == 0 || empty($ids)) {
|
||||
return [];
|
||||
}
|
||||
return self::where('id', 'in', $ids)->select()->toArray();
|
||||
}
|
||||
//根据ID获取单条数据
|
||||
public static function getById($id)
|
||||
{
|
||||
if($id <= 0){
|
||||
return [];
|
||||
}
|
||||
return self::where('id', $id)->findOrEmpty()->toArray();
|
||||
}
|
||||
|
||||
//根据ID更新数据
|
||||
public static function updateById($id, $data)
|
||||
{
|
||||
return self::where('id', $id)->update($data);
|
||||
}
|
||||
|
||||
//根据where条件和排序获取记录
|
||||
public static function getListByWhereAndOrder($where, $order, $limit = 1)
|
||||
{
|
||||
return self::where($where)
|
||||
->order($order)
|
||||
->limit($limit)
|
||||
->select()
|
||||
->toArray();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
<?php
|
||||
namespace app\model;
|
||||
|
||||
class Block extends Base
|
||||
{
|
||||
const BLOCK = 1;
|
||||
const IMG = 2;
|
||||
const TEXT = 3;
|
||||
const GROUP = 4;
|
||||
const VIDEO = 5;
|
||||
const CODE = 6;
|
||||
|
||||
//获取类型键值对
|
||||
public static function getTypes()
|
||||
{
|
||||
return [
|
||||
'1' => 'block',
|
||||
'2' => 'img',
|
||||
'3' => 'text',
|
||||
'4' => 'group',
|
||||
'5' => 'video',
|
||||
'6' => 'code'
|
||||
];
|
||||
}
|
||||
|
||||
//根据键值和类目获取数据
|
||||
public static function getByKeyword($keyword, $categoryId)
|
||||
{
|
||||
return self::where('keyword', $keyword)->where('category_id', $categoryId)->findOrEmpty()->toArray();
|
||||
}
|
||||
|
||||
//根据栏目ID获取块列表
|
||||
public static function getByCategoryId($categoryId)
|
||||
{
|
||||
|
||||
if($categoryId <= 0){
|
||||
return [];
|
||||
}
|
||||
$category = Category::getById($categoryId);
|
||||
if(empty($category)){
|
||||
return [];
|
||||
}
|
||||
$items = self::where('category_id', $categoryId)
|
||||
->order('sort asc')
|
||||
->select()
|
||||
->toArray();
|
||||
if(empty($items)){
|
||||
return [];
|
||||
}
|
||||
$data = [];
|
||||
foreach($items as $item){
|
||||
$data[$item['keyword']] = $item;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
public static function onAfterInsert($item)
|
||||
{
|
||||
$item->sort = $item->id;
|
||||
$item->save();
|
||||
}
|
||||
|
||||
//获取在使用中的文件
|
||||
public static function getFilesInUse()
|
||||
{
|
||||
$items = self::whereIn('type', [self::IMG, self::GROUP, self::VIDEO, self::TEXT])
|
||||
->select()
|
||||
->toArray();
|
||||
$data = [];
|
||||
foreach($items as $item){
|
||||
if($item['type'] == self::IMG){
|
||||
$file = trim($item['value']);
|
||||
if(!empty($file)){
|
||||
$key = getKeyByPath($file);
|
||||
$data[$key] = $file;
|
||||
}
|
||||
}elseif($item['type'] == self::GROUP){
|
||||
$val = trim($item['value']);
|
||||
if (!empty($val) && $val != '[]' && $val != 'null') {
|
||||
$files = json_decode($val, true);
|
||||
foreach($files as $file){
|
||||
$src = trim($file['src']);
|
||||
if(!empty($src)){
|
||||
$key = getKeyByPath($src);
|
||||
$data[$key] = $src;
|
||||
}
|
||||
}
|
||||
}
|
||||
}elseif($item['type'] == self::VIDEO){
|
||||
$video = trim($item['value']);
|
||||
if(!empty($video)){
|
||||
$key = getKeyByPath($video);
|
||||
$data[$key] = $video;
|
||||
}
|
||||
$img = trim($item['img']);
|
||||
if(!empty($img)){
|
||||
$key = getKeyByPath($img);
|
||||
$data[$key] = $img;
|
||||
}
|
||||
}elseif($item['type'] == self::TEXT){
|
||||
$imgs = getImageUrlFromText($item['value']);
|
||||
if(!empty($imgs)){
|
||||
$data = array_merge($data, $imgs);
|
||||
}
|
||||
$videos = getVideoUrlFromText($item['value']);
|
||||
if(!empty($videos)){
|
||||
$data = array_merge($data, $videos);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,188 @@
|
|||
<?php
|
||||
namespace app\model;
|
||||
|
||||
class Category extends Base
|
||||
{
|
||||
//获取首页栏目ID
|
||||
public static function getIndex()
|
||||
{
|
||||
return self::where('is_index', 1)->findOrEmpty()->toArray();
|
||||
}
|
||||
//根据上级ID和语言获取下级栏目
|
||||
public static function getChildrenByParentId($parentId)
|
||||
{
|
||||
if($parentId <= 0){
|
||||
return [];
|
||||
}
|
||||
$category = self::getById($parentId);
|
||||
if(empty($category)){
|
||||
return [];
|
||||
}
|
||||
return self::alias('c')
|
||||
->leftJoin('model m', 'c.model_id=m.id')
|
||||
->field('c.*, m.manager, m.template, m.name as modelName')
|
||||
->where('c.parent_id', $parentId)
|
||||
->order('c.sort','asc')
|
||||
->select()
|
||||
->toArray();
|
||||
}
|
||||
//重写方法
|
||||
public static function getById($categoryId)
|
||||
{
|
||||
return self::alias('c')
|
||||
->leftJoin('model m', 'c.model_id = m.id')
|
||||
->where('c.id', $categoryId)
|
||||
->field('c.*, m.template')
|
||||
->findOrEmpty()
|
||||
->toArray();
|
||||
}
|
||||
//查看是否有下级栏目
|
||||
public static function hasChildren($categoryId)
|
||||
{
|
||||
if (is_array($categoryId)) {
|
||||
$count = self::where('parent_id', 'in', $categoryId)->count();
|
||||
} else {
|
||||
$count = self::where(['parent_id'=>$categoryId])->count();
|
||||
}
|
||||
return $count ? true : false;
|
||||
}
|
||||
|
||||
//获取前台菜单
|
||||
public static function getListForFrontMenu()
|
||||
{
|
||||
$items = self::alias('c')
|
||||
->leftJoin('model m','c.model_id=m.id')
|
||||
->field('c.*, m.manager, m.template')
|
||||
->where('c.state', 1)
|
||||
->order('is_index desc, sort asc')
|
||||
->select()
|
||||
->toArray();
|
||||
return self::getCates($items);
|
||||
}
|
||||
/**
|
||||
* 获取栏目
|
||||
* @param bool $limit 是否限制查询
|
||||
* @param array $cates 需要限制查询的栏目IDs
|
||||
*/
|
||||
public static function getList($limit = false, $cates = [])
|
||||
{
|
||||
if ($limit && empty($cates)) {
|
||||
return [];
|
||||
}
|
||||
return self::alias('c')
|
||||
->leftJoin('model m', 'c.model_id=m.id')
|
||||
->field('c.*, m.manager, m.name as modelName')
|
||||
->when($limit, function($query) use($cates) {
|
||||
$query->whereIn('c.id', $cates);
|
||||
})
|
||||
->order('sort','asc')
|
||||
->select()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
//获取栏目分级列表
|
||||
public static function getListTree($isMenu = false)
|
||||
{
|
||||
if ($isMenu) {
|
||||
$items = self::where('c.state', 1) ->order('sort','asc')->select()->toArray();
|
||||
} else {
|
||||
$items = self::order('sort','asc')->select()->toArray();
|
||||
}
|
||||
return self::getCates($items);
|
||||
}
|
||||
|
||||
//获取递归栏目
|
||||
public static function getCates($cates,$parentId=0)
|
||||
{
|
||||
$menus = [];
|
||||
foreach($cates as $cate){
|
||||
if($cate['parent_id'] == $parentId){
|
||||
$children = self::getCates($cates,$cate['id']);
|
||||
if(!empty($children)){
|
||||
$cate['children'] = $children;
|
||||
}
|
||||
$menus[] = $cate;
|
||||
}
|
||||
}
|
||||
return $menus;
|
||||
}
|
||||
|
||||
public static function onAfterInsert($category)
|
||||
{
|
||||
$category->sort = $category->id;
|
||||
$category->save();
|
||||
}
|
||||
|
||||
//递归获取上级栏目名称
|
||||
public static function getCatesCrumbs($currentId = 0)
|
||||
{
|
||||
$crumbs = [];
|
||||
$category = self::getById($currentId);
|
||||
if($category['parent_id'] == 0){
|
||||
$cateCrumbs[] = $category['title'];
|
||||
}else{
|
||||
$categoryIds = explode(',', trim($category['path'], ','));
|
||||
$categories = self::where('id', 'in', $categoryIds)->column('*', 'id');
|
||||
foreach($categoryIds as $id){
|
||||
if(isset($categories[$id])){
|
||||
$crumbs[] = $categories[$id]['title'];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $crumbs;
|
||||
}
|
||||
|
||||
//获取栏目中涉及到的文件
|
||||
public static function getFilesInUse()
|
||||
{
|
||||
$items = self::select()->toArray();
|
||||
$data = [];
|
||||
foreach($items as $item){
|
||||
$src = trim($item['src']);
|
||||
if(!empty($src)){
|
||||
$key = getKeyByPath($src);
|
||||
$data[$key] = $src;
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
//当前分类的最高级分类Id
|
||||
public static function firstGradeById($id)
|
||||
{
|
||||
$res = 0;
|
||||
$item = self::getById($id);
|
||||
if ($item) {
|
||||
$res = $id;
|
||||
if ($item['parent_id'] > 0) {
|
||||
$items = self::select()->toArray();
|
||||
$first = self::getFirstGrade($items, $item['parent_id']);
|
||||
if (!empty($first)) {
|
||||
$res = $first['id'];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
public static function getFirstGrade($items, $parentId)
|
||||
{
|
||||
$data = [];
|
||||
foreach ($items as $key=> $item) {
|
||||
if($item['id'] == $parentId) {
|
||||
if ($item['parent_id'] > 0) {
|
||||
unset($items[$key]);
|
||||
$parent = self::getFirstGrade($items, $item['parent_id']);
|
||||
if (!empty($parent)) {
|
||||
$data = $parent;
|
||||
} else {
|
||||
$data = $item;
|
||||
}
|
||||
} else {
|
||||
$data = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
<?php
|
||||
namespace app\model;
|
||||
|
||||
use think\Image;
|
||||
|
||||
class File extends Base
|
||||
{
|
||||
const IMG = 'img';
|
||||
const VIDEO = 'video';
|
||||
const FILE = 'file';
|
||||
|
||||
//获取文件类型
|
||||
public static function getTypes()
|
||||
{
|
||||
return [
|
||||
'img' => '图片',
|
||||
'video' => '视频',
|
||||
'file' => '文件'
|
||||
];
|
||||
}
|
||||
|
||||
//获取文件列表
|
||||
public static function getList($type = '', $page = 1, $per = 20)
|
||||
{
|
||||
$limit = ($page - 1) * $per;
|
||||
if($type != ''){
|
||||
if(!in_array($type, array_keys(self::getTypes()))){
|
||||
return [];
|
||||
}
|
||||
$items = self::where('type', $type)
|
||||
->order('id desc');
|
||||
}else{
|
||||
$items = self::order('id desc');
|
||||
}
|
||||
$items = $items->limit($limit, $per)->select()->toArray();
|
||||
foreach($items as &$item){
|
||||
$item['sizeStr'] = sizeToStr($item['size']);
|
||||
}
|
||||
return $items;
|
||||
}
|
||||
|
||||
//获取分页列表
|
||||
public static function getListPage($type = '', $per = 20)
|
||||
{
|
||||
if($type != ''){
|
||||
if(!in_array($type, array_keys(self::getTypes()))){
|
||||
return [];
|
||||
}
|
||||
return self::where('type', $type)
|
||||
->order('id desc')
|
||||
->paginate([
|
||||
'list_rows' => $per,
|
||||
'query' => [
|
||||
'type' => $type
|
||||
]
|
||||
], false);
|
||||
}else{
|
||||
return self::order('id desc')
|
||||
->paginate([
|
||||
'list_rows' => $per
|
||||
], false);
|
||||
}
|
||||
}
|
||||
|
||||
//添加,$w_h图片尺寸大小,单位像素,只对type=img时有效
|
||||
public static function add($file, $src, $type = 'img')
|
||||
{
|
||||
$realPath = app()->getRootPath() . ltrim($src,'/');
|
||||
if(is_file($realPath) && $type == 'img'){
|
||||
$img = Image::open($realPath);
|
||||
list($w,$h) = $img->size();
|
||||
$w_h = $w . 'px * ' . $h . 'px';
|
||||
}else{
|
||||
$w_h = '';
|
||||
}
|
||||
return self::create([
|
||||
'type' => $type,
|
||||
'name' => $file->getOriginalName(),
|
||||
'src' => $src,
|
||||
'size' => $file->getSize(),
|
||||
'suffix' => $file->getOriginalExtension(),
|
||||
'mime_type' => $file->getOriginalMime(),
|
||||
'create_time' => time(),
|
||||
'w_h' => $w_h
|
||||
]);
|
||||
}
|
||||
|
||||
//获取所有记录
|
||||
public static function getAll()
|
||||
{
|
||||
return self::select()->toArray();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
namespace app\model;
|
||||
|
||||
class History extends Base
|
||||
{
|
||||
public static function onAfterInsert($item)
|
||||
{
|
||||
$item->sort = $item->id;
|
||||
$item->save();
|
||||
}
|
||||
|
||||
public static function getPaginateList($categoryId, $per = 20, $isSample = false)
|
||||
{
|
||||
$paginate = [
|
||||
'list_rows' => $per
|
||||
];
|
||||
$items = self::where('category_id', $categoryId)->order('sort', 'asc')->paginate($paginate, $isSample);
|
||||
if(!$items->isEmpty()) {
|
||||
$ids = $items->column('id');
|
||||
$infoList = HistoryInfo::getByHistoryIds($ids);
|
||||
foreach ($items as $k => $item) {
|
||||
$items[$k]['info'] = $infoList[$item['id']] ?? [];
|
||||
}
|
||||
}
|
||||
return $items;
|
||||
}
|
||||
|
||||
public static function getByCategoryId($categoryId, $onlyVisible = false)
|
||||
{
|
||||
$items = self::where('category_id', $categoryId)
|
||||
->when($onlyVisible, function($query){
|
||||
$query->where('visible', 1);
|
||||
})
|
||||
->order('sort', 'asc')
|
||||
->select();
|
||||
if(!$items->isEmpty()) {
|
||||
$ids = $items->column('id');
|
||||
$infoList = HistoryInfo::getByHistoryIds($ids, $onlyVisible);
|
||||
foreach ($items as $k => $item) {
|
||||
$items[$k]['info'] = $infoList[$item['id']] ?? [];
|
||||
}
|
||||
}
|
||||
return $items->toArray();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
namespace app\model;
|
||||
|
||||
class HistoryInfo extends Base
|
||||
{
|
||||
public static function onAfterInsert($item)
|
||||
{
|
||||
$item->sort = $item->id;
|
||||
$item->save();
|
||||
}
|
||||
|
||||
public static function getByHistoryIds($historyIds = [], $onlyVisible = false)
|
||||
{
|
||||
if(!is_array($historyIds) || count($historyIds) == 0) {
|
||||
return [];
|
||||
}
|
||||
$list = self::whereIn('history_id', $historyIds)
|
||||
->when($onlyVisible, function ($query) {
|
||||
$query->where('visible', 1);
|
||||
})
|
||||
->order(['history_id'=>'asc','sort'=>'asc'])
|
||||
->select()
|
||||
->toArray();
|
||||
$data = [];
|
||||
foreach ($list as $item) {
|
||||
$item['img_list'] = [];
|
||||
if(!empty($item['imgs'])) {
|
||||
$item['img_list'] = array_filter(explode(',', $item['imgs']));
|
||||
}
|
||||
$data[$item['history_id']][] = $item;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
public static function countByHistoryId($historyId)
|
||||
{
|
||||
return self::where('history_id', $historyId)->count();
|
||||
}
|
||||
|
||||
public static function delByHistoryId($historyId)
|
||||
{
|
||||
return self::where('history_id', $historyId)->delete();
|
||||
}
|
||||
|
||||
public static function getByHistoryId($historyId)
|
||||
{
|
||||
if($historyId <= 0) {
|
||||
return [];
|
||||
}
|
||||
return self::where('history_id', $historyId)->order(['sort'=>'asc'])->select()->toArray();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
namespace app\model;
|
||||
|
||||
class Link extends Base
|
||||
{
|
||||
public static function delByIds($ids)
|
||||
{
|
||||
return self::whereIn('id', $ids)->delete();
|
||||
}
|
||||
|
||||
//获取友情链接
|
||||
public static function getList()
|
||||
{
|
||||
return self::order('sort asc')
|
||||
->select()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public static function onAfterInsert($item)
|
||||
{
|
||||
$item->sort = $item->id;
|
||||
$item->save();
|
||||
}
|
||||
|
||||
//获取友情链接涉及到的文件
|
||||
public static function getFilesInUse()
|
||||
{
|
||||
$items = self::select()->toArray();
|
||||
$data = [];
|
||||
foreach($items as $item){
|
||||
$src = trim($item['src']);
|
||||
if(!empty($src)){
|
||||
$key = getKeyByPath($src);
|
||||
$data[$key] = $src;
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
namespace app\model;
|
||||
|
||||
class Log extends Base
|
||||
{
|
||||
//记录操作日志
|
||||
public static function write($controller, $action, $content)
|
||||
{
|
||||
$auth = session('auth');
|
||||
return self::create([
|
||||
'member_id' => $auth['userId'],
|
||||
'name' => $auth['userName'],
|
||||
'ip' => request()->ip(),
|
||||
'create_time' => time(),
|
||||
'controller' => $controller,
|
||||
'action' => $action,
|
||||
'content' => $content
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
namespace app\model;
|
||||
|
||||
class LoginLog extends Base
|
||||
{
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
namespace app\model;
|
||||
|
||||
class Member extends Base
|
||||
{
|
||||
public static function getList($limit = 40)
|
||||
{
|
||||
$items = self::alias('m')
|
||||
->leftjoin('auth_group g','g.id=m.group_id')
|
||||
->field('m.id,m.username,m.login_time,m.group_id,g.title')
|
||||
->order('m.id', 'asc')
|
||||
->paginate($limit);
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据角色分组返回用户
|
||||
* @param int $groupId 角色分组ID
|
||||
* @param int $limit 每页数量
|
||||
*/
|
||||
public static function getListByGroup($groupId, $limit = 40)
|
||||
{
|
||||
$items = self::alias('m')
|
||||
->leftjoin('auth_group g','g.id=m.group_id')
|
||||
->field('m.id,m.username,m.login_time,m.group_id,g.title')
|
||||
->where('m.group_id', '=', $groupId)
|
||||
->order('m.id', 'asc')
|
||||
->paginate($limit);
|
||||
return $items;
|
||||
}
|
||||
|
||||
//根据用户名获取管理账号
|
||||
public static function getByUserName($username)
|
||||
{
|
||||
return self::where('username', trim($username))
|
||||
->findOrEmpty()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
//根据ID获取管理账户和相关权限
|
||||
public static function getMemberAndRulesByID($memberId)
|
||||
{
|
||||
return self::alias('m')
|
||||
->join('auth_group g', 'm.group_id = g.id', 'LEFT')
|
||||
->field('m.group_id,g.rules')
|
||||
->where('m.id', $memberId)
|
||||
->findOrEmpty()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public static function updateCates($id, $cates)
|
||||
{
|
||||
$cates = implode(',', $cates);
|
||||
$data = ['cates' => $cates];
|
||||
self::updateById($id, $data);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
namespace app\model;
|
||||
|
||||
class Message extends Base
|
||||
{
|
||||
/**
|
||||
* 获取留言列表
|
||||
*/
|
||||
public static function getList($per = 20)
|
||||
{
|
||||
return self::order("create_time desc")
|
||||
->paginate($per);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
namespace app\model;
|
||||
|
||||
class Model extends Base
|
||||
{
|
||||
//获取模型列表
|
||||
public static function getList()
|
||||
{
|
||||
return self::order('sort asc')
|
||||
->select()
|
||||
->toArray();
|
||||
}
|
||||
public static function onAfterInsert($model)
|
||||
{
|
||||
$model->sort = $model->id;
|
||||
$model->save();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
namespace app\model;
|
||||
|
||||
class Slide extends Base
|
||||
{
|
||||
public static function delByIds($ids)
|
||||
{
|
||||
return self::whereIn('id', $ids)->delete();
|
||||
}
|
||||
|
||||
//获取幻灯片列表
|
||||
public static function getList()
|
||||
{
|
||||
return self::order("sort asc")
|
||||
->select()
|
||||
->toArray();
|
||||
}
|
||||
public static function onAfterInsert($slide)
|
||||
{
|
||||
$slide->sort = $slide->id;
|
||||
$slide->save();
|
||||
}
|
||||
|
||||
//获取轮播图涉及到的文件
|
||||
public static function getFilesInUse()
|
||||
{
|
||||
$items = self::select()->toArray();
|
||||
$data = [];
|
||||
foreach($items as $item){
|
||||
$src = trim($item['src']);
|
||||
if(!empty($src)){
|
||||
$key = getKeyByPath($src);
|
||||
$data[$key] = $src;
|
||||
}
|
||||
$mobileSrc = trim($item['src_mobile']);
|
||||
if(!empty($mobileSrc)){
|
||||
$key = getKeyByPath($mobileSrc);
|
||||
$data[$key] = $mobileSrc;
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
namespace app\model;
|
||||
|
||||
class System extends Base
|
||||
{
|
||||
//获取友情链接上传图片尺寸
|
||||
public static function getLinkImageSize()
|
||||
{
|
||||
$system = self::getSystem();
|
||||
if(!empty($system['link_w']) && !empty($system['link_h'])){
|
||||
$linkSize = $system['link_w'] . '像素 X ' . $system['link_h'] . '像素';
|
||||
}else{
|
||||
$linkSize = '';
|
||||
}
|
||||
return $linkSize;
|
||||
}
|
||||
//获取幻灯片上传尺寸
|
||||
public static function getSlideImageSize()
|
||||
{
|
||||
$system = self::getSystem();
|
||||
if(!empty($system['slide_w']) && !empty($system['slide_h'])){
|
||||
$slideSize = $system['slide_w'] . '像素 X ' . $system['slide_h'] . '像素';
|
||||
}else{
|
||||
$slideSize = '';
|
||||
}
|
||||
if(!empty($system['slide_mobile_w']) && !empty($system['slide_mobile_h'])){
|
||||
$slideMobileSize = $system['slide_mobile_w'] . '像素 X ' . $system['slide_mobile_h'] . '像素';
|
||||
}else{
|
||||
$slideMobileSize = '';
|
||||
}
|
||||
return [
|
||||
'slide_size' => $slideSize,
|
||||
'slide_mobile_size' => $slideMobileSize
|
||||
];
|
||||
}
|
||||
//获取文章图片尺寸
|
||||
public static function getArticleImageSize()
|
||||
{
|
||||
$system = self::getSystem();
|
||||
if(!empty($system['article_w']) && !empty($system['article_h'])){
|
||||
$articleSize = $system['article_w'] . '像素 X ' . $system['article_h'] . '像素';
|
||||
}else{
|
||||
$articleSize = '';
|
||||
}
|
||||
return $articleSize;
|
||||
}
|
||||
//获取系统配置信息
|
||||
public static function getSystem()
|
||||
{
|
||||
return self::order('id asc')
|
||||
->findOrEmpty()
|
||||
->toArray();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
namespace app\service;
|
||||
|
||||
use think\file\UploadedFile;
|
||||
|
||||
class File
|
||||
{
|
||||
//上传文件移动到上传文件夹
|
||||
public static function move(UploadedFile $file)
|
||||
{
|
||||
$upload_path = 'uploads/' . date('Ymd');
|
||||
$path = app()->getRootPath() . $upload_path;
|
||||
$filename = uniqid() . '.' . $file->extension();
|
||||
$upload_filename = '/' . $upload_path . '/' . $filename;
|
||||
return [$file->move($path, $filename), $file, $upload_filename];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
<?php
|
||||
namespace app\service;
|
||||
|
||||
use think\Image as TImage;
|
||||
use app\model\System;
|
||||
|
||||
class Image extends File
|
||||
{
|
||||
/**
|
||||
* 对图片进行重置大小,并对宽度大于max宽度的等比缩放为宽度为1920
|
||||
* milo
|
||||
* 2019-10-24修改
|
||||
*/
|
||||
public static function resize($src)
|
||||
{
|
||||
$max = 1920;
|
||||
$realPath = app()->getRootPath() . 'public/' . ltrim($src,'/');
|
||||
if(is_file($realPath)){
|
||||
$img = TImage::open($realPath);
|
||||
list($img_w,$img_h) = $img->size();
|
||||
if($max > 0 && $img_w > $max){
|
||||
$img->thumb($max, $max * ($img_h / $img_w))->save($realPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加水印
|
||||
* milo
|
||||
* 2018-01-17
|
||||
*/
|
||||
public static function mark($src)
|
||||
{
|
||||
$rootPath = app()->getRootPath();
|
||||
if(!empty($src)){
|
||||
$system = System::getSystem();
|
||||
$realPath = $rootPath . 'public/' . ltrim($src, '/');
|
||||
if(is_file($realPath)){
|
||||
if($system['is_mark']){
|
||||
$mark = $rootPath . ltrim($system['mark_img'], '/');
|
||||
if(is_file($mark)){
|
||||
$mark_position = $system['mark_position']??5;
|
||||
$mark_opacity = $system['mark_opacity']??50;
|
||||
$img = TImage::Open($realPath);
|
||||
$img->water($mark,$mark_position,$mark_opacity)->save($realPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//获取水印位置键值对
|
||||
public static function getMarkPosition()
|
||||
{
|
||||
return [
|
||||
"1" => '上左',
|
||||
"2" => '上中',
|
||||
"3" => '上右',
|
||||
"4" => '中左',
|
||||
"5" => '正中',
|
||||
"6" => '中右',
|
||||
"7" => '下左',
|
||||
"8" => '下中',
|
||||
"9" => '下右'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除图片
|
||||
* milo
|
||||
* 2018-01-15
|
||||
*/
|
||||
public static function delImg($src)
|
||||
{
|
||||
if(!empty(trim($src))){
|
||||
$realPath = app()->getRootPath() . 'public/' . ltrim($src, '/');
|
||||
if (file_exists($realPath)) {
|
||||
$info = pathinfo($realPath);
|
||||
$source = $info['dirname'] . DIRECTORY_SEPARATOR . $info['filename'] . '*.' . $info['extension'];
|
||||
foreach(glob($source) as $filename){
|
||||
if(is_file($filename)){
|
||||
unlink($filename);
|
||||
}
|
||||
}
|
||||
clearstatcache();// 清除缓存
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取缩略图
|
||||
* milo
|
||||
* 2019-10-24修改
|
||||
* 避免跨平台出错,目录分隔符全部转换为'/'
|
||||
* app()->getRuntimePath() = app()->getRootPath().'runtime/当前应用模块(api)/'
|
||||
*/
|
||||
public static function getThumb($src,$width=0,$height=0,$type = TImage::THUMB_CENTER)
|
||||
{
|
||||
if(empty($src)){
|
||||
return '';
|
||||
}
|
||||
$rootPath = app()->getRootPath();
|
||||
$realPath = $rootPath . 'public/' . ltrim($src, '/');
|
||||
$realPath = str_replace('\\', '/', $realPath);
|
||||
if(!file_exists($realPath)){
|
||||
return '';
|
||||
}
|
||||
$info = pathinfo($src);
|
||||
if($width <= 0 && $height <= 0){ //高宽都小于或等于0,则返回原图片
|
||||
return $src;
|
||||
}
|
||||
$image = TImage::open($realPath);
|
||||
list($imageWidth, $imageHeight) = $image->size();
|
||||
if($width <= 0){
|
||||
$width = floor($height * ($imageWidth / $imageHeight));
|
||||
}elseif($height <= 0){
|
||||
$height = floor($width * ($imageHeight / $imageWidth));
|
||||
}
|
||||
if($width >= $imageWidth || $height >= $imageHeight){
|
||||
return $src;
|
||||
}
|
||||
$thumbName = $info['dirname']. DIRECTORY_SEPARATOR .$info['filename'].'_'.$width.'_'.$height.'.'.$info['extension'];
|
||||
$realThumbName = $rootPath . 'public/' . ltrim($thumbName, '/');
|
||||
$realThumbName = str_replace('\\', '/', $realThumbName);
|
||||
if(!file_exists($realThumbName)){
|
||||
$image = TImage::open($realPath);
|
||||
$image->thumb($width, $height, $type)->save($realThumbName);
|
||||
}
|
||||
return str_replace('\\', '/', $thumbName);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
namespace app\service;
|
||||
|
||||
class Tool
|
||||
{
|
||||
//删除文件
|
||||
public static function delFile($path,$isImg = 0)
|
||||
{
|
||||
if(!empty(trim($path))){
|
||||
$realPath = app()->getRootPath() . ltrim($path, '/');
|
||||
if (file_exists($realPath)) {
|
||||
$info = pathinfo($realPath);
|
||||
$source = $info['dirname'] . DIRECTORY_SEPARATOR . $info['filename'] . '*.' . $info['extension'];
|
||||
foreach(glob($source) as $filename){
|
||||
if(is_file($filename)){
|
||||
unlink($filename);
|
||||
}
|
||||
}
|
||||
clearstatcache();// 清除缓存
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除目录下的所有文件和子目录
|
||||
* 调用完毕后请用clearstatcache()清理缓存
|
||||
*/
|
||||
public static function removeByPath($path)
|
||||
{
|
||||
if(is_dir($path)) {
|
||||
if($handle = @opendir($path)) {
|
||||
while (($file = readdir($handle)) !== false){
|
||||
if($file != '.' && $file != '..') {
|
||||
$child = $path.'/'.$file;
|
||||
is_dir($child) ? self::removeByPath($child) : @unlink($child);
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir($handle);
|
||||
} elseif (is_file($path)) {
|
||||
@unlink($path);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
namespace app\validate;
|
||||
|
||||
use think\Validate;
|
||||
|
||||
class Article extends Validate
|
||||
{
|
||||
protected $rule = [
|
||||
'title' => 'require',
|
||||
'link' => 'url'
|
||||
];
|
||||
protected $message = [
|
||||
'title.require' => '标题必须',
|
||||
'link.url' => '请填写有效的网址'
|
||||
];
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
namespace app\validate;
|
||||
|
||||
use think\Validate;
|
||||
|
||||
class AuthGroup extends Validate
|
||||
{
|
||||
protected $rule = [
|
||||
'title' => 'require',
|
||||
'status' => 'require|number',
|
||||
];
|
||||
protected $message = [
|
||||
'title.require' => '角色名称不能为空',
|
||||
'status.require' => '角色状态必须设置',
|
||||
'status.number' => '角色状态参数值只能为数字类型',
|
||||
];
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
namespace app\validate;
|
||||
|
||||
use think\Validate;
|
||||
|
||||
class AuthRule extends Validate
|
||||
{
|
||||
protected $rule = [
|
||||
'title' => 'require',
|
||||
'name' => 'require',
|
||||
'status' =>'require|number',
|
||||
];
|
||||
protected $message = [
|
||||
'title.require' => '名称必须',
|
||||
'name.require'=> '标识必须',
|
||||
'status.require'=> '显示状态必须传值',
|
||||
'status.number'=> '显示状态传值只能为数字表示',
|
||||
];
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
namespace app\validate;
|
||||
|
||||
use think\Validate;
|
||||
|
||||
class Block extends Validate
|
||||
{
|
||||
protected $rule = [
|
||||
'title' => 'require',
|
||||
'keyword' => 'require',
|
||||
];
|
||||
protected $message = [
|
||||
'title.require' => '名称必须',
|
||||
'keyword.require' => '键值必须'
|
||||
];
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
namespace app\validate;
|
||||
|
||||
use think\Validate;
|
||||
|
||||
class Category extends Validate
|
||||
{
|
||||
protected $rule = [
|
||||
'title' => 'require',
|
||||
'model_id' => 'require|number|min:1',
|
||||
];
|
||||
protected $message = [
|
||||
'title.require' => '栏目名称必须',
|
||||
'model_id.require' => '栏目模型必须',
|
||||
'model_id.number' => '栏目模型格式要正确',
|
||||
'model_id.min' => '请选择正确的栏目模型'
|
||||
];
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
namespace app\validate;
|
||||
|
||||
use think\Validate;
|
||||
|
||||
class History extends Validate
|
||||
{
|
||||
protected $rule = [
|
||||
'title' => 'require|length:1,60',
|
||||
'visible' => 'require|in:0,1',
|
||||
];
|
||||
protected $message = [
|
||||
'title.require' => '标题不能为空',
|
||||
'name.length' => '标题长度限制为60个字符以内',
|
||||
'visible.require' => '历程状态必须设置',
|
||||
'visible.in' => '状态参数错误',
|
||||
];
|
||||
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
namespace app\validate;
|
||||
|
||||
use think\Validate;
|
||||
|
||||
class HistoryInfo extends Validate
|
||||
{
|
||||
protected $rule = [
|
||||
'title' => 'require|length:1,200',
|
||||
'visible' => 'require|in:0,1',
|
||||
];
|
||||
protected $message = [
|
||||
'title.require' => '标题不能为空',
|
||||
'name.length' => '标题长度限制为200个字符以内',
|
||||
'visible.require' => '历程事例状态必须设置',
|
||||
'visible.in' => '状态参数错误',
|
||||
];
|
||||
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
namespace app\validate;
|
||||
|
||||
use think\Validate;
|
||||
|
||||
class Link extends Validate
|
||||
{
|
||||
protected $rule = [
|
||||
'title' => 'require',
|
||||
'url' => 'url',
|
||||
];
|
||||
protected $message = [
|
||||
'title.require' => '名称必须',
|
||||
'url.url' => '请填写有效的网址,以http://或https://开头'
|
||||
];
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
namespace app\validate;
|
||||
|
||||
use think\Validate;
|
||||
|
||||
class Member extends Validate
|
||||
{
|
||||
protected $rule = [
|
||||
'group_id' => 'require|number',
|
||||
'username' => 'require',
|
||||
];
|
||||
protected $message = [
|
||||
'group_id.require' => '所属角色不能为空!',
|
||||
'group_id.number' => '用户角色信息数据格式不正确!',
|
||||
'username.require' => '用户姓名不能为空!',
|
||||
];
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
namespace app\validate;
|
||||
|
||||
use think\Validate;
|
||||
|
||||
class Model extends Validate
|
||||
{
|
||||
protected $rule = [
|
||||
'name' => 'require',
|
||||
'manager' => 'require',
|
||||
];
|
||||
protected $message = [
|
||||
'name.require' => '名称必须',
|
||||
'manager.require' => '后台管理必须'
|
||||
];
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
namespace app\validate;
|
||||
|
||||
use think\Validate;
|
||||
|
||||
class Slide extends Validate
|
||||
{
|
||||
protected $rule = [
|
||||
'title' => 'require',
|
||||
'url' => 'url',
|
||||
];
|
||||
protected $message = [
|
||||
'title.require' => '标题必须',
|
||||
'url.url' => '请填写有效的网址,以http://或https://开头'
|
||||
];
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
namespace app\validate;
|
||||
|
||||
use think\Validate;
|
||||
use app\model\System;
|
||||
use think\Lang;
|
||||
|
||||
class Upload extends Validate
|
||||
{
|
||||
protected $rule = [];
|
||||
protected $message = [];
|
||||
protected $system;
|
||||
public function __construct()
|
||||
{
|
||||
$this->system = System::getSystem();
|
||||
$this->lang = new Lang;
|
||||
}
|
||||
|
||||
//验证图片上传
|
||||
public function checkImage($image)
|
||||
{
|
||||
$ext = str_replace(',', ',', $this->system['img_type']);
|
||||
$size = $this->system['img_size'] * 1024 * 1024;
|
||||
$this->rule = [
|
||||
'image' => [
|
||||
'fileExt' => $ext,
|
||||
'fileSize' => (int)$size
|
||||
]
|
||||
];
|
||||
return $this->check(['image' => $image]);
|
||||
}
|
||||
|
||||
//验证视频上传
|
||||
public function checkVideo($video)
|
||||
{
|
||||
$ext = str_replace(',', ',', $this->system['video_type']);
|
||||
$size = $this->system['video_size'] * 1024 * 1024;
|
||||
$this->rule = [
|
||||
'video' => [
|
||||
'fileExt' => $ext,
|
||||
'fileSize' => (int)$size
|
||||
]
|
||||
];
|
||||
return $this->check(['video' => $video]);
|
||||
}
|
||||
|
||||
//验证文件上传
|
||||
public function checkFile($file)
|
||||
{
|
||||
$ext = str_replace(',', ',', $this->system['file_type']);
|
||||
$size = $this->system['file_size'] * 1024 * 1024;
|
||||
$this->rule = [
|
||||
'file' => [
|
||||
'fileExt' => $ext,
|
||||
'fileSize' => (int)$size
|
||||
]
|
||||
];
|
||||
return $this->check(['file' => $file]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
namespace app\widget;
|
||||
|
||||
use app\model\Category;
|
||||
use think\facade\View;
|
||||
|
||||
class Crumbs
|
||||
{
|
||||
public function index($categoryId)
|
||||
{
|
||||
$data = [
|
||||
'crumbs' => Category::getCatesCrumbs($categoryId)
|
||||
];
|
||||
return View::assign($data)->fetch('public/crumbs');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
namespace app\widget;
|
||||
|
||||
use think\facade\{View, Cache};
|
||||
use app\model\Category;
|
||||
|
||||
class Menu
|
||||
{
|
||||
public function index($categoryId)
|
||||
{
|
||||
$menus = Cache::get('rules');
|
||||
if(empty($menus)){
|
||||
$menus = Category::getListForFrontMenu();
|
||||
}
|
||||
$currentFirstId = 0;
|
||||
if (is_numeric($categoryId) && $categoryId > 0) {
|
||||
$currentFirstId = Category::firstGradeById($categoryId);
|
||||
}
|
||||
$data = [
|
||||
'categoryId' => $categoryId,
|
||||
'menus' => $menus,
|
||||
];
|
||||
return View::assign($data)->fetch('public/menu');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
namespace app\widget;
|
||||
|
||||
use think\facade\View;
|
||||
use app\model\Slide as WSlide;
|
||||
|
||||
class Slide
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$data = [
|
||||
'slides' => WSlide::getList(),
|
||||
];
|
||||
return View::assign($data)->fetch('public/slide');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
namespace app\widget\manager;
|
||||
|
||||
use app\model\{Category, AuthRule};
|
||||
use think\facade\View;
|
||||
|
||||
class Crumbs
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$request = request();
|
||||
$controller = strtolower($request->controller());
|
||||
$action = strtolower($request->action());
|
||||
$controller = str_replace('manager.', '', $controller);
|
||||
$name = $controller . '/' . $action;
|
||||
if($action == 'index'){
|
||||
$rule = AuthRule::getByTwoName($name, $controller);
|
||||
}else{
|
||||
$rule = AuthRule::getByName($name);
|
||||
}
|
||||
$parent = [];
|
||||
if(!empty($rule) && $rule['parent_id']){
|
||||
$parent = AuthRule::getById($rule['parent_id']);
|
||||
}
|
||||
$cateCrumbs = [];
|
||||
$isContent = false;
|
||||
if($controller == 'content') {
|
||||
$isContent = true;
|
||||
$categoryId = $request->param('category_id', 0);
|
||||
if (is_numeric($categoryId) && $categoryId > 0) {
|
||||
$cateCrumbs = Category::getCatesCrumbs($categoryId);
|
||||
}
|
||||
}
|
||||
|
||||
$data = [
|
||||
'rule' => $rule,
|
||||
'parent' => $parent,
|
||||
'isContent' => $isContent,
|
||||
'cateCrumbs' => $cateCrumbs
|
||||
];
|
||||
return View::assign($data)->fetch('manager/widget/crumbs');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
namespace app\widget\manager;
|
||||
|
||||
use think\facade\{View, Cache};
|
||||
use app\model\Category;
|
||||
|
||||
class Menu
|
||||
{
|
||||
/**
|
||||
* 左侧菜单
|
||||
* milo
|
||||
* 2018-01-06
|
||||
*/
|
||||
public function left($categoryId = 0)
|
||||
{
|
||||
$auth = session('auth');
|
||||
$authCates = array_filter(explode(',', $auth['cates']));
|
||||
$rules = Cache::get('group_rules_'.$auth['groupId']);
|
||||
$menuRules = $this->getMenuRules($rules);
|
||||
|
||||
$current = strtolower(request()->controller());
|
||||
$current = str_replace('manager.', '', $current);
|
||||
$currentAction = strtolower($current.'/'.request()->action());
|
||||
// message 留言管理是否集成在内容管理中,后期开发中根据情况调整
|
||||
if(in_array($current, ['article', 'product', 'page'], true)){
|
||||
$current = 'content';
|
||||
}
|
||||
if($auth['groupId'] == 1) {
|
||||
$menus = $this->getMenus(Category::getList(false));
|
||||
} else {
|
||||
$menus = $this->getMenus(Category::getList(true, $authCates));
|
||||
}
|
||||
$data = [
|
||||
'rules' => $menuRules,
|
||||
'categoryId' => $categoryId,
|
||||
'menus' => $menus,
|
||||
'current' => $current,
|
||||
'currentAction' => $currentAction
|
||||
];
|
||||
return View::assign($data)->fetch('manager/widget/left');
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤出权限菜单
|
||||
* @param $rules
|
||||
* @return array
|
||||
*/
|
||||
private function getMenuRules($rules)
|
||||
{
|
||||
$menuRules = [];
|
||||
if (!empty($rules)) {
|
||||
foreach ($rules as $rule) {
|
||||
$hasChildren = $rule['hasChildren'] ?? false;
|
||||
if ($hasChildren) {
|
||||
$rule['children'] = $this->getMenuRules($rule['children']);
|
||||
if(count($rule['children']) > 0) {
|
||||
$rule['status'] = 1;
|
||||
}
|
||||
}
|
||||
if($rule['status'] > 0) {
|
||||
$menuRules[] = $rule;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $menuRules;
|
||||
}
|
||||
|
||||
/**
|
||||
* 内容栏目菜单
|
||||
* @param $cates
|
||||
* @param int $parent_id
|
||||
* @return array
|
||||
*/
|
||||
private function getMenus($cates,$parentId=0)
|
||||
{
|
||||
$menus = [];
|
||||
foreach($cates as $cate){
|
||||
if($cate['parent_id'] == $parentId && $cate['state'] == 1){
|
||||
$children = $this->getMenus($cates,$cate['id']);
|
||||
if(!empty($children)){
|
||||
$cate['children'] = $children;
|
||||
}
|
||||
if(!empty($cate['children']) || !empty($cate['manager'])){
|
||||
$menus[] = $cate;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $menus;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
namespace app\widget\manager;
|
||||
|
||||
use think\facade\View;
|
||||
|
||||
class Upload
|
||||
{
|
||||
//视频
|
||||
public function video($src = '', $append = '')
|
||||
{
|
||||
$data = [
|
||||
'src' => $src,
|
||||
'append' => $append
|
||||
];
|
||||
return View::assign($data)->fetch('manager/widget/video');
|
||||
}
|
||||
|
||||
//图片(layui自带上传控件),若同一页面内徐亚加载多层本上传控件则需要传不同的$append来区分控件ID
|
||||
public function image($src = '', $append = '', $imgSize = 0, $thumb = 0)
|
||||
{
|
||||
$data = [
|
||||
'src' => $src,
|
||||
'append' => $append,
|
||||
'imgSize' => $imgSize,
|
||||
'thumb' => $thumb,
|
||||
];
|
||||
return View::assign($data)->fetch('manager/widget/image');
|
||||
}
|
||||
|
||||
//上传文件,目前在文章中添加附件
|
||||
public function files($files = [], $num = 10, $append = '')
|
||||
{
|
||||
if(!empty($files) && $files == 'null') {
|
||||
$files = [];
|
||||
}
|
||||
$data = [
|
||||
'files' => $files,
|
||||
'append' => $append,
|
||||
'num' => $num,
|
||||
];
|
||||
return View::assign($data)->fetch('manager/widget/files');
|
||||
}
|
||||
|
||||
/**
|
||||
* 水印图片上传
|
||||
* milo
|
||||
* 2018-01-13
|
||||
*/
|
||||
public function mark($src = '')
|
||||
{
|
||||
return View::assign(['src' => $src])->fetch('manager/widget/mark');
|
||||
}
|
||||
|
||||
/**
|
||||
* layui组图上传
|
||||
* milo
|
||||
*/
|
||||
public function multi($imgs = [], $num = 10, $append = '', $imgSize = '')
|
||||
{
|
||||
if(!empty($imgs) && $imgs == 'null') {
|
||||
$imgs = [];
|
||||
}
|
||||
$data = [
|
||||
'imgs' => $imgs,
|
||||
'append' => $append,
|
||||
'imgSize' => $imgSize,
|
||||
'num' => $num
|
||||
];
|
||||
return View::assign($data)->fetch('manager/widget/multi');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* php think build 自动生成应用的目录结构的定义示例
|
||||
*/
|
||||
return [
|
||||
// 需要自动创建的文件
|
||||
'__file__' => [],
|
||||
// 需要自动创建的目录
|
||||
'__dir__' => ['controller', 'model', 'view'],
|
||||
// 需要自动创建的控制器
|
||||
'controller' => ['Index'],
|
||||
// 需要自动创建的模型
|
||||
'model' => ['User'],
|
||||
// 需要自动创建的模板
|
||||
'view' => ['index/index'],
|
||||
];
|
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"name": "topthink/think",
|
||||
"description": "the new thinkphp framework",
|
||||
"type": "project",
|
||||
"keywords": [
|
||||
"framework",
|
||||
"thinkphp",
|
||||
"ORM"
|
||||
],
|
||||
"homepage": "http://thinkphp.cn/",
|
||||
"license": "Apache-2.0",
|
||||
"authors": [
|
||||
{
|
||||
"name": "liu21st",
|
||||
"email": "liu21st@gmail.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.1.0",
|
||||
"topthink/framework": "^6.0.0",
|
||||
"topthink/think-orm": "^2.0",
|
||||
"topthink/think-view": "^1.0",
|
||||
"topthink/think-image": "^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/var-dumper": "^4.2",
|
||||
"topthink/think-trace":"^1.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"app\\": "app"
|
||||
},
|
||||
"psr-0": {
|
||||
"": "extend/"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"preferred-install": "dist"
|
||||
},
|
||||
"scripts": {
|
||||
"post-autoload-dump": [
|
||||
"@php think service:discover",
|
||||
"@php think vendor:publish"
|
||||
]
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
use think\facade\Env;
|
||||
|
||||
return [
|
||||
// 应用地址
|
||||
'app_host' => Env::get('app.host', ''),
|
||||
// 应用的命名空间
|
||||
'app_namespace' => '',
|
||||
// 是否启用路由
|
||||
'with_route' => true,
|
||||
// 是否启用事件
|
||||
'with_event' => true,
|
||||
// 自动多应用模式
|
||||
'auto_multi_app' => true,
|
||||
// 应用映射(自动多应用模式有效)
|
||||
'app_map' => [],
|
||||
// 域名绑定(自动多应用模式有效)
|
||||
'domain_bind' => [],
|
||||
// 禁止URL访问的应用列表(自动多应用模式有效)
|
||||
'deny_app_list' => [],
|
||||
// 默认应用
|
||||
'default_app' => 'index',
|
||||
// 默认时区
|
||||
'default_timezone' => 'Asia/Shanghai',
|
||||
|
||||
// 异常页面的模板文件
|
||||
'exception_tmpl' => app()->getThinkPath() . 'tpl/think_exception.tpl',
|
||||
|
||||
/*
|
||||
'http_exception_template' => [
|
||||
400 => '',
|
||||
500 => app()->getThinkPath() . 'tpl/think_exception.tpl',
|
||||
],
|
||||
*/
|
||||
|
||||
// 错误显示信息,非调试模式有效
|
||||
'error_message' => '页面错误!请稍后再试~',
|
||||
// 显示错误信息
|
||||
'show_error_msg' => true,
|
||||
];
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
use think\facade\Env;
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | 缓存设置
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
return [
|
||||
// 默认缓存驱动
|
||||
'default' => Env::get('cache.driver', 'file'),
|
||||
|
||||
// 缓存连接方式配置
|
||||
'stores' => [
|
||||
'file' => [
|
||||
// 驱动方式
|
||||
'type' => 'File',
|
||||
// 缓存保存目录
|
||||
'path' => '',
|
||||
// 缓存前缀
|
||||
'prefix' => '',
|
||||
// 缓存有效期 0表示永久缓存
|
||||
'expire' => 0,
|
||||
// 缓存标签前缀
|
||||
'tag_prefix' => 'tag:',
|
||||
// 序列化机制 例如 ['serialize', 'unserialize']
|
||||
'serialize' => [],
|
||||
],
|
||||
// 更多的缓存连接
|
||||
],
|
||||
];
|
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | 控制台配置
|
||||
// +----------------------------------------------------------------------
|
||||
return [
|
||||
// 指令定义
|
||||
'commands' => [
|
||||
],
|
||||
];
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | Cookie设置
|
||||
// +----------------------------------------------------------------------
|
||||
return [
|
||||
// cookie 保存时间
|
||||
'expire' => 0,
|
||||
// cookie 保存路径
|
||||
'path' => '/',
|
||||
// cookie 有效域名
|
||||
'domain' => '',
|
||||
// cookie 启用安全传输
|
||||
'secure' => false,
|
||||
// httponly设置
|
||||
'httponly' => false,
|
||||
// 是否使用 setcookie
|
||||
'setcookie' => true,
|
||||
];
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
use think\facade\Env;
|
||||
|
||||
return [
|
||||
// 默认使用的数据库连接配置
|
||||
'default' => Env::get('database.driver', 'mysql'),
|
||||
|
||||
// 自定义时间查询规则
|
||||
'time_query_rule' => [],
|
||||
|
||||
// 自动写入时间戳字段
|
||||
// true为自动识别类型 false关闭
|
||||
// 字符串则明确指定时间字段类型 支持 int timestamp datetime date
|
||||
'auto_timestamp' => true,
|
||||
|
||||
// 时间字段取出后的默认时间格式
|
||||
'datetime_format' => 'Y-m-d H:i:s',
|
||||
|
||||
// 数据库连接配置信息
|
||||
'connections' => [
|
||||
'mysql' => [
|
||||
// 数据库类型
|
||||
'type' => Env::get('database.type', 'mysql'),
|
||||
// 服务器地址
|
||||
'hostname' => Env::get('database.hostname', '127.0.0.1'),
|
||||
// 数据库名
|
||||
'database' => Env::get('database.database', ''),
|
||||
// 用户名
|
||||
'username' => Env::get('database.username', 'root'),
|
||||
// 密码
|
||||
'password' => Env::get('database.password', ''),
|
||||
// 端口
|
||||
'hostport' => Env::get('database.hostport', '3306'),
|
||||
// 数据库连接参数
|
||||
'params' => [],
|
||||
// 数据库编码默认采用utf8
|
||||
'charset' => Env::get('database.charset', 'utf8'),
|
||||
// 数据库表前缀
|
||||
'prefix' => Env::get('database.prefix', ''),
|
||||
|
||||
// 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
|
||||
'deploy' => 0,
|
||||
// 数据库读写是否分离 主从式有效
|
||||
'rw_separate' => false,
|
||||
// 读写分离后 主服务器数量
|
||||
'master_num' => 1,
|
||||
// 指定从服务器序号
|
||||
'slave_no' => '',
|
||||
// 是否严格检查字段是否存在
|
||||
'fields_strict' => true,
|
||||
// 是否需要断线重连
|
||||
'break_reconnect' => false,
|
||||
// 监听SQL
|
||||
'trigger_sql' => true,
|
||||
// 开启字段缓存
|
||||
'fields_cache' => false,
|
||||
// 字段缓存路径
|
||||
'schema_cache_path' => app()->getRuntimePath() . 'schema' . DIRECTORY_SEPARATOR,
|
||||
],
|
||||
|
||||
// 更多的数据库配置信息
|
||||
],
|
||||
];
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
use think\facade\Env;
|
||||
|
||||
return [
|
||||
// 默认磁盘
|
||||
'default' => Env::get('filesystem.driver', 'local'),
|
||||
// 磁盘列表
|
||||
'disks' => [
|
||||
'local' => [
|
||||
// 磁盘类型
|
||||
'type' => 'local',
|
||||
// 磁盘路径
|
||||
'root' => app()->getRootPath() . 'public/storage',
|
||||
// 磁盘路径对应的外部URL路径
|
||||
'url' => '/storage',
|
||||
// 可见性
|
||||
'visibility' => 'public',
|
||||
],
|
||||
'video' => [
|
||||
// 磁盘类型
|
||||
'type' => 'local',
|
||||
// 磁盘路径
|
||||
'root' => app()->getRootPath() . 'public/storage/videos',
|
||||
// 磁盘路径对应的外部URL路径
|
||||
'url' => '/storage/videos',
|
||||
// 可见性
|
||||
'visibility' => 'public',
|
||||
],
|
||||
'backup' => [
|
||||
// 磁盘类型
|
||||
'type' => 'local',
|
||||
// 磁盘路径
|
||||
'root' => app()->getRootPath() . 'public/storage/backup',
|
||||
// 磁盘路径对应的外部URL路径
|
||||
'url' => '/storage/backup',
|
||||
// 可见性
|
||||
'visibility' => 'public',
|
||||
],
|
||||
// 更多的磁盘配置信息
|
||||
],
|
||||
];
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | 多语言设置
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
use think\facade\Env;
|
||||
|
||||
return [
|
||||
// 默认语言
|
||||
'default_lang' => Env::get('lang.default_lang', 'zh-cn'),
|
||||
// 允许的语言列表
|
||||
'allow_lang_list' => ['zh-cn'],
|
||||
// 多语言自动侦测变量名
|
||||
'detect_var' => 'lang',
|
||||
// 是否使用Cookie记录
|
||||
'use_cookie' => true,
|
||||
// 多语言cookie变量
|
||||
'cookie_var' => 'think_lang',
|
||||
// 扩展语言包
|
||||
'extend_list' => [],
|
||||
// Accept-Language转义为对应语言包名称
|
||||
'accept_language' => [
|
||||
'zh-hans-cn' => 'zh-cn',
|
||||
],
|
||||
// 是否支持语言分组
|
||||
'allow_group' => false,
|
||||
];
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
use think\facade\Env;
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | 日志设置
|
||||
// +----------------------------------------------------------------------
|
||||
return [
|
||||
// 默认日志记录通道
|
||||
'default' => Env::get('log.channel', 'file'),
|
||||
// 日志记录级别
|
||||
'level' => [],
|
||||
// 日志类型记录的通道 ['error'=>'email',...]
|
||||
'type_channel' => [],
|
||||
// 关闭全局日志写入
|
||||
'close' => false,
|
||||
// 全局日志处理 支持闭包
|
||||
'processor' => null,
|
||||
|
||||
// 日志通道列表
|
||||
'channels' => [
|
||||
'file' => [
|
||||
// 日志记录方式
|
||||
'type' => 'File',
|
||||
// 日志保存目录
|
||||
'path' => '',
|
||||
// 单文件日志写入
|
||||
'single' => false,
|
||||
// 独立日志级别
|
||||
'apart_level' => [],
|
||||
// 最大日志文件数量
|
||||
'max_files' => 0,
|
||||
// 使用JSON格式记录
|
||||
'json' => false,
|
||||
// 日志处理
|
||||
'processor' => null,
|
||||
// 关闭通道日志写入
|
||||
'close' => false,
|
||||
// 日志输出格式化
|
||||
'format' => '[%s][%s] %s',
|
||||
// 是否实时写入
|
||||
'realtime_write' => false,
|
||||
],
|
||||
// 其它日志通道配置
|
||||
],
|
||||
|
||||
];
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
// 中间件配置
|
||||
return [
|
||||
// 别名或分组
|
||||
'alias' => [
|
||||
'auth' => app\middleware\Auth::class,
|
||||
'csrf' => app\middleware\Csrf::class,
|
||||
],
|
||||
// 优先级设置,此数组中的中间件会按照数组中的顺序优先执行
|
||||
'priority' => [],
|
||||
];
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | 路由设置
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
return [
|
||||
// pathinfo分隔符
|
||||
'pathinfo_depr' => '/',
|
||||
// URL伪静态后缀
|
||||
'url_html_suffix' => 'html',
|
||||
// URL普通方式参数 用于自动生成
|
||||
'url_common_param' => true,
|
||||
// 是否开启路由延迟解析
|
||||
'url_lazy_route' => false,
|
||||
// 是否强制使用路由
|
||||
'url_route_must' => false,
|
||||
// 合并路由规则
|
||||
'route_rule_merge' => false,
|
||||
// 路由是否完全匹配
|
||||
'route_complete_match' => false,
|
||||
// 是否开启路由缓存
|
||||
'route_check_cache' => false,
|
||||
// 路由缓存连接参数
|
||||
'route_cache_option' => [],
|
||||
// 路由缓存Key
|
||||
'route_check_cache_key' => '',
|
||||
// 访问控制器层名称
|
||||
'controller_layer' => 'controller',
|
||||
// 空控制器名
|
||||
'empty_controller' => 'Error',
|
||||
// 是否使用控制器后缀
|
||||
'controller_suffix' => false,
|
||||
// 默认的路由变量规则
|
||||
'default_route_pattern' => '[\w\.]+',
|
||||
// 是否开启请求缓存 true自动缓存 支持设置请求缓存规则
|
||||
'request_cache' => false,
|
||||
// 请求缓存有效期
|
||||
'request_cache_expire' => null,
|
||||
// 全局请求缓存排除规则
|
||||
'request_cache_except' => [],
|
||||
// 默认控制器名
|
||||
'default_controller' => 'Index',
|
||||
// 默认操作名
|
||||
'default_action' => 'index',
|
||||
// 操作方法后缀
|
||||
'action_suffix' => '',
|
||||
// 默认JSONP格式返回的处理方法
|
||||
'default_jsonp_handler' => 'jsonpReturn',
|
||||
// 默认JSONP处理方法
|
||||
'var_jsonp_handler' => 'callback',
|
||||
];
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | 会话设置
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
return [
|
||||
// session name
|
||||
'name' => 'PHPSESSID',
|
||||
// SESSION_ID的提交变量,解决flash上传跨域
|
||||
'var_session_id' => '',
|
||||
// 驱动方式 支持file cache
|
||||
'type' => 'file',
|
||||
// 存储连接标识 当type使用cache的时候有效
|
||||
'store' => null,
|
||||
// 过期时间(秒)
|
||||
'expire' => 3600 * 2,
|
||||
// 前缀
|
||||
'prefix' => '',
|
||||
];
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | Trace设置 开启调试模式后有效
|
||||
// +----------------------------------------------------------------------
|
||||
return [
|
||||
// 内置Html和Console两种方式 支持扩展
|
||||
'type' => 'Html',
|
||||
// 读取的日志通道名
|
||||
'channel' => '',
|
||||
];
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | 模板设置
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
return [
|
||||
// 模板引擎类型使用Think
|
||||
'type' => 'Think',
|
||||
// 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 3 保持操作方法
|
||||
'auto_rule' => 1,
|
||||
// 模板目录名
|
||||
'view_dir_name' => 'view',
|
||||
// 模板后缀
|
||||
'view_suffix' => 'html',
|
||||
// 模板文件名分隔符
|
||||
'view_depr' => DIRECTORY_SEPARATOR,
|
||||
// 模板引擎普通标签开始标记
|
||||
'tpl_begin' => '{',
|
||||
// 模板引擎普通标签结束标记
|
||||
'tpl_end' => '}',
|
||||
// 标签库标签开始标记
|
||||
'taglib_begin' => '{',
|
||||
// 标签库标签结束标记
|
||||
'taglib_end' => '}',
|
||||
'tpl_replace_string' => [
|
||||
'__STATIC__' => '/static',
|
||||
'__MANAGER__' => '/static/manager',
|
||||
'__COMMON__' => '/static/common',
|
||||
'__JS__' => '/static/js',
|
||||
'__CSS__' => '/static/css',
|
||||
'__IMG__' => '/static/image'
|
||||
],
|
||||
];
|
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "cms",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "cms",
|
||||
"version": "1.0.0",
|
||||
"description": "本CMS基于ThinkPHP 6.0开发",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://gitee.com/dxtc/cms.git"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
// [ 应用入口文件 ]
|
||||
namespace think;
|
||||
|
||||
header("Access-Control-Allow-Origin:*");
|
||||
header('Access-Control-Allow-Methods:POST');
|
||||
header('Access-Control-Allow-Headers:x-requested-with, content-type');
|
||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
// 执行HTTP应用并响应
|
||||
$http = (new App())->http;
|
||||
$response = $http->run();
|
||||
$response->send();
|
||||
$http->end($response);
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,349 @@
|
|||
/* BASICS */
|
||||
|
||||
.CodeMirror {
|
||||
/* Set height, width, borders, and global font properties here */
|
||||
font-family: monospace;
|
||||
height: 300px;
|
||||
color: black;
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
/* PADDING */
|
||||
|
||||
.CodeMirror-lines {
|
||||
padding: 4px 0; /* Vertical padding around content */
|
||||
}
|
||||
.CodeMirror pre.CodeMirror-line,
|
||||
.CodeMirror pre.CodeMirror-line-like {
|
||||
padding: 0 4px; /* Horizontal padding of content */
|
||||
}
|
||||
|
||||
.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||
background-color: white; /* The little square between H and V scrollbars */
|
||||
}
|
||||
|
||||
/* GUTTER */
|
||||
|
||||
.CodeMirror-gutters {
|
||||
border-right: 1px solid #ddd;
|
||||
background-color: #f7f7f7;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.CodeMirror-linenumbers {}
|
||||
.CodeMirror-linenumber {
|
||||
padding: 0 3px 0 5px;
|
||||
min-width: 20px;
|
||||
text-align: right;
|
||||
color: #999;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.CodeMirror-guttermarker { color: black; }
|
||||
.CodeMirror-guttermarker-subtle { color: #999; }
|
||||
|
||||
/* CURSOR */
|
||||
|
||||
.CodeMirror-cursor {
|
||||
border-left: 1px solid black;
|
||||
border-right: none;
|
||||
width: 0;
|
||||
}
|
||||
/* Shown when moving in bi-directional text */
|
||||
.CodeMirror div.CodeMirror-secondarycursor {
|
||||
border-left: 1px solid silver;
|
||||
}
|
||||
.cm-fat-cursor .CodeMirror-cursor {
|
||||
width: auto;
|
||||
border: 0 !important;
|
||||
background: #7e7;
|
||||
}
|
||||
.cm-fat-cursor div.CodeMirror-cursors {
|
||||
z-index: 1;
|
||||
}
|
||||
.cm-fat-cursor-mark {
|
||||
background-color: rgba(20, 255, 20, 0.5);
|
||||
-webkit-animation: blink 1.06s steps(1) infinite;
|
||||
-moz-animation: blink 1.06s steps(1) infinite;
|
||||
animation: blink 1.06s steps(1) infinite;
|
||||
}
|
||||
.cm-animate-fat-cursor {
|
||||
width: auto;
|
||||
border: 0;
|
||||
-webkit-animation: blink 1.06s steps(1) infinite;
|
||||
-moz-animation: blink 1.06s steps(1) infinite;
|
||||
animation: blink 1.06s steps(1) infinite;
|
||||
background-color: #7e7;
|
||||
}
|
||||
@-moz-keyframes blink {
|
||||
0% {}
|
||||
50% { background-color: transparent; }
|
||||
100% {}
|
||||
}
|
||||
@-webkit-keyframes blink {
|
||||
0% {}
|
||||
50% { background-color: transparent; }
|
||||
100% {}
|
||||
}
|
||||
@keyframes blink {
|
||||
0% {}
|
||||
50% { background-color: transparent; }
|
||||
100% {}
|
||||
}
|
||||
|
||||
/* Can style cursor different in overwrite (non-insert) mode */
|
||||
.CodeMirror-overwrite .CodeMirror-cursor {}
|
||||
|
||||
.cm-tab { display: inline-block; text-decoration: inherit; }
|
||||
|
||||
.CodeMirror-rulers {
|
||||
position: absolute;
|
||||
left: 0; right: 0; top: -50px; bottom: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.CodeMirror-ruler {
|
||||
border-left: 1px solid #ccc;
|
||||
top: 0; bottom: 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
/* DEFAULT THEME */
|
||||
|
||||
.cm-s-default .cm-header {color: blue;}
|
||||
.cm-s-default .cm-quote {color: #090;}
|
||||
.cm-negative {color: #d44;}
|
||||
.cm-positive {color: #292;}
|
||||
.cm-header, .cm-strong {font-weight: bold;}
|
||||
.cm-em {font-style: italic;}
|
||||
.cm-link {text-decoration: underline;}
|
||||
.cm-strikethrough {text-decoration: line-through;}
|
||||
|
||||
.cm-s-default .cm-keyword {color: #708;}
|
||||
.cm-s-default .cm-atom {color: #219;}
|
||||
.cm-s-default .cm-number {color: #164;}
|
||||
.cm-s-default .cm-def {color: #00f;}
|
||||
.cm-s-default .cm-variable,
|
||||
.cm-s-default .cm-punctuation,
|
||||
.cm-s-default .cm-property,
|
||||
.cm-s-default .cm-operator {}
|
||||
.cm-s-default .cm-variable-2 {color: #05a;}
|
||||
.cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;}
|
||||
.cm-s-default .cm-comment {color: #a50;}
|
||||
.cm-s-default .cm-string {color: #a11;}
|
||||
.cm-s-default .cm-string-2 {color: #f50;}
|
||||
.cm-s-default .cm-meta {color: #555;}
|
||||
.cm-s-default .cm-qualifier {color: #555;}
|
||||
.cm-s-default .cm-builtin {color: #30a;}
|
||||
.cm-s-default .cm-bracket {color: #997;}
|
||||
.cm-s-default .cm-tag {color: #170;}
|
||||
.cm-s-default .cm-attribute {color: #00c;}
|
||||
.cm-s-default .cm-hr {color: #999;}
|
||||
.cm-s-default .cm-link {color: #00c;}
|
||||
|
||||
.cm-s-default .cm-error {color: #f00;}
|
||||
.cm-invalidchar {color: #f00;}
|
||||
|
||||
.CodeMirror-composing { border-bottom: 2px solid; }
|
||||
|
||||
/* Default styles for common addons */
|
||||
|
||||
div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;}
|
||||
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;}
|
||||
.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
|
||||
.CodeMirror-activeline-background {background: #e8f2ff;}
|
||||
|
||||
/* STOP */
|
||||
|
||||
/* The rest of this file contains styles related to the mechanics of
|
||||
the editor. You probably shouldn't touch them. */
|
||||
|
||||
.CodeMirror {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.CodeMirror-scroll {
|
||||
overflow: scroll !important; /* Things will break if this is overridden */
|
||||
/* 50px is the magic margin used to hide the element's real scrollbars */
|
||||
/* See overflow: hidden in .CodeMirror */
|
||||
margin-bottom: -50px; margin-right: -50px;
|
||||
padding-bottom: 50px;
|
||||
height: 100%;
|
||||
outline: none; /* Prevent dragging from highlighting the element */
|
||||
position: relative;
|
||||
}
|
||||
.CodeMirror-sizer {
|
||||
position: relative;
|
||||
border-right: 50px solid transparent;
|
||||
}
|
||||
|
||||
/* The fake, visible scrollbars. Used to force redraw during scrolling
|
||||
before actual scrolling happens, thus preventing shaking and
|
||||
flickering artifacts. */
|
||||
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||
position: absolute;
|
||||
z-index: 6;
|
||||
display: none;
|
||||
}
|
||||
.CodeMirror-vscrollbar {
|
||||
right: 0; top: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
.CodeMirror-hscrollbar {
|
||||
bottom: 0; left: 0;
|
||||
overflow-y: hidden;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
.CodeMirror-scrollbar-filler {
|
||||
right: 0; bottom: 0;
|
||||
}
|
||||
.CodeMirror-gutter-filler {
|
||||
left: 0; bottom: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-gutters {
|
||||
position: absolute; left: 0; top: 0;
|
||||
min-height: 100%;
|
||||
z-index: 3;
|
||||
}
|
||||
.CodeMirror-gutter {
|
||||
white-space: normal;
|
||||
height: 100%;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin-bottom: -50px;
|
||||
}
|
||||
.CodeMirror-gutter-wrapper {
|
||||
position: absolute;
|
||||
z-index: 4;
|
||||
background: none !important;
|
||||
border: none !important;
|
||||
}
|
||||
.CodeMirror-gutter-background {
|
||||
position: absolute;
|
||||
top: 0; bottom: 0;
|
||||
z-index: 4;
|
||||
}
|
||||
.CodeMirror-gutter-elt {
|
||||
position: absolute;
|
||||
cursor: default;
|
||||
z-index: 4;
|
||||
}
|
||||
.CodeMirror-gutter-wrapper ::selection { background-color: transparent }
|
||||
.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
|
||||
|
||||
.CodeMirror-lines {
|
||||
cursor: text;
|
||||
min-height: 1px; /* prevents collapsing before first draw */
|
||||
}
|
||||
.CodeMirror pre.CodeMirror-line,
|
||||
.CodeMirror pre.CodeMirror-line-like {
|
||||
/* Reset some styles that the rest of the page might have set */
|
||||
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
|
||||
border-width: 0;
|
||||
background: transparent;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
margin: 0;
|
||||
white-space: pre;
|
||||
word-wrap: normal;
|
||||
line-height: inherit;
|
||||
color: inherit;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
-webkit-font-variant-ligatures: contextual;
|
||||
font-variant-ligatures: contextual;
|
||||
}
|
||||
.CodeMirror-wrap pre.CodeMirror-line,
|
||||
.CodeMirror-wrap pre.CodeMirror-line-like {
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
.CodeMirror-linebackground {
|
||||
position: absolute;
|
||||
left: 0; right: 0; top: 0; bottom: 0;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-linewidget {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
padding: 0.1px; /* Force widget margins to stay inside of the container */
|
||||
}
|
||||
|
||||
.CodeMirror-widget {}
|
||||
|
||||
.CodeMirror-rtl pre { direction: rtl; }
|
||||
|
||||
.CodeMirror-code {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Force content-box sizing for the elements where we expect it */
|
||||
.CodeMirror-scroll,
|
||||
.CodeMirror-sizer,
|
||||
.CodeMirror-gutter,
|
||||
.CodeMirror-gutters,
|
||||
.CodeMirror-linenumber {
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.CodeMirror-measure {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.CodeMirror-cursor {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
.CodeMirror-measure pre { position: static; }
|
||||
|
||||
div.CodeMirror-cursors {
|
||||
visibility: hidden;
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
}
|
||||
div.CodeMirror-dragcursors {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.CodeMirror-focused div.CodeMirror-cursors {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.CodeMirror-selected { background: #d9d9d9; }
|
||||
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
|
||||
.CodeMirror-crosshair { cursor: crosshair; }
|
||||
.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
|
||||
.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
|
||||
|
||||
.cm-searching {
|
||||
background-color: #ffa;
|
||||
background-color: rgba(255, 255, 0, .4);
|
||||
}
|
||||
|
||||
/* Used to force a border model for a node */
|
||||
.cm-force-border { padding-right: .1px; }
|
||||
|
||||
@media print {
|
||||
/* Hide the cursor when printing */
|
||||
.CodeMirror div.CodeMirror-cursors {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
/* See issue #2901 */
|
||||
.cm-tab-wrap-hack:after { content: ''; }
|
||||
|
||||
/* Help users use markselection to safely style text background */
|
||||
span.CodeMirror-selectedtext { background: none; }
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,860 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: https://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
CodeMirror.defineMode("css", function(config, parserConfig) {
|
||||
var inline = parserConfig.inline
|
||||
if (!parserConfig.propertyKeywords) parserConfig = CodeMirror.resolveMode("text/css");
|
||||
|
||||
var indentUnit = config.indentUnit,
|
||||
tokenHooks = parserConfig.tokenHooks,
|
||||
documentTypes = parserConfig.documentTypes || {},
|
||||
mediaTypes = parserConfig.mediaTypes || {},
|
||||
mediaFeatures = parserConfig.mediaFeatures || {},
|
||||
mediaValueKeywords = parserConfig.mediaValueKeywords || {},
|
||||
propertyKeywords = parserConfig.propertyKeywords || {},
|
||||
nonStandardPropertyKeywords = parserConfig.nonStandardPropertyKeywords || {},
|
||||
fontProperties = parserConfig.fontProperties || {},
|
||||
counterDescriptors = parserConfig.counterDescriptors || {},
|
||||
colorKeywords = parserConfig.colorKeywords || {},
|
||||
valueKeywords = parserConfig.valueKeywords || {},
|
||||
allowNested = parserConfig.allowNested,
|
||||
lineComment = parserConfig.lineComment,
|
||||
supportsAtComponent = parserConfig.supportsAtComponent === true;
|
||||
|
||||
var type, override;
|
||||
function ret(style, tp) { type = tp; return style; }
|
||||
|
||||
// Tokenizers
|
||||
|
||||
function tokenBase(stream, state) {
|
||||
var ch = stream.next();
|
||||
if (tokenHooks[ch]) {
|
||||
var result = tokenHooks[ch](stream, state);
|
||||
if (result !== false) return result;
|
||||
}
|
||||
if (ch == "@") {
|
||||
stream.eatWhile(/[\w\\\-]/);
|
||||
return ret("def", stream.current());
|
||||
} else if (ch == "=" || (ch == "~" || ch == "|") && stream.eat("=")) {
|
||||
return ret(null, "compare");
|
||||
} else if (ch == "\"" || ch == "'") {
|
||||
state.tokenize = tokenString(ch);
|
||||
return state.tokenize(stream, state);
|
||||
} else if (ch == "#") {
|
||||
stream.eatWhile(/[\w\\\-]/);
|
||||
return ret("atom", "hash");
|
||||
} else if (ch == "!") {
|
||||
stream.match(/^\s*\w*/);
|
||||
return ret("keyword", "important");
|
||||
} else if (/\d/.test(ch) || ch == "." && stream.eat(/\d/)) {
|
||||
stream.eatWhile(/[\w.%]/);
|
||||
return ret("number", "unit");
|
||||
} else if (ch === "-") {
|
||||
if (/[\d.]/.test(stream.peek())) {
|
||||
stream.eatWhile(/[\w.%]/);
|
||||
return ret("number", "unit");
|
||||
} else if (stream.match(/^-[\w\\\-]*/)) {
|
||||
stream.eatWhile(/[\w\\\-]/);
|
||||
if (stream.match(/^\s*:/, false))
|
||||
return ret("variable-2", "variable-definition");
|
||||
return ret("variable-2", "variable");
|
||||
} else if (stream.match(/^\w+-/)) {
|
||||
return ret("meta", "meta");
|
||||
}
|
||||
} else if (/[,+>*\/]/.test(ch)) {
|
||||
return ret(null, "select-op");
|
||||
} else if (ch == "." && stream.match(/^-?[_a-z][_a-z0-9-]*/i)) {
|
||||
return ret("qualifier", "qualifier");
|
||||
} else if (/[:;{}\[\]\(\)]/.test(ch)) {
|
||||
return ret(null, ch);
|
||||
} else if (stream.match(/[\w-.]+(?=\()/)) {
|
||||
if (/^(url(-prefix)?|domain|regexp)$/.test(stream.current().toLowerCase())) {
|
||||
state.tokenize = tokenParenthesized;
|
||||
}
|
||||
return ret("variable callee", "variable");
|
||||
} else if (/[\w\\\-]/.test(ch)) {
|
||||
stream.eatWhile(/[\w\\\-]/);
|
||||
return ret("property", "word");
|
||||
} else {
|
||||
return ret(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
function tokenString(quote) {
|
||||
return function(stream, state) {
|
||||
var escaped = false, ch;
|
||||
while ((ch = stream.next()) != null) {
|
||||
if (ch == quote && !escaped) {
|
||||
if (quote == ")") stream.backUp(1);
|
||||
break;
|
||||
}
|
||||
escaped = !escaped && ch == "\\";
|
||||
}
|
||||
if (ch == quote || !escaped && quote != ")") state.tokenize = null;
|
||||
return ret("string", "string");
|
||||
};
|
||||
}
|
||||
|
||||
function tokenParenthesized(stream, state) {
|
||||
stream.next(); // Must be '('
|
||||
if (!stream.match(/\s*[\"\')]/, false))
|
||||
state.tokenize = tokenString(")");
|
||||
else
|
||||
state.tokenize = null;
|
||||
return ret(null, "(");
|
||||
}
|
||||
|
||||
// Context management
|
||||
|
||||
function Context(type, indent, prev) {
|
||||
this.type = type;
|
||||
this.indent = indent;
|
||||
this.prev = prev;
|
||||
}
|
||||
|
||||
function pushContext(state, stream, type, indent) {
|
||||
state.context = new Context(type, stream.indentation() + (indent === false ? 0 : indentUnit), state.context);
|
||||
return type;
|
||||
}
|
||||
|
||||
function popContext(state) {
|
||||
if (state.context.prev)
|
||||
state.context = state.context.prev;
|
||||
return state.context.type;
|
||||
}
|
||||
|
||||
function pass(type, stream, state) {
|
||||
return states[state.context.type](type, stream, state);
|
||||
}
|
||||
function popAndPass(type, stream, state, n) {
|
||||
for (var i = n || 1; i > 0; i--)
|
||||
state.context = state.context.prev;
|
||||
return pass(type, stream, state);
|
||||
}
|
||||
|
||||
// Parser
|
||||
|
||||
function wordAsValue(stream) {
|
||||
var word = stream.current().toLowerCase();
|
||||
if (valueKeywords.hasOwnProperty(word))
|
||||
override = "atom";
|
||||
else if (colorKeywords.hasOwnProperty(word))
|
||||
override = "keyword";
|
||||
else
|
||||
override = "variable";
|
||||
}
|
||||
|
||||
var states = {};
|
||||
|
||||
states.top = function(type, stream, state) {
|
||||
if (type == "{") {
|
||||
return pushContext(state, stream, "block");
|
||||
} else if (type == "}" && state.context.prev) {
|
||||
return popContext(state);
|
||||
} else if (supportsAtComponent && /@component/i.test(type)) {
|
||||
return pushContext(state, stream, "atComponentBlock");
|
||||
} else if (/^@(-moz-)?document$/i.test(type)) {
|
||||
return pushContext(state, stream, "documentTypes");
|
||||
} else if (/^@(media|supports|(-moz-)?document|import)$/i.test(type)) {
|
||||
return pushContext(state, stream, "atBlock");
|
||||
} else if (/^@(font-face|counter-style)/i.test(type)) {
|
||||
state.stateArg = type;
|
||||
return "restricted_atBlock_before";
|
||||
} else if (/^@(-(moz|ms|o|webkit)-)?keyframes$/i.test(type)) {
|
||||
return "keyframes";
|
||||
} else if (type && type.charAt(0) == "@") {
|
||||
return pushContext(state, stream, "at");
|
||||
} else if (type == "hash") {
|
||||
override = "builtin";
|
||||
} else if (type == "word") {
|
||||
override = "tag";
|
||||
} else if (type == "variable-definition") {
|
||||
return "maybeprop";
|
||||
} else if (type == "interpolation") {
|
||||
return pushContext(state, stream, "interpolation");
|
||||
} else if (type == ":") {
|
||||
return "pseudo";
|
||||
} else if (allowNested && type == "(") {
|
||||
return pushContext(state, stream, "parens");
|
||||
}
|
||||
return state.context.type;
|
||||
};
|
||||
|
||||
states.block = function(type, stream, state) {
|
||||
if (type == "word") {
|
||||
var word = stream.current().toLowerCase();
|
||||
if (propertyKeywords.hasOwnProperty(word)) {
|
||||
override = "property";
|
||||
return "maybeprop";
|
||||
} else if (nonStandardPropertyKeywords.hasOwnProperty(word)) {
|
||||
override = "string-2";
|
||||
return "maybeprop";
|
||||
} else if (allowNested) {
|
||||
override = stream.match(/^\s*:(?:\s|$)/, false) ? "property" : "tag";
|
||||
return "block";
|
||||
} else {
|
||||
override += " error";
|
||||
return "maybeprop";
|
||||
}
|
||||
} else if (type == "meta") {
|
||||
return "block";
|
||||
} else if (!allowNested && (type == "hash" || type == "qualifier")) {
|
||||
override = "error";
|
||||
return "block";
|
||||
} else {
|
||||
return states.top(type, stream, state);
|
||||
}
|
||||
};
|
||||
|
||||
states.maybeprop = function(type, stream, state) {
|
||||
if (type == ":") return pushContext(state, stream, "prop");
|
||||
return pass(type, stream, state);
|
||||
};
|
||||
|
||||
states.prop = function(type, stream, state) {
|
||||
if (type == ";") return popContext(state);
|
||||
if (type == "{" && allowNested) return pushContext(state, stream, "propBlock");
|
||||
if (type == "}" || type == "{") return popAndPass(type, stream, state);
|
||||
if (type == "(") return pushContext(state, stream, "parens");
|
||||
|
||||
if (type == "hash" && !/^#([0-9a-fA-f]{3,4}|[0-9a-fA-f]{6}|[0-9a-fA-f]{8})$/.test(stream.current())) {
|
||||
override += " error";
|
||||
} else if (type == "word") {
|
||||
wordAsValue(stream);
|
||||
} else if (type == "interpolation") {
|
||||
return pushContext(state, stream, "interpolation");
|
||||
}
|
||||
return "prop";
|
||||
};
|
||||
|
||||
states.propBlock = function(type, _stream, state) {
|
||||
if (type == "}") return popContext(state);
|
||||
if (type == "word") { override = "property"; return "maybeprop"; }
|
||||
return state.context.type;
|
||||
};
|
||||
|
||||
states.parens = function(type, stream, state) {
|
||||
if (type == "{" || type == "}") return popAndPass(type, stream, state);
|
||||
if (type == ")") return popContext(state);
|
||||
if (type == "(") return pushContext(state, stream, "parens");
|
||||
if (type == "interpolation") return pushContext(state, stream, "interpolation");
|
||||
if (type == "word") wordAsValue(stream);
|
||||
return "parens";
|
||||
};
|
||||
|
||||
states.pseudo = function(type, stream, state) {
|
||||
if (type == "meta") return "pseudo";
|
||||
|
||||
if (type == "word") {
|
||||
override = "variable-3";
|
||||
return state.context.type;
|
||||
}
|
||||
return pass(type, stream, state);
|
||||
};
|
||||
|
||||
states.documentTypes = function(type, stream, state) {
|
||||
if (type == "word" && documentTypes.hasOwnProperty(stream.current())) {
|
||||
override = "tag";
|
||||
return state.context.type;
|
||||
} else {
|
||||
return states.atBlock(type, stream, state);
|
||||
}
|
||||
};
|
||||
|
||||
states.atBlock = function(type, stream, state) {
|
||||
if (type == "(") return pushContext(state, stream, "atBlock_parens");
|
||||
if (type == "}" || type == ";") return popAndPass(type, stream, state);
|
||||
if (type == "{") return popContext(state) && pushContext(state, stream, allowNested ? "block" : "top");
|
||||
|
||||
if (type == "interpolation") return pushContext(state, stream, "interpolation");
|
||||
|
||||
if (type == "word") {
|
||||
var word = stream.current().toLowerCase();
|
||||
if (word == "only" || word == "not" || word == "and" || word == "or")
|
||||
override = "keyword";
|
||||
else if (mediaTypes.hasOwnProperty(word))
|
||||
override = "attribute";
|
||||
else if (mediaFeatures.hasOwnProperty(word))
|
||||
override = "property";
|
||||
else if (mediaValueKeywords.hasOwnProperty(word))
|
||||
override = "keyword";
|
||||
else if (propertyKeywords.hasOwnProperty(word))
|
||||
override = "property";
|
||||
else if (nonStandardPropertyKeywords.hasOwnProperty(word))
|
||||
override = "string-2";
|
||||
else if (valueKeywords.hasOwnProperty(word))
|
||||
override = "atom";
|
||||
else if (colorKeywords.hasOwnProperty(word))
|
||||
override = "keyword";
|
||||
else
|
||||
override = "error";
|
||||
}
|
||||
return state.context.type;
|
||||
};
|
||||
|
||||
states.atComponentBlock = function(type, stream, state) {
|
||||
if (type == "}")
|
||||
return popAndPass(type, stream, state);
|
||||
if (type == "{")
|
||||
return popContext(state) && pushContext(state, stream, allowNested ? "block" : "top", false);
|
||||
if (type == "word")
|
||||
override = "error";
|
||||
return state.context.type;
|
||||
};
|
||||
|
||||
states.atBlock_parens = function(type, stream, state) {
|
||||
if (type == ")") return popContext(state);
|
||||
if (type == "{" || type == "}") return popAndPass(type, stream, state, 2);
|
||||
return states.atBlock(type, stream, state);
|
||||
};
|
||||
|
||||
states.restricted_atBlock_before = function(type, stream, state) {
|
||||
if (type == "{")
|
||||
return pushContext(state, stream, "restricted_atBlock");
|
||||
if (type == "word" && state.stateArg == "@counter-style") {
|
||||
override = "variable";
|
||||
return "restricted_atBlock_before";
|
||||
}
|
||||
return pass(type, stream, state);
|
||||
};
|
||||
|
||||
states.restricted_atBlock = function(type, stream, state) {
|
||||
if (type == "}") {
|
||||
state.stateArg = null;
|
||||
return popContext(state);
|
||||
}
|
||||
if (type == "word") {
|
||||
if ((state.stateArg == "@font-face" && !fontProperties.hasOwnProperty(stream.current().toLowerCase())) ||
|
||||
(state.stateArg == "@counter-style" && !counterDescriptors.hasOwnProperty(stream.current().toLowerCase())))
|
||||
override = "error";
|
||||
else
|
||||
override = "property";
|
||||
return "maybeprop";
|
||||
}
|
||||
return "restricted_atBlock";
|
||||
};
|
||||
|
||||
states.keyframes = function(type, stream, state) {
|
||||
if (type == "word") { override = "variable"; return "keyframes"; }
|
||||
if (type == "{") return pushContext(state, stream, "top");
|
||||
return pass(type, stream, state);
|
||||
};
|
||||
|
||||
states.at = function(type, stream, state) {
|
||||
if (type == ";") return popContext(state);
|
||||
if (type == "{" || type == "}") return popAndPass(type, stream, state);
|
||||
if (type == "word") override = "tag";
|
||||
else if (type == "hash") override = "builtin";
|
||||
return "at";
|
||||
};
|
||||
|
||||
states.interpolation = function(type, stream, state) {
|
||||
if (type == "}") return popContext(state);
|
||||
if (type == "{" || type == ";") return popAndPass(type, stream, state);
|
||||
if (type == "word") override = "variable";
|
||||
else if (type != "variable" && type != "(" && type != ")") override = "error";
|
||||
return "interpolation";
|
||||
};
|
||||
|
||||
return {
|
||||
startState: function(base) {
|
||||
return {tokenize: null,
|
||||
state: inline ? "block" : "top",
|
||||
stateArg: null,
|
||||
context: new Context(inline ? "block" : "top", base || 0, null)};
|
||||
},
|
||||
|
||||
token: function(stream, state) {
|
||||
if (!state.tokenize && stream.eatSpace()) return null;
|
||||
var style = (state.tokenize || tokenBase)(stream, state);
|
||||
if (style && typeof style == "object") {
|
||||
type = style[1];
|
||||
style = style[0];
|
||||
}
|
||||
override = style;
|
||||
if (type != "comment")
|
||||
state.state = states[state.state](type, stream, state);
|
||||
return override;
|
||||
},
|
||||
|
||||
indent: function(state, textAfter) {
|
||||
var cx = state.context, ch = textAfter && textAfter.charAt(0);
|
||||
var indent = cx.indent;
|
||||
if (cx.type == "prop" && (ch == "}" || ch == ")")) cx = cx.prev;
|
||||
if (cx.prev) {
|
||||
if (ch == "}" && (cx.type == "block" || cx.type == "top" ||
|
||||
cx.type == "interpolation" || cx.type == "restricted_atBlock")) {
|
||||
// Resume indentation from parent context.
|
||||
cx = cx.prev;
|
||||
indent = cx.indent;
|
||||
} else if (ch == ")" && (cx.type == "parens" || cx.type == "atBlock_parens") ||
|
||||
ch == "{" && (cx.type == "at" || cx.type == "atBlock")) {
|
||||
// Dedent relative to current context.
|
||||
indent = Math.max(0, cx.indent - indentUnit);
|
||||
}
|
||||
}
|
||||
return indent;
|
||||
},
|
||||
|
||||
electricChars: "}",
|
||||
blockCommentStart: "/*",
|
||||
blockCommentEnd: "*/",
|
||||
blockCommentContinue: " * ",
|
||||
lineComment: lineComment,
|
||||
fold: "brace"
|
||||
};
|
||||
});
|
||||
|
||||
function keySet(array) {
|
||||
var keys = {};
|
||||
for (var i = 0; i < array.length; ++i) {
|
||||
keys[array[i].toLowerCase()] = true;
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
var documentTypes_ = [
|
||||
"domain", "regexp", "url", "url-prefix"
|
||||
], documentTypes = keySet(documentTypes_);
|
||||
|
||||
var mediaTypes_ = [
|
||||
"all", "aural", "braille", "handheld", "print", "projection", "screen",
|
||||
"tty", "tv", "embossed"
|
||||
], mediaTypes = keySet(mediaTypes_);
|
||||
|
||||
var mediaFeatures_ = [
|
||||
"width", "min-width", "max-width", "height", "min-height", "max-height",
|
||||
"device-width", "min-device-width", "max-device-width", "device-height",
|
||||
"min-device-height", "max-device-height", "aspect-ratio",
|
||||
"min-aspect-ratio", "max-aspect-ratio", "device-aspect-ratio",
|
||||
"min-device-aspect-ratio", "max-device-aspect-ratio", "color", "min-color",
|
||||
"max-color", "color-index", "min-color-index", "max-color-index",
|
||||
"monochrome", "min-monochrome", "max-monochrome", "resolution",
|
||||
"min-resolution", "max-resolution", "scan", "grid", "orientation",
|
||||
"device-pixel-ratio", "min-device-pixel-ratio", "max-device-pixel-ratio",
|
||||
"pointer", "any-pointer", "hover", "any-hover"
|
||||
], mediaFeatures = keySet(mediaFeatures_);
|
||||
|
||||
var mediaValueKeywords_ = [
|
||||
"landscape", "portrait", "none", "coarse", "fine", "on-demand", "hover",
|
||||
"interlace", "progressive"
|
||||
], mediaValueKeywords = keySet(mediaValueKeywords_);
|
||||
|
||||
var propertyKeywords_ = [
|
||||
"align-content", "align-items", "align-self", "alignment-adjust",
|
||||
"alignment-baseline", "anchor-point", "animation", "animation-delay",
|
||||
"animation-direction", "animation-duration", "animation-fill-mode",
|
||||
"animation-iteration-count", "animation-name", "animation-play-state",
|
||||
"animation-timing-function", "appearance", "azimuth", "backdrop-filter",
|
||||
"backface-visibility", "background", "background-attachment",
|
||||
"background-blend-mode", "background-clip", "background-color",
|
||||
"background-image", "background-origin", "background-position",
|
||||
"background-position-x", "background-position-y", "background-repeat",
|
||||
"background-size", "baseline-shift", "binding", "bleed", "block-size",
|
||||
"bookmark-label", "bookmark-level", "bookmark-state", "bookmark-target",
|
||||
"border", "border-bottom", "border-bottom-color", "border-bottom-left-radius",
|
||||
"border-bottom-right-radius", "border-bottom-style", "border-bottom-width",
|
||||
"border-collapse", "border-color", "border-image", "border-image-outset",
|
||||
"border-image-repeat", "border-image-slice", "border-image-source",
|
||||
"border-image-width", "border-left", "border-left-color", "border-left-style",
|
||||
"border-left-width", "border-radius", "border-right", "border-right-color",
|
||||
"border-right-style", "border-right-width", "border-spacing", "border-style",
|
||||
"border-top", "border-top-color", "border-top-left-radius",
|
||||
"border-top-right-radius", "border-top-style", "border-top-width",
|
||||
"border-width", "bottom", "box-decoration-break", "box-shadow", "box-sizing",
|
||||
"break-after", "break-before", "break-inside", "caption-side", "caret-color",
|
||||
"clear", "clip", "color", "color-profile", "column-count", "column-fill",
|
||||
"column-gap", "column-rule", "column-rule-color", "column-rule-style",
|
||||
"column-rule-width", "column-span", "column-width", "columns", "contain",
|
||||
"content", "counter-increment", "counter-reset", "crop", "cue", "cue-after",
|
||||
"cue-before", "cursor", "direction", "display", "dominant-baseline",
|
||||
"drop-initial-after-adjust", "drop-initial-after-align",
|
||||
"drop-initial-before-adjust", "drop-initial-before-align", "drop-initial-size",
|
||||
"drop-initial-value", "elevation", "empty-cells", "fit", "fit-position",
|
||||
"flex", "flex-basis", "flex-direction", "flex-flow", "flex-grow",
|
||||
"flex-shrink", "flex-wrap", "float", "float-offset", "flow-from", "flow-into",
|
||||
"font", "font-family", "font-feature-settings", "font-kerning",
|
||||
"font-language-override", "font-optical-sizing", "font-size",
|
||||
"font-size-adjust", "font-stretch", "font-style", "font-synthesis",
|
||||
"font-variant", "font-variant-alternates", "font-variant-caps",
|
||||
"font-variant-east-asian", "font-variant-ligatures", "font-variant-numeric",
|
||||
"font-variant-position", "font-variation-settings", "font-weight", "gap",
|
||||
"grid", "grid-area", "grid-auto-columns", "grid-auto-flow", "grid-auto-rows",
|
||||
"grid-column", "grid-column-end", "grid-column-gap", "grid-column-start",
|
||||
"grid-gap", "grid-row", "grid-row-end", "grid-row-gap", "grid-row-start",
|
||||
"grid-template", "grid-template-areas", "grid-template-columns",
|
||||
"grid-template-rows", "hanging-punctuation", "height", "hyphens", "icon",
|
||||
"image-orientation", "image-rendering", "image-resolution", "inline-box-align",
|
||||
"inset", "inset-block", "inset-block-end", "inset-block-start", "inset-inline",
|
||||
"inset-inline-end", "inset-inline-start", "isolation", "justify-content",
|
||||
"justify-items", "justify-self", "left", "letter-spacing", "line-break",
|
||||
"line-height", "line-height-step", "line-stacking", "line-stacking-ruby",
|
||||
"line-stacking-shift", "line-stacking-strategy", "list-style",
|
||||
"list-style-image", "list-style-position", "list-style-type", "margin",
|
||||
"margin-bottom", "margin-left", "margin-right", "margin-top", "marks",
|
||||
"marquee-direction", "marquee-loop", "marquee-play-count", "marquee-speed",
|
||||
"marquee-style", "max-block-size", "max-height", "max-inline-size",
|
||||
"max-width", "min-block-size", "min-height", "min-inline-size", "min-width",
|
||||
"mix-blend-mode", "move-to", "nav-down", "nav-index", "nav-left", "nav-right",
|
||||
"nav-up", "object-fit", "object-position", "offset", "offset-anchor",
|
||||
"offset-distance", "offset-path", "offset-position", "offset-rotate",
|
||||
"opacity", "order", "orphans", "outline", "outline-color", "outline-offset",
|
||||
"outline-style", "outline-width", "overflow", "overflow-style",
|
||||
"overflow-wrap", "overflow-x", "overflow-y", "padding", "padding-bottom",
|
||||
"padding-left", "padding-right", "padding-top", "page", "page-break-after",
|
||||
"page-break-before", "page-break-inside", "page-policy", "pause",
|
||||
"pause-after", "pause-before", "perspective", "perspective-origin", "pitch",
|
||||
"pitch-range", "place-content", "place-items", "place-self", "play-during",
|
||||
"position", "presentation-level", "punctuation-trim", "quotes",
|
||||
"region-break-after", "region-break-before", "region-break-inside",
|
||||
"region-fragment", "rendering-intent", "resize", "rest", "rest-after",
|
||||
"rest-before", "richness", "right", "rotate", "rotation", "rotation-point",
|
||||
"row-gap", "ruby-align", "ruby-overhang", "ruby-position", "ruby-span",
|
||||
"scale", "scroll-behavior", "scroll-margin", "scroll-margin-block",
|
||||
"scroll-margin-block-end", "scroll-margin-block-start", "scroll-margin-bottom",
|
||||
"scroll-margin-inline", "scroll-margin-inline-end",
|
||||
"scroll-margin-inline-start", "scroll-margin-left", "scroll-margin-right",
|
||||
"scroll-margin-top", "scroll-padding", "scroll-padding-block",
|
||||
"scroll-padding-block-end", "scroll-padding-block-start",
|
||||
"scroll-padding-bottom", "scroll-padding-inline", "scroll-padding-inline-end",
|
||||
"scroll-padding-inline-start", "scroll-padding-left", "scroll-padding-right",
|
||||
"scroll-padding-top", "scroll-snap-align", "scroll-snap-type",
|
||||
"shape-image-threshold", "shape-inside", "shape-margin", "shape-outside",
|
||||
"size", "speak", "speak-as", "speak-header", "speak-numeral",
|
||||
"speak-punctuation", "speech-rate", "stress", "string-set", "tab-size",
|
||||
"table-layout", "target", "target-name", "target-new", "target-position",
|
||||
"text-align", "text-align-last", "text-combine-upright", "text-decoration",
|
||||
"text-decoration-color", "text-decoration-line", "text-decoration-skip",
|
||||
"text-decoration-skip-ink", "text-decoration-style", "text-emphasis",
|
||||
"text-emphasis-color", "text-emphasis-position", "text-emphasis-style",
|
||||
"text-height", "text-indent", "text-justify", "text-orientation",
|
||||
"text-outline", "text-overflow", "text-rendering", "text-shadow",
|
||||
"text-size-adjust", "text-space-collapse", "text-transform",
|
||||
"text-underline-position", "text-wrap", "top", "transform", "transform-origin",
|
||||
"transform-style", "transition", "transition-delay", "transition-duration",
|
||||
"transition-property", "transition-timing-function", "translate",
|
||||
"unicode-bidi", "user-select", "vertical-align", "visibility", "voice-balance",
|
||||
"voice-duration", "voice-family", "voice-pitch", "voice-range", "voice-rate",
|
||||
"voice-stress", "voice-volume", "volume", "white-space", "widows", "width",
|
||||
"will-change", "word-break", "word-spacing", "word-wrap", "writing-mode", "z-index",
|
||||
// SVG-specific
|
||||
"clip-path", "clip-rule", "mask", "enable-background", "filter", "flood-color",
|
||||
"flood-opacity", "lighting-color", "stop-color", "stop-opacity", "pointer-events",
|
||||
"color-interpolation", "color-interpolation-filters",
|
||||
"color-rendering", "fill", "fill-opacity", "fill-rule", "image-rendering",
|
||||
"marker", "marker-end", "marker-mid", "marker-start", "shape-rendering", "stroke",
|
||||
"stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin",
|
||||
"stroke-miterlimit", "stroke-opacity", "stroke-width", "text-rendering",
|
||||
"baseline-shift", "dominant-baseline", "glyph-orientation-horizontal",
|
||||
"glyph-orientation-vertical", "text-anchor", "writing-mode"
|
||||
], propertyKeywords = keySet(propertyKeywords_);
|
||||
|
||||
var nonStandardPropertyKeywords_ = [
|
||||
"border-block", "border-block-color", "border-block-end",
|
||||
"border-block-end-color", "border-block-end-style", "border-block-end-width",
|
||||
"border-block-start", "border-block-start-color", "border-block-start-style",
|
||||
"border-block-start-width", "border-block-style", "border-block-width",
|
||||
"border-inline", "border-inline-color", "border-inline-end",
|
||||
"border-inline-end-color", "border-inline-end-style",
|
||||
"border-inline-end-width", "border-inline-start", "border-inline-start-color",
|
||||
"border-inline-start-style", "border-inline-start-width",
|
||||
"border-inline-style", "border-inline-width", "margin-block",
|
||||
"margin-block-end", "margin-block-start", "margin-inline", "margin-inline-end",
|
||||
"margin-inline-start", "padding-block", "padding-block-end",
|
||||
"padding-block-start", "padding-inline", "padding-inline-end",
|
||||
"padding-inline-start", "scroll-snap-stop", "scrollbar-3d-light-color",
|
||||
"scrollbar-arrow-color", "scrollbar-base-color", "scrollbar-dark-shadow-color",
|
||||
"scrollbar-face-color", "scrollbar-highlight-color", "scrollbar-shadow-color",
|
||||
"scrollbar-track-color", "searchfield-cancel-button", "searchfield-decoration",
|
||||
"searchfield-results-button", "searchfield-results-decoration", "shape-inside", "zoom"
|
||||
], nonStandardPropertyKeywords = keySet(nonStandardPropertyKeywords_);
|
||||
|
||||
var fontProperties_ = [
|
||||
"font-display", "font-family", "src", "unicode-range", "font-variant",
|
||||
"font-feature-settings", "font-stretch", "font-weight", "font-style"
|
||||
], fontProperties = keySet(fontProperties_);
|
||||
|
||||
var counterDescriptors_ = [
|
||||
"additive-symbols", "fallback", "negative", "pad", "prefix", "range",
|
||||
"speak-as", "suffix", "symbols", "system"
|
||||
], counterDescriptors = keySet(counterDescriptors_);
|
||||
|
||||
var colorKeywords_ = [
|
||||
"aliceblue", "antiquewhite", "aqua", "aquamarine", "azure", "beige",
|
||||
"bisque", "black", "blanchedalmond", "blue", "blueviolet", "brown",
|
||||
"burlywood", "cadetblue", "chartreuse", "chocolate", "coral", "cornflowerblue",
|
||||
"cornsilk", "crimson", "cyan", "darkblue", "darkcyan", "darkgoldenrod",
|
||||
"darkgray", "darkgreen", "darkkhaki", "darkmagenta", "darkolivegreen",
|
||||
"darkorange", "darkorchid", "darkred", "darksalmon", "darkseagreen",
|
||||
"darkslateblue", "darkslategray", "darkturquoise", "darkviolet",
|
||||
"deeppink", "deepskyblue", "dimgray", "dodgerblue", "firebrick",
|
||||
"floralwhite", "forestgreen", "fuchsia", "gainsboro", "ghostwhite",
|
||||
"gold", "goldenrod", "gray", "grey", "green", "greenyellow", "honeydew",
|
||||
"hotpink", "indianred", "indigo", "ivory", "khaki", "lavender",
|
||||
"lavenderblush", "lawngreen", "lemonchiffon", "lightblue", "lightcoral",
|
||||
"lightcyan", "lightgoldenrodyellow", "lightgray", "lightgreen", "lightpink",
|
||||
"lightsalmon", "lightseagreen", "lightskyblue", "lightslategray",
|
||||
"lightsteelblue", "lightyellow", "lime", "limegreen", "linen", "magenta",
|
||||
"maroon", "mediumaquamarine", "mediumblue", "mediumorchid", "mediumpurple",
|
||||
"mediumseagreen", "mediumslateblue", "mediumspringgreen", "mediumturquoise",
|
||||
"mediumvioletred", "midnightblue", "mintcream", "mistyrose", "moccasin",
|
||||
"navajowhite", "navy", "oldlace", "olive", "olivedrab", "orange", "orangered",
|
||||
"orchid", "palegoldenrod", "palegreen", "paleturquoise", "palevioletred",
|
||||
"papayawhip", "peachpuff", "peru", "pink", "plum", "powderblue",
|
||||
"purple", "rebeccapurple", "red", "rosybrown", "royalblue", "saddlebrown",
|
||||
"salmon", "sandybrown", "seagreen", "seashell", "sienna", "silver", "skyblue",
|
||||
"slateblue", "slategray", "snow", "springgreen", "steelblue", "tan",
|
||||
"teal", "thistle", "tomato", "turquoise", "violet", "wheat", "white",
|
||||
"whitesmoke", "yellow", "yellowgreen"
|
||||
], colorKeywords = keySet(colorKeywords_);
|
||||
|
||||
var valueKeywords_ = [
|
||||
"above", "absolute", "activeborder", "additive", "activecaption", "afar",
|
||||
"after-white-space", "ahead", "alias", "all", "all-scroll", "alphabetic", "alternate",
|
||||
"always", "amharic", "amharic-abegede", "antialiased", "appworkspace",
|
||||
"arabic-indic", "armenian", "asterisks", "attr", "auto", "auto-flow", "avoid", "avoid-column", "avoid-page",
|
||||
"avoid-region", "background", "backwards", "baseline", "below", "bidi-override", "binary",
|
||||
"bengali", "blink", "block", "block-axis", "bold", "bolder", "border", "border-box",
|
||||
"both", "bottom", "break", "break-all", "break-word", "bullets", "button", "button-bevel",
|
||||
"buttonface", "buttonhighlight", "buttonshadow", "buttontext", "calc", "cambodian",
|
||||
"capitalize", "caps-lock-indicator", "caption", "captiontext", "caret",
|
||||
"cell", "center", "checkbox", "circle", "cjk-decimal", "cjk-earthly-branch",
|
||||
"cjk-heavenly-stem", "cjk-ideographic", "clear", "clip", "close-quote",
|
||||
"col-resize", "collapse", "color", "color-burn", "color-dodge", "column", "column-reverse",
|
||||
"compact", "condensed", "contain", "content", "contents",
|
||||
"content-box", "context-menu", "continuous", "copy", "counter", "counters", "cover", "crop",
|
||||
"cross", "crosshair", "currentcolor", "cursive", "cyclic", "darken", "dashed", "decimal",
|
||||
"decimal-leading-zero", "default", "default-button", "dense", "destination-atop",
|
||||
"destination-in", "destination-out", "destination-over", "devanagari", "difference",
|
||||
"disc", "discard", "disclosure-closed", "disclosure-open", "document",
|
||||
"dot-dash", "dot-dot-dash",
|
||||
"dotted", "double", "down", "e-resize", "ease", "ease-in", "ease-in-out", "ease-out",
|
||||
"element", "ellipse", "ellipsis", "embed", "end", "ethiopic", "ethiopic-abegede",
|
||||
"ethiopic-abegede-am-et", "ethiopic-abegede-gez", "ethiopic-abegede-ti-er",
|
||||
"ethiopic-abegede-ti-et", "ethiopic-halehame-aa-er",
|
||||
"ethiopic-halehame-aa-et", "ethiopic-halehame-am-et",
|
||||
"ethiopic-halehame-gez", "ethiopic-halehame-om-et",
|
||||
"ethiopic-halehame-sid-et", "ethiopic-halehame-so-et",
|
||||
"ethiopic-halehame-ti-er", "ethiopic-halehame-ti-et", "ethiopic-halehame-tig",
|
||||
"ethiopic-numeric", "ew-resize", "exclusion", "expanded", "extends", "extra-condensed",
|
||||
"extra-expanded", "fantasy", "fast", "fill", "fixed", "flat", "flex", "flex-end", "flex-start", "footnotes",
|
||||
"forwards", "from", "geometricPrecision", "georgian", "graytext", "grid", "groove",
|
||||
"gujarati", "gurmukhi", "hand", "hangul", "hangul-consonant", "hard-light", "hebrew",
|
||||
"help", "hidden", "hide", "higher", "highlight", "highlighttext",
|
||||
"hiragana", "hiragana-iroha", "horizontal", "hsl", "hsla", "hue", "icon", "ignore",
|
||||
"inactiveborder", "inactivecaption", "inactivecaptiontext", "infinite",
|
||||
"infobackground", "infotext", "inherit", "initial", "inline", "inline-axis",
|
||||
"inline-block", "inline-flex", "inline-grid", "inline-table", "inset", "inside", "intrinsic", "invert",
|
||||
"italic", "japanese-formal", "japanese-informal", "justify", "kannada",
|
||||
"katakana", "katakana-iroha", "keep-all", "khmer",
|
||||
"korean-hangul-formal", "korean-hanja-formal", "korean-hanja-informal",
|
||||
"landscape", "lao", "large", "larger", "left", "level", "lighter", "lighten",
|
||||
"line-through", "linear", "linear-gradient", "lines", "list-item", "listbox", "listitem",
|
||||
"local", "logical", "loud", "lower", "lower-alpha", "lower-armenian",
|
||||
"lower-greek", "lower-hexadecimal", "lower-latin", "lower-norwegian",
|
||||
"lower-roman", "lowercase", "ltr", "luminosity", "malayalam", "match", "matrix", "matrix3d",
|
||||
"media-controls-background", "media-current-time-display",
|
||||
"media-fullscreen-button", "media-mute-button", "media-play-button",
|
||||
"media-return-to-realtime-button", "media-rewind-button",
|
||||
"media-seek-back-button", "media-seek-forward-button", "media-slider",
|
||||
"media-sliderthumb", "media-time-remaining-display", "media-volume-slider",
|
||||
"media-volume-slider-container", "media-volume-sliderthumb", "medium",
|
||||
"menu", "menulist", "menulist-button", "menulist-text",
|
||||
"menulist-textfield", "menutext", "message-box", "middle", "min-intrinsic",
|
||||
"mix", "mongolian", "monospace", "move", "multiple", "multiply", "myanmar", "n-resize",
|
||||
"narrower", "ne-resize", "nesw-resize", "no-close-quote", "no-drop",
|
||||
"no-open-quote", "no-repeat", "none", "normal", "not-allowed", "nowrap",
|
||||
"ns-resize", "numbers", "numeric", "nw-resize", "nwse-resize", "oblique", "octal", "opacity", "open-quote",
|
||||
"optimizeLegibility", "optimizeSpeed", "oriya", "oromo", "outset",
|
||||
"outside", "outside-shape", "overlay", "overline", "padding", "padding-box",
|
||||
"painted", "page", "paused", "persian", "perspective", "plus-darker", "plus-lighter",
|
||||
"pointer", "polygon", "portrait", "pre", "pre-line", "pre-wrap", "preserve-3d",
|
||||
"progress", "push-button", "radial-gradient", "radio", "read-only",
|
||||
"read-write", "read-write-plaintext-only", "rectangle", "region",
|
||||
"relative", "repeat", "repeating-linear-gradient",
|
||||
"repeating-radial-gradient", "repeat-x", "repeat-y", "reset", "reverse",
|
||||
"rgb", "rgba", "ridge", "right", "rotate", "rotate3d", "rotateX", "rotateY",
|
||||
"rotateZ", "round", "row", "row-resize", "row-reverse", "rtl", "run-in", "running",
|
||||
"s-resize", "sans-serif", "saturation", "scale", "scale3d", "scaleX", "scaleY", "scaleZ", "screen",
|
||||
"scroll", "scrollbar", "scroll-position", "se-resize", "searchfield",
|
||||
"searchfield-cancel-button", "searchfield-decoration",
|
||||
"searchfield-results-button", "searchfield-results-decoration", "self-start", "self-end",
|
||||
"semi-condensed", "semi-expanded", "separate", "serif", "show", "sidama",
|
||||
"simp-chinese-formal", "simp-chinese-informal", "single",
|
||||
"skew", "skewX", "skewY", "skip-white-space", "slide", "slider-horizontal",
|
||||
"slider-vertical", "sliderthumb-horizontal", "sliderthumb-vertical", "slow",
|
||||
"small", "small-caps", "small-caption", "smaller", "soft-light", "solid", "somali",
|
||||
"source-atop", "source-in", "source-out", "source-over", "space", "space-around", "space-between", "space-evenly", "spell-out", "square",
|
||||
"square-button", "start", "static", "status-bar", "stretch", "stroke", "sub",
|
||||
"subpixel-antialiased", "super", "sw-resize", "symbolic", "symbols", "system-ui", "table",
|
||||
"table-caption", "table-cell", "table-column", "table-column-group",
|
||||
"table-footer-group", "table-header-group", "table-row", "table-row-group",
|
||||
"tamil",
|
||||
"telugu", "text", "text-bottom", "text-top", "textarea", "textfield", "thai",
|
||||
"thick", "thin", "threeddarkshadow", "threedface", "threedhighlight",
|
||||
"threedlightshadow", "threedshadow", "tibetan", "tigre", "tigrinya-er",
|
||||
"tigrinya-er-abegede", "tigrinya-et", "tigrinya-et-abegede", "to", "top",
|
||||
"trad-chinese-formal", "trad-chinese-informal", "transform",
|
||||
"translate", "translate3d", "translateX", "translateY", "translateZ",
|
||||
"transparent", "ultra-condensed", "ultra-expanded", "underline", "unset", "up",
|
||||
"upper-alpha", "upper-armenian", "upper-greek", "upper-hexadecimal",
|
||||
"upper-latin", "upper-norwegian", "upper-roman", "uppercase", "urdu", "url",
|
||||
"var", "vertical", "vertical-text", "visible", "visibleFill", "visiblePainted",
|
||||
"visibleStroke", "visual", "w-resize", "wait", "wave", "wider",
|
||||
"window", "windowframe", "windowtext", "words", "wrap", "wrap-reverse", "x-large", "x-small", "xor",
|
||||
"xx-large", "xx-small"
|
||||
], valueKeywords = keySet(valueKeywords_);
|
||||
|
||||
var allWords = documentTypes_.concat(mediaTypes_).concat(mediaFeatures_).concat(mediaValueKeywords_)
|
||||
.concat(propertyKeywords_).concat(nonStandardPropertyKeywords_).concat(colorKeywords_)
|
||||
.concat(valueKeywords_);
|
||||
CodeMirror.registerHelper("hintWords", "css", allWords);
|
||||
|
||||
function tokenCComment(stream, state) {
|
||||
var maybeEnd = false, ch;
|
||||
while ((ch = stream.next()) != null) {
|
||||
if (maybeEnd && ch == "/") {
|
||||
state.tokenize = null;
|
||||
break;
|
||||
}
|
||||
maybeEnd = (ch == "*");
|
||||
}
|
||||
return ["comment", "comment"];
|
||||
}
|
||||
|
||||
CodeMirror.defineMIME("text/css", {
|
||||
documentTypes: documentTypes,
|
||||
mediaTypes: mediaTypes,
|
||||
mediaFeatures: mediaFeatures,
|
||||
mediaValueKeywords: mediaValueKeywords,
|
||||
propertyKeywords: propertyKeywords,
|
||||
nonStandardPropertyKeywords: nonStandardPropertyKeywords,
|
||||
fontProperties: fontProperties,
|
||||
counterDescriptors: counterDescriptors,
|
||||
colorKeywords: colorKeywords,
|
||||
valueKeywords: valueKeywords,
|
||||
tokenHooks: {
|
||||
"/": function(stream, state) {
|
||||
if (!stream.eat("*")) return false;
|
||||
state.tokenize = tokenCComment;
|
||||
return tokenCComment(stream, state);
|
||||
}
|
||||
},
|
||||
name: "css"
|
||||
});
|
||||
|
||||
CodeMirror.defineMIME("text/x-scss", {
|
||||
mediaTypes: mediaTypes,
|
||||
mediaFeatures: mediaFeatures,
|
||||
mediaValueKeywords: mediaValueKeywords,
|
||||
propertyKeywords: propertyKeywords,
|
||||
nonStandardPropertyKeywords: nonStandardPropertyKeywords,
|
||||
colorKeywords: colorKeywords,
|
||||
valueKeywords: valueKeywords,
|
||||
fontProperties: fontProperties,
|
||||
allowNested: true,
|
||||
lineComment: "//",
|
||||
tokenHooks: {
|
||||
"/": function(stream, state) {
|
||||
if (stream.eat("/")) {
|
||||
stream.skipToEnd();
|
||||
return ["comment", "comment"];
|
||||
} else if (stream.eat("*")) {
|
||||
state.tokenize = tokenCComment;
|
||||
return tokenCComment(stream, state);
|
||||
} else {
|
||||
return ["operator", "operator"];
|
||||
}
|
||||
},
|
||||
":": function(stream) {
|
||||
if (stream.match(/\s*\{/, false))
|
||||
return [null, null]
|
||||
return false;
|
||||
},
|
||||
"$": function(stream) {
|
||||
stream.match(/^[\w-]+/);
|
||||
if (stream.match(/^\s*:/, false))
|
||||
return ["variable-2", "variable-definition"];
|
||||
return ["variable-2", "variable"];
|
||||
},
|
||||
"#": function(stream) {
|
||||
if (!stream.eat("{")) return false;
|
||||
return [null, "interpolation"];
|
||||
}
|
||||
},
|
||||
name: "css",
|
||||
helperType: "scss"
|
||||
});
|
||||
|
||||
CodeMirror.defineMIME("text/x-less", {
|
||||
mediaTypes: mediaTypes,
|
||||
mediaFeatures: mediaFeatures,
|
||||
mediaValueKeywords: mediaValueKeywords,
|
||||
propertyKeywords: propertyKeywords,
|
||||
nonStandardPropertyKeywords: nonStandardPropertyKeywords,
|
||||
colorKeywords: colorKeywords,
|
||||
valueKeywords: valueKeywords,
|
||||
fontProperties: fontProperties,
|
||||
allowNested: true,
|
||||
lineComment: "//",
|
||||
tokenHooks: {
|
||||
"/": function(stream, state) {
|
||||
if (stream.eat("/")) {
|
||||
stream.skipToEnd();
|
||||
return ["comment", "comment"];
|
||||
} else if (stream.eat("*")) {
|
||||
state.tokenize = tokenCComment;
|
||||
return tokenCComment(stream, state);
|
||||
} else {
|
||||
return ["operator", "operator"];
|
||||
}
|
||||
},
|
||||
"@": function(stream) {
|
||||
if (stream.eat("{")) return [null, "interpolation"];
|
||||
if (stream.match(/^(charset|document|font-face|import|(-(moz|ms|o|webkit)-)?keyframes|media|namespace|page|supports)\b/i, false)) return false;
|
||||
stream.eatWhile(/[\w\\\-]/);
|
||||
if (stream.match(/^\s*:/, false))
|
||||
return ["variable-2", "variable-definition"];
|
||||
return ["variable-2", "variable"];
|
||||
},
|
||||
"&": function() {
|
||||
return ["atom", "atom"];
|
||||
}
|
||||
},
|
||||
name: "css",
|
||||
helperType: "less"
|
||||
});
|
||||
|
||||
CodeMirror.defineMIME("text/x-gss", {
|
||||
documentTypes: documentTypes,
|
||||
mediaTypes: mediaTypes,
|
||||
mediaFeatures: mediaFeatures,
|
||||
propertyKeywords: propertyKeywords,
|
||||
nonStandardPropertyKeywords: nonStandardPropertyKeywords,
|
||||
fontProperties: fontProperties,
|
||||
counterDescriptors: counterDescriptors,
|
||||
colorKeywords: colorKeywords,
|
||||
valueKeywords: valueKeywords,
|
||||
supportsAtComponent: true,
|
||||
tokenHooks: {
|
||||
"/": function(stream, state) {
|
||||
if (!stream.eat("*")) return false;
|
||||
state.tokenize = tokenCComment;
|
||||
return tokenCComment(stream, state);
|
||||
}
|
||||
},
|
||||
name: "css",
|
||||
helperType: "gss"
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,152 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: https://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"), require("../xml/xml"), require("../javascript/javascript"), require("../css/css"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror", "../xml/xml", "../javascript/javascript", "../css/css"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
var defaultTags = {
|
||||
script: [
|
||||
["lang", /(javascript|babel)/i, "javascript"],
|
||||
["type", /^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^module$|^$/i, "javascript"],
|
||||
["type", /./, "text/plain"],
|
||||
[null, null, "javascript"]
|
||||
],
|
||||
style: [
|
||||
["lang", /^css$/i, "css"],
|
||||
["type", /^(text\/)?(x-)?(stylesheet|css)$/i, "css"],
|
||||
["type", /./, "text/plain"],
|
||||
[null, null, "css"]
|
||||
]
|
||||
};
|
||||
|
||||
function maybeBackup(stream, pat, style) {
|
||||
var cur = stream.current(), close = cur.search(pat);
|
||||
if (close > -1) {
|
||||
stream.backUp(cur.length - close);
|
||||
} else if (cur.match(/<\/?$/)) {
|
||||
stream.backUp(cur.length);
|
||||
if (!stream.match(pat, false)) stream.match(cur);
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
var attrRegexpCache = {};
|
||||
function getAttrRegexp(attr) {
|
||||
var regexp = attrRegexpCache[attr];
|
||||
if (regexp) return regexp;
|
||||
return attrRegexpCache[attr] = new RegExp("\\s+" + attr + "\\s*=\\s*('|\")?([^'\"]+)('|\")?\\s*");
|
||||
}
|
||||
|
||||
function getAttrValue(text, attr) {
|
||||
var match = text.match(getAttrRegexp(attr))
|
||||
return match ? /^\s*(.*?)\s*$/.exec(match[2])[1] : ""
|
||||
}
|
||||
|
||||
function getTagRegexp(tagName, anchored) {
|
||||
return new RegExp((anchored ? "^" : "") + "<\/\s*" + tagName + "\s*>", "i");
|
||||
}
|
||||
|
||||
function addTags(from, to) {
|
||||
for (var tag in from) {
|
||||
var dest = to[tag] || (to[tag] = []);
|
||||
var source = from[tag];
|
||||
for (var i = source.length - 1; i >= 0; i--)
|
||||
dest.unshift(source[i])
|
||||
}
|
||||
}
|
||||
|
||||
function findMatchingMode(tagInfo, tagText) {
|
||||
for (var i = 0; i < tagInfo.length; i++) {
|
||||
var spec = tagInfo[i];
|
||||
if (!spec[0] || spec[1].test(getAttrValue(tagText, spec[0]))) return spec[2];
|
||||
}
|
||||
}
|
||||
|
||||
CodeMirror.defineMode("htmlmixed", function (config, parserConfig) {
|
||||
var htmlMode = CodeMirror.getMode(config, {
|
||||
name: "xml",
|
||||
htmlMode: true,
|
||||
multilineTagIndentFactor: parserConfig.multilineTagIndentFactor,
|
||||
multilineTagIndentPastTag: parserConfig.multilineTagIndentPastTag
|
||||
});
|
||||
|
||||
var tags = {};
|
||||
var configTags = parserConfig && parserConfig.tags, configScript = parserConfig && parserConfig.scriptTypes;
|
||||
addTags(defaultTags, tags);
|
||||
if (configTags) addTags(configTags, tags);
|
||||
if (configScript) for (var i = configScript.length - 1; i >= 0; i--)
|
||||
tags.script.unshift(["type", configScript[i].matches, configScript[i].mode])
|
||||
|
||||
function html(stream, state) {
|
||||
var style = htmlMode.token(stream, state.htmlState), tag = /\btag\b/.test(style), tagName
|
||||
if (tag && !/[<>\s\/]/.test(stream.current()) &&
|
||||
(tagName = state.htmlState.tagName && state.htmlState.tagName.toLowerCase()) &&
|
||||
tags.hasOwnProperty(tagName)) {
|
||||
state.inTag = tagName + " "
|
||||
} else if (state.inTag && tag && />$/.test(stream.current())) {
|
||||
var inTag = /^([\S]+) (.*)/.exec(state.inTag)
|
||||
state.inTag = null
|
||||
var modeSpec = stream.current() == ">" && findMatchingMode(tags[inTag[1]], inTag[2])
|
||||
var mode = CodeMirror.getMode(config, modeSpec)
|
||||
var endTagA = getTagRegexp(inTag[1], true), endTag = getTagRegexp(inTag[1], false);
|
||||
state.token = function (stream, state) {
|
||||
if (stream.match(endTagA, false)) {
|
||||
state.token = html;
|
||||
state.localState = state.localMode = null;
|
||||
return null;
|
||||
}
|
||||
return maybeBackup(stream, endTag, state.localMode.token(stream, state.localState));
|
||||
};
|
||||
state.localMode = mode;
|
||||
state.localState = CodeMirror.startState(mode, htmlMode.indent(state.htmlState, "", ""));
|
||||
} else if (state.inTag) {
|
||||
state.inTag += stream.current()
|
||||
if (stream.eol()) state.inTag += " "
|
||||
}
|
||||
return style;
|
||||
};
|
||||
|
||||
return {
|
||||
startState: function () {
|
||||
var state = CodeMirror.startState(htmlMode);
|
||||
return {token: html, inTag: null, localMode: null, localState: null, htmlState: state};
|
||||
},
|
||||
|
||||
copyState: function (state) {
|
||||
var local;
|
||||
if (state.localState) {
|
||||
local = CodeMirror.copyState(state.localMode, state.localState);
|
||||
}
|
||||
return {token: state.token, inTag: state.inTag,
|
||||
localMode: state.localMode, localState: local,
|
||||
htmlState: CodeMirror.copyState(htmlMode, state.htmlState)};
|
||||
},
|
||||
|
||||
token: function (stream, state) {
|
||||
return state.token(stream, state);
|
||||
},
|
||||
|
||||
indent: function (state, textAfter, line) {
|
||||
if (!state.localMode || /^\s*<\//.test(textAfter))
|
||||
return htmlMode.indent(state.htmlState, textAfter, line);
|
||||
else if (state.localMode.indent)
|
||||
return state.localMode.indent(state.localState, textAfter, line);
|
||||
else
|
||||
return CodeMirror.Pass;
|
||||
},
|
||||
|
||||
innerMode: function (state) {
|
||||
return {state: state.localState || state.htmlState, mode: state.localMode || htmlMode};
|
||||
}
|
||||
};
|
||||
}, "xml", "javascript", "css");
|
||||
|
||||
CodeMirror.defineMIME("text/html", "htmlmixed");
|
||||
});
|
|
@ -0,0 +1,933 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: https://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
CodeMirror.defineMode("javascript", function(config, parserConfig) {
|
||||
var indentUnit = config.indentUnit;
|
||||
var statementIndent = parserConfig.statementIndent;
|
||||
var jsonldMode = parserConfig.jsonld;
|
||||
var jsonMode = parserConfig.json || jsonldMode;
|
||||
var isTS = parserConfig.typescript;
|
||||
var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/;
|
||||
|
||||
// Tokenizer
|
||||
|
||||
var keywords = function(){
|
||||
function kw(type) {return {type: type, style: "keyword"};}
|
||||
var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"), D = kw("keyword d");
|
||||
var operator = kw("operator"), atom = {type: "atom", style: "atom"};
|
||||
|
||||
return {
|
||||
"if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
|
||||
"return": D, "break": D, "continue": D, "new": kw("new"), "delete": C, "void": C, "throw": C,
|
||||
"debugger": kw("debugger"), "var": kw("var"), "const": kw("var"), "let": kw("var"),
|
||||
"function": kw("function"), "catch": kw("catch"),
|
||||
"for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
|
||||
"in": operator, "typeof": operator, "instanceof": operator,
|
||||
"true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom,
|
||||
"this": kw("this"), "class": kw("class"), "super": kw("atom"),
|
||||
"yield": C, "export": kw("export"), "import": kw("import"), "extends": C,
|
||||
"await": C
|
||||
};
|
||||
}();
|
||||
|
||||
var isOperatorChar = /[+\-*&%=<>!?|~^@]/;
|
||||
var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/;
|
||||
|
||||
function readRegexp(stream) {
|
||||
var escaped = false, next, inSet = false;
|
||||
while ((next = stream.next()) != null) {
|
||||
if (!escaped) {
|
||||
if (next == "/" && !inSet) return;
|
||||
if (next == "[") inSet = true;
|
||||
else if (inSet && next == "]") inSet = false;
|
||||
}
|
||||
escaped = !escaped && next == "\\";
|
||||
}
|
||||
}
|
||||
|
||||
// Used as scratch variables to communicate multiple values without
|
||||
// consing up tons of objects.
|
||||
var type, content;
|
||||
function ret(tp, style, cont) {
|
||||
type = tp; content = cont;
|
||||
return style;
|
||||
}
|
||||
function tokenBase(stream, state) {
|
||||
var ch = stream.next();
|
||||
if (ch == '"' || ch == "'") {
|
||||
state.tokenize = tokenString(ch);
|
||||
return state.tokenize(stream, state);
|
||||
} else if (ch == "." && stream.match(/^\d[\d_]*(?:[eE][+\-]?[\d_]+)?/)) {
|
||||
return ret("number", "number");
|
||||
} else if (ch == "." && stream.match("..")) {
|
||||
return ret("spread", "meta");
|
||||
} else if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
|
||||
return ret(ch);
|
||||
} else if (ch == "=" && stream.eat(">")) {
|
||||
return ret("=>", "operator");
|
||||
} else if (ch == "0" && stream.match(/^(?:x[\dA-Fa-f_]+|o[0-7_]+|b[01_]+)n?/)) {
|
||||
return ret("number", "number");
|
||||
} else if (/\d/.test(ch)) {
|
||||
stream.match(/^[\d_]*(?:n|(?:\.[\d_]*)?(?:[eE][+\-]?[\d_]+)?)?/);
|
||||
return ret("number", "number");
|
||||
} else if (ch == "/") {
|
||||
if (stream.eat("*")) {
|
||||
state.tokenize = tokenComment;
|
||||
return tokenComment(stream, state);
|
||||
} else if (stream.eat("/")) {
|
||||
stream.skipToEnd();
|
||||
return ret("comment", "comment");
|
||||
} else if (expressionAllowed(stream, state, 1)) {
|
||||
readRegexp(stream);
|
||||
stream.match(/^\b(([gimyus])(?![gimyus]*\2))+\b/);
|
||||
return ret("regexp", "string-2");
|
||||
} else {
|
||||
stream.eat("=");
|
||||
return ret("operator", "operator", stream.current());
|
||||
}
|
||||
} else if (ch == "`") {
|
||||
state.tokenize = tokenQuasi;
|
||||
return tokenQuasi(stream, state);
|
||||
} else if (ch == "#" && stream.peek() == "!") {
|
||||
stream.skipToEnd();
|
||||
return ret("meta", "meta");
|
||||
} else if (ch == "#" && stream.eatWhile(wordRE)) {
|
||||
return ret("variable", "property")
|
||||
} else if (ch == "<" && stream.match("!--") || ch == "-" && stream.match("->")) {
|
||||
stream.skipToEnd()
|
||||
return ret("comment", "comment")
|
||||
} else if (isOperatorChar.test(ch)) {
|
||||
if (ch != ">" || !state.lexical || state.lexical.type != ">") {
|
||||
if (stream.eat("=")) {
|
||||
if (ch == "!" || ch == "=") stream.eat("=")
|
||||
} else if (/[<>*+\-]/.test(ch)) {
|
||||
stream.eat(ch)
|
||||
if (ch == ">") stream.eat(ch)
|
||||
}
|
||||
}
|
||||
if (ch == "?" && stream.eat(".")) return ret(".")
|
||||
return ret("operator", "operator", stream.current());
|
||||
} else if (wordRE.test(ch)) {
|
||||
stream.eatWhile(wordRE);
|
||||
var word = stream.current()
|
||||
if (state.lastType != ".") {
|
||||
if (keywords.propertyIsEnumerable(word)) {
|
||||
var kw = keywords[word]
|
||||
return ret(kw.type, kw.style, word)
|
||||
}
|
||||
if (word == "async" && stream.match(/^(\s|\/\*.*?\*\/)*[\[\(\w]/, false))
|
||||
return ret("async", "keyword", word)
|
||||
}
|
||||
return ret("variable", "variable", word)
|
||||
}
|
||||
}
|
||||
|
||||
function tokenString(quote) {
|
||||
return function(stream, state) {
|
||||
var escaped = false, next;
|
||||
if (jsonldMode && stream.peek() == "@" && stream.match(isJsonldKeyword)){
|
||||
state.tokenize = tokenBase;
|
||||
return ret("jsonld-keyword", "meta");
|
||||
}
|
||||
while ((next = stream.next()) != null) {
|
||||
if (next == quote && !escaped) break;
|
||||
escaped = !escaped && next == "\\";
|
||||
}
|
||||
if (!escaped) state.tokenize = tokenBase;
|
||||
return ret("string", "string");
|
||||
};
|
||||
}
|
||||
|
||||
function tokenComment(stream, state) {
|
||||
var maybeEnd = false, ch;
|
||||
while (ch = stream.next()) {
|
||||
if (ch == "/" && maybeEnd) {
|
||||
state.tokenize = tokenBase;
|
||||
break;
|
||||
}
|
||||
maybeEnd = (ch == "*");
|
||||
}
|
||||
return ret("comment", "comment");
|
||||
}
|
||||
|
||||
function tokenQuasi(stream, state) {
|
||||
var escaped = false, next;
|
||||
while ((next = stream.next()) != null) {
|
||||
if (!escaped && (next == "`" || next == "$" && stream.eat("{"))) {
|
||||
state.tokenize = tokenBase;
|
||||
break;
|
||||
}
|
||||
escaped = !escaped && next == "\\";
|
||||
}
|
||||
return ret("quasi", "string-2", stream.current());
|
||||
}
|
||||
|
||||
var brackets = "([{}])";
|
||||
// This is a crude lookahead trick to try and notice that we're
|
||||
// parsing the argument patterns for a fat-arrow function before we
|
||||
// actually hit the arrow token. It only works if the arrow is on
|
||||
// the same line as the arguments and there's no strange noise
|
||||
// (comments) in between. Fallback is to only notice when we hit the
|
||||
// arrow, and not declare the arguments as locals for the arrow
|
||||
// body.
|
||||
function findFatArrow(stream, state) {
|
||||
if (state.fatArrowAt) state.fatArrowAt = null;
|
||||
var arrow = stream.string.indexOf("=>", stream.start);
|
||||
if (arrow < 0) return;
|
||||
|
||||
if (isTS) { // Try to skip TypeScript return type declarations after the arguments
|
||||
var m = /:\s*(?:\w+(?:<[^>]*>|\[\])?|\{[^}]*\})\s*$/.exec(stream.string.slice(stream.start, arrow))
|
||||
if (m) arrow = m.index
|
||||
}
|
||||
|
||||
var depth = 0, sawSomething = false;
|
||||
for (var pos = arrow - 1; pos >= 0; --pos) {
|
||||
var ch = stream.string.charAt(pos);
|
||||
var bracket = brackets.indexOf(ch);
|
||||
if (bracket >= 0 && bracket < 3) {
|
||||
if (!depth) { ++pos; break; }
|
||||
if (--depth == 0) { if (ch == "(") sawSomething = true; break; }
|
||||
} else if (bracket >= 3 && bracket < 6) {
|
||||
++depth;
|
||||
} else if (wordRE.test(ch)) {
|
||||
sawSomething = true;
|
||||
} else if (/["'\/`]/.test(ch)) {
|
||||
for (;; --pos) {
|
||||
if (pos == 0) return
|
||||
var next = stream.string.charAt(pos - 1)
|
||||
if (next == ch && stream.string.charAt(pos - 2) != "\\") { pos--; break }
|
||||
}
|
||||
} else if (sawSomething && !depth) {
|
||||
++pos;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (sawSomething && !depth) state.fatArrowAt = pos;
|
||||
}
|
||||
|
||||
// Parser
|
||||
|
||||
var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true, "jsonld-keyword": true};
|
||||
|
||||
function JSLexical(indented, column, type, align, prev, info) {
|
||||
this.indented = indented;
|
||||
this.column = column;
|
||||
this.type = type;
|
||||
this.prev = prev;
|
||||
this.info = info;
|
||||
if (align != null) this.align = align;
|
||||
}
|
||||
|
||||
function inScope(state, varname) {
|
||||
for (var v = state.localVars; v; v = v.next)
|
||||
if (v.name == varname) return true;
|
||||
for (var cx = state.context; cx; cx = cx.prev) {
|
||||
for (var v = cx.vars; v; v = v.next)
|
||||
if (v.name == varname) return true;
|
||||
}
|
||||
}
|
||||
|
||||
function parseJS(state, style, type, content, stream) {
|
||||
var cc = state.cc;
|
||||
// Communicate our context to the combinators.
|
||||
// (Less wasteful than consing up a hundred closures on every call.)
|
||||
cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style;
|
||||
|
||||
if (!state.lexical.hasOwnProperty("align"))
|
||||
state.lexical.align = true;
|
||||
|
||||
while(true) {
|
||||
var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
|
||||
if (combinator(type, content)) {
|
||||
while(cc.length && cc[cc.length - 1].lex)
|
||||
cc.pop()();
|
||||
if (cx.marked) return cx.marked;
|
||||
if (type == "variable" && inScope(state, content)) return "variable-2";
|
||||
return style;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Combinator utils
|
||||
|
||||
var cx = {state: null, column: null, marked: null, cc: null};
|
||||
function pass() {
|
||||
for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
|
||||
}
|
||||
function cont() {
|
||||
pass.apply(null, arguments);
|
||||
return true;
|
||||
}
|
||||
function inList(name, list) {
|
||||
for (var v = list; v; v = v.next) if (v.name == name) return true
|
||||
return false;
|
||||
}
|
||||
function register(varname) {
|
||||
var state = cx.state;
|
||||
cx.marked = "def";
|
||||
if (state.context) {
|
||||
if (state.lexical.info == "var" && state.context && state.context.block) {
|
||||
// FIXME function decls are also not block scoped
|
||||
var newContext = registerVarScoped(varname, state.context)
|
||||
if (newContext != null) {
|
||||
state.context = newContext
|
||||
return
|
||||
}
|
||||
} else if (!inList(varname, state.localVars)) {
|
||||
state.localVars = new Var(varname, state.localVars)
|
||||
return
|
||||
}
|
||||
}
|
||||
// Fall through means this is global
|
||||
if (parserConfig.globalVars && !inList(varname, state.globalVars))
|
||||
state.globalVars = new Var(varname, state.globalVars)
|
||||
}
|
||||
function registerVarScoped(varname, context) {
|
||||
if (!context) {
|
||||
return null
|
||||
} else if (context.block) {
|
||||
var inner = registerVarScoped(varname, context.prev)
|
||||
if (!inner) return null
|
||||
if (inner == context.prev) return context
|
||||
return new Context(inner, context.vars, true)
|
||||
} else if (inList(varname, context.vars)) {
|
||||
return context
|
||||
} else {
|
||||
return new Context(context.prev, new Var(varname, context.vars), false)
|
||||
}
|
||||
}
|
||||
|
||||
function isModifier(name) {
|
||||
return name == "public" || name == "private" || name == "protected" || name == "abstract" || name == "readonly"
|
||||
}
|
||||
|
||||
// Combinators
|
||||
|
||||
function Context(prev, vars, block) { this.prev = prev; this.vars = vars; this.block = block }
|
||||
function Var(name, next) { this.name = name; this.next = next }
|
||||
|
||||
var defaultVars = new Var("this", new Var("arguments", null))
|
||||
function pushcontext() {
|
||||
cx.state.context = new Context(cx.state.context, cx.state.localVars, false)
|
||||
cx.state.localVars = defaultVars
|
||||
}
|
||||
function pushblockcontext() {
|
||||
cx.state.context = new Context(cx.state.context, cx.state.localVars, true)
|
||||
cx.state.localVars = null
|
||||
}
|
||||
function popcontext() {
|
||||
cx.state.localVars = cx.state.context.vars
|
||||
cx.state.context = cx.state.context.prev
|
||||
}
|
||||
popcontext.lex = true
|
||||
function pushlex(type, info) {
|
||||
var result = function() {
|
||||
var state = cx.state, indent = state.indented;
|
||||
if (state.lexical.type == "stat") indent = state.lexical.indented;
|
||||
else for (var outer = state.lexical; outer && outer.type == ")" && outer.align; outer = outer.prev)
|
||||
indent = outer.indented;
|
||||
state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info);
|
||||
};
|
||||
result.lex = true;
|
||||
return result;
|
||||
}
|
||||
function poplex() {
|
||||
var state = cx.state;
|
||||
if (state.lexical.prev) {
|
||||
if (state.lexical.type == ")")
|
||||
state.indented = state.lexical.indented;
|
||||
state.lexical = state.lexical.prev;
|
||||
}
|
||||
}
|
||||
poplex.lex = true;
|
||||
|
||||
function expect(wanted) {
|
||||
function exp(type) {
|
||||
if (type == wanted) return cont();
|
||||
else if (wanted == ";" || type == "}" || type == ")" || type == "]") return pass();
|
||||
else return cont(exp);
|
||||
};
|
||||
return exp;
|
||||
}
|
||||
|
||||
function statement(type, value) {
|
||||
if (type == "var") return cont(pushlex("vardef", value), vardef, expect(";"), poplex);
|
||||
if (type == "keyword a") return cont(pushlex("form"), parenExpr, statement, poplex);
|
||||
if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
|
||||
if (type == "keyword d") return cx.stream.match(/^\s*$/, false) ? cont() : cont(pushlex("stat"), maybeexpression, expect(";"), poplex);
|
||||
if (type == "debugger") return cont(expect(";"));
|
||||
if (type == "{") return cont(pushlex("}"), pushblockcontext, block, poplex, popcontext);
|
||||
if (type == ";") return cont();
|
||||
if (type == "if") {
|
||||
if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex)
|
||||
cx.state.cc.pop()();
|
||||
return cont(pushlex("form"), parenExpr, statement, poplex, maybeelse);
|
||||
}
|
||||
if (type == "function") return cont(functiondef);
|
||||
if (type == "for") return cont(pushlex("form"), forspec, statement, poplex);
|
||||
if (type == "class" || (isTS && value == "interface")) {
|
||||
cx.marked = "keyword"
|
||||
return cont(pushlex("form", type == "class" ? type : value), className, poplex)
|
||||
}
|
||||
if (type == "variable") {
|
||||
if (isTS && value == "declare") {
|
||||
cx.marked = "keyword"
|
||||
return cont(statement)
|
||||
} else if (isTS && (value == "module" || value == "enum" || value == "type") && cx.stream.match(/^\s*\w/, false)) {
|
||||
cx.marked = "keyword"
|
||||
if (value == "enum") return cont(enumdef);
|
||||
else if (value == "type") return cont(typename, expect("operator"), typeexpr, expect(";"));
|
||||
else return cont(pushlex("form"), pattern, expect("{"), pushlex("}"), block, poplex, poplex)
|
||||
} else if (isTS && value == "namespace") {
|
||||
cx.marked = "keyword"
|
||||
return cont(pushlex("form"), expression, statement, poplex)
|
||||
} else if (isTS && value == "abstract") {
|
||||
cx.marked = "keyword"
|
||||
return cont(statement)
|
||||
} else {
|
||||
return cont(pushlex("stat"), maybelabel);
|
||||
}
|
||||
}
|
||||
if (type == "switch") return cont(pushlex("form"), parenExpr, expect("{"), pushlex("}", "switch"), pushblockcontext,
|
||||
block, poplex, poplex, popcontext);
|
||||
if (type == "case") return cont(expression, expect(":"));
|
||||
if (type == "default") return cont(expect(":"));
|
||||
if (type == "catch") return cont(pushlex("form"), pushcontext, maybeCatchBinding, statement, poplex, popcontext);
|
||||
if (type == "export") return cont(pushlex("stat"), afterExport, poplex);
|
||||
if (type == "import") return cont(pushlex("stat"), afterImport, poplex);
|
||||
if (type == "async") return cont(statement)
|
||||
if (value == "@") return cont(expression, statement)
|
||||
return pass(pushlex("stat"), expression, expect(";"), poplex);
|
||||
}
|
||||
function maybeCatchBinding(type) {
|
||||
if (type == "(") return cont(funarg, expect(")"))
|
||||
}
|
||||
function expression(type, value) {
|
||||
return expressionInner(type, value, false);
|
||||
}
|
||||
function expressionNoComma(type, value) {
|
||||
return expressionInner(type, value, true);
|
||||
}
|
||||
function parenExpr(type) {
|
||||
if (type != "(") return pass()
|
||||
return cont(pushlex(")"), maybeexpression, expect(")"), poplex)
|
||||
}
|
||||
function expressionInner(type, value, noComma) {
|
||||
if (cx.state.fatArrowAt == cx.stream.start) {
|
||||
var body = noComma ? arrowBodyNoComma : arrowBody;
|
||||
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, expect("=>"), body, popcontext);
|
||||
else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext);
|
||||
}
|
||||
|
||||
var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;
|
||||
if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);
|
||||
if (type == "function") return cont(functiondef, maybeop);
|
||||
if (type == "class" || (isTS && value == "interface")) { cx.marked = "keyword"; return cont(pushlex("form"), classExpression, poplex); }
|
||||
if (type == "keyword c" || type == "async") return cont(noComma ? expressionNoComma : expression);
|
||||
if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop);
|
||||
if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression);
|
||||
if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop);
|
||||
if (type == "{") return contCommasep(objprop, "}", null, maybeop);
|
||||
if (type == "quasi") return pass(quasi, maybeop);
|
||||
if (type == "new") return cont(maybeTarget(noComma));
|
||||
if (type == "import") return cont(expression);
|
||||
return cont();
|
||||
}
|
||||
function maybeexpression(type) {
|
||||
if (type.match(/[;\}\)\],]/)) return pass();
|
||||
return pass(expression);
|
||||
}
|
||||
|
||||
function maybeoperatorComma(type, value) {
|
||||
if (type == ",") return cont(maybeexpression);
|
||||
return maybeoperatorNoComma(type, value, false);
|
||||
}
|
||||
function maybeoperatorNoComma(type, value, noComma) {
|
||||
var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma;
|
||||
var expr = noComma == false ? expression : expressionNoComma;
|
||||
if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext);
|
||||
if (type == "operator") {
|
||||
if (/\+\+|--/.test(value) || isTS && value == "!") return cont(me);
|
||||
if (isTS && value == "<" && cx.stream.match(/^([^<>]|<[^<>]*>)*>\s*\(/, false))
|
||||
return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, me);
|
||||
if (value == "?") return cont(expression, expect(":"), expr);
|
||||
return cont(expr);
|
||||
}
|
||||
if (type == "quasi") { return pass(quasi, me); }
|
||||
if (type == ";") return;
|
||||
if (type == "(") return contCommasep(expressionNoComma, ")", "call", me);
|
||||
if (type == ".") return cont(property, me);
|
||||
if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me);
|
||||
if (isTS && value == "as") { cx.marked = "keyword"; return cont(typeexpr, me) }
|
||||
if (type == "regexp") {
|
||||
cx.state.lastType = cx.marked = "operator"
|
||||
cx.stream.backUp(cx.stream.pos - cx.stream.start - 1)
|
||||
return cont(expr)
|
||||
}
|
||||
}
|
||||
function quasi(type, value) {
|
||||
if (type != "quasi") return pass();
|
||||
if (value.slice(value.length - 2) != "${") return cont(quasi);
|
||||
return cont(expression, continueQuasi);
|
||||
}
|
||||
function continueQuasi(type) {
|
||||
if (type == "}") {
|
||||
cx.marked = "string-2";
|
||||
cx.state.tokenize = tokenQuasi;
|
||||
return cont(quasi);
|
||||
}
|
||||
}
|
||||
function arrowBody(type) {
|
||||
findFatArrow(cx.stream, cx.state);
|
||||
return pass(type == "{" ? statement : expression);
|
||||
}
|
||||
function arrowBodyNoComma(type) {
|
||||
findFatArrow(cx.stream, cx.state);
|
||||
return pass(type == "{" ? statement : expressionNoComma);
|
||||
}
|
||||
function maybeTarget(noComma) {
|
||||
return function(type) {
|
||||
if (type == ".") return cont(noComma ? targetNoComma : target);
|
||||
else if (type == "variable" && isTS) return cont(maybeTypeArgs, noComma ? maybeoperatorNoComma : maybeoperatorComma)
|
||||
else return pass(noComma ? expressionNoComma : expression);
|
||||
};
|
||||
}
|
||||
function target(_, value) {
|
||||
if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorComma); }
|
||||
}
|
||||
function targetNoComma(_, value) {
|
||||
if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorNoComma); }
|
||||
}
|
||||
function maybelabel(type) {
|
||||
if (type == ":") return cont(poplex, statement);
|
||||
return pass(maybeoperatorComma, expect(";"), poplex);
|
||||
}
|
||||
function property(type) {
|
||||
if (type == "variable") {cx.marked = "property"; return cont();}
|
||||
}
|
||||
function objprop(type, value) {
|
||||
if (type == "async") {
|
||||
cx.marked = "property";
|
||||
return cont(objprop);
|
||||
} else if (type == "variable" || cx.style == "keyword") {
|
||||
cx.marked = "property";
|
||||
if (value == "get" || value == "set") return cont(getterSetter);
|
||||
var m // Work around fat-arrow-detection complication for detecting typescript typed arrow params
|
||||
if (isTS && cx.state.fatArrowAt == cx.stream.start && (m = cx.stream.match(/^\s*:\s*/, false)))
|
||||
cx.state.fatArrowAt = cx.stream.pos + m[0].length
|
||||
return cont(afterprop);
|
||||
} else if (type == "number" || type == "string") {
|
||||
cx.marked = jsonldMode ? "property" : (cx.style + " property");
|
||||
return cont(afterprop);
|
||||
} else if (type == "jsonld-keyword") {
|
||||
return cont(afterprop);
|
||||
} else if (isTS && isModifier(value)) {
|
||||
cx.marked = "keyword"
|
||||
return cont(objprop)
|
||||
} else if (type == "[") {
|
||||
return cont(expression, maybetype, expect("]"), afterprop);
|
||||
} else if (type == "spread") {
|
||||
return cont(expressionNoComma, afterprop);
|
||||
} else if (value == "*") {
|
||||
cx.marked = "keyword";
|
||||
return cont(objprop);
|
||||
} else if (type == ":") {
|
||||
return pass(afterprop)
|
||||
}
|
||||
}
|
||||
function getterSetter(type) {
|
||||
if (type != "variable") return pass(afterprop);
|
||||
cx.marked = "property";
|
||||
return cont(functiondef);
|
||||
}
|
||||
function afterprop(type) {
|
||||
if (type == ":") return cont(expressionNoComma);
|
||||
if (type == "(") return pass(functiondef);
|
||||
}
|
||||
function commasep(what, end, sep) {
|
||||
function proceed(type, value) {
|
||||
if (sep ? sep.indexOf(type) > -1 : type == ",") {
|
||||
var lex = cx.state.lexical;
|
||||
if (lex.info == "call") lex.pos = (lex.pos || 0) + 1;
|
||||
return cont(function(type, value) {
|
||||
if (type == end || value == end) return pass()
|
||||
return pass(what)
|
||||
}, proceed);
|
||||
}
|
||||
if (type == end || value == end) return cont();
|
||||
if (sep && sep.indexOf(";") > -1) return pass(what)
|
||||
return cont(expect(end));
|
||||
}
|
||||
return function(type, value) {
|
||||
if (type == end || value == end) return cont();
|
||||
return pass(what, proceed);
|
||||
};
|
||||
}
|
||||
function contCommasep(what, end, info) {
|
||||
for (var i = 3; i < arguments.length; i++)
|
||||
cx.cc.push(arguments[i]);
|
||||
return cont(pushlex(end, info), commasep(what, end), poplex);
|
||||
}
|
||||
function block(type) {
|
||||
if (type == "}") return cont();
|
||||
return pass(statement, block);
|
||||
}
|
||||
function maybetype(type, value) {
|
||||
if (isTS) {
|
||||
if (type == ":") return cont(typeexpr);
|
||||
if (value == "?") return cont(maybetype);
|
||||
}
|
||||
}
|
||||
function maybetypeOrIn(type, value) {
|
||||
if (isTS && (type == ":" || value == "in")) return cont(typeexpr)
|
||||
}
|
||||
function mayberettype(type) {
|
||||
if (isTS && type == ":") {
|
||||
if (cx.stream.match(/^\s*\w+\s+is\b/, false)) return cont(expression, isKW, typeexpr)
|
||||
else return cont(typeexpr)
|
||||
}
|
||||
}
|
||||
function isKW(_, value) {
|
||||
if (value == "is") {
|
||||
cx.marked = "keyword"
|
||||
return cont()
|
||||
}
|
||||
}
|
||||
function typeexpr(type, value) {
|
||||
if (value == "keyof" || value == "typeof" || value == "infer") {
|
||||
cx.marked = "keyword"
|
||||
return cont(value == "typeof" ? expressionNoComma : typeexpr)
|
||||
}
|
||||
if (type == "variable" || value == "void") {
|
||||
cx.marked = "type"
|
||||
return cont(afterType)
|
||||
}
|
||||
if (value == "|" || value == "&") return cont(typeexpr)
|
||||
if (type == "string" || type == "number" || type == "atom") return cont(afterType);
|
||||
if (type == "[") return cont(pushlex("]"), commasep(typeexpr, "]", ","), poplex, afterType)
|
||||
if (type == "{") return cont(pushlex("}"), commasep(typeprop, "}", ",;"), poplex, afterType)
|
||||
if (type == "(") return cont(commasep(typearg, ")"), maybeReturnType, afterType)
|
||||
if (type == "<") return cont(commasep(typeexpr, ">"), typeexpr)
|
||||
}
|
||||
function maybeReturnType(type) {
|
||||
if (type == "=>") return cont(typeexpr)
|
||||
}
|
||||
function typeprop(type, value) {
|
||||
if (type == "variable" || cx.style == "keyword") {
|
||||
cx.marked = "property"
|
||||
return cont(typeprop)
|
||||
} else if (value == "?" || type == "number" || type == "string") {
|
||||
return cont(typeprop)
|
||||
} else if (type == ":") {
|
||||
return cont(typeexpr)
|
||||
} else if (type == "[") {
|
||||
return cont(expect("variable"), maybetypeOrIn, expect("]"), typeprop)
|
||||
} else if (type == "(") {
|
||||
return pass(functiondecl, typeprop)
|
||||
}
|
||||
}
|
||||
function typearg(type, value) {
|
||||
if (type == "variable" && cx.stream.match(/^\s*[?:]/, false) || value == "?") return cont(typearg)
|
||||
if (type == ":") return cont(typeexpr)
|
||||
if (type == "spread") return cont(typearg)
|
||||
return pass(typeexpr)
|
||||
}
|
||||
function afterType(type, value) {
|
||||
if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
|
||||
if (value == "|" || type == "." || value == "&") return cont(typeexpr)
|
||||
if (type == "[") return cont(typeexpr, expect("]"), afterType)
|
||||
if (value == "extends" || value == "implements") { cx.marked = "keyword"; return cont(typeexpr) }
|
||||
if (value == "?") return cont(typeexpr, expect(":"), typeexpr)
|
||||
}
|
||||
function maybeTypeArgs(_, value) {
|
||||
if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
|
||||
}
|
||||
function typeparam() {
|
||||
return pass(typeexpr, maybeTypeDefault)
|
||||
}
|
||||
function maybeTypeDefault(_, value) {
|
||||
if (value == "=") return cont(typeexpr)
|
||||
}
|
||||
function vardef(_, value) {
|
||||
if (value == "enum") {cx.marked = "keyword"; return cont(enumdef)}
|
||||
return pass(pattern, maybetype, maybeAssign, vardefCont);
|
||||
}
|
||||
function pattern(type, value) {
|
||||
if (isTS && isModifier(value)) { cx.marked = "keyword"; return cont(pattern) }
|
||||
if (type == "variable") { register(value); return cont(); }
|
||||
if (type == "spread") return cont(pattern);
|
||||
if (type == "[") return contCommasep(eltpattern, "]");
|
||||
if (type == "{") return contCommasep(proppattern, "}");
|
||||
}
|
||||
function proppattern(type, value) {
|
||||
if (type == "variable" && !cx.stream.match(/^\s*:/, false)) {
|
||||
register(value);
|
||||
return cont(maybeAssign);
|
||||
}
|
||||
if (type == "variable") cx.marked = "property";
|
||||
if (type == "spread") return cont(pattern);
|
||||
if (type == "}") return pass();
|
||||
if (type == "[") return cont(expression, expect(']'), expect(':'), proppattern);
|
||||
return cont(expect(":"), pattern, maybeAssign);
|
||||
}
|
||||
function eltpattern() {
|
||||
return pass(pattern, maybeAssign)
|
||||
}
|
||||
function maybeAssign(_type, value) {
|
||||
if (value == "=") return cont(expressionNoComma);
|
||||
}
|
||||
function vardefCont(type) {
|
||||
if (type == ",") return cont(vardef);
|
||||
}
|
||||
function maybeelse(type, value) {
|
||||
if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex);
|
||||
}
|
||||
function forspec(type, value) {
|
||||
if (value == "await") return cont(forspec);
|
||||
if (type == "(") return cont(pushlex(")"), forspec1, poplex);
|
||||
}
|
||||
function forspec1(type) {
|
||||
if (type == "var") return cont(vardef, forspec2);
|
||||
if (type == "variable") return cont(forspec2);
|
||||
return pass(forspec2)
|
||||
}
|
||||
function forspec2(type, value) {
|
||||
if (type == ")") return cont()
|
||||
if (type == ";") return cont(forspec2)
|
||||
if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression, forspec2) }
|
||||
return pass(expression, forspec2)
|
||||
}
|
||||
function functiondef(type, value) {
|
||||
if (value == "*") {cx.marked = "keyword"; return cont(functiondef);}
|
||||
if (type == "variable") {register(value); return cont(functiondef);}
|
||||
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, mayberettype, statement, popcontext);
|
||||
if (isTS && value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, functiondef)
|
||||
}
|
||||
function functiondecl(type, value) {
|
||||
if (value == "*") {cx.marked = "keyword"; return cont(functiondecl);}
|
||||
if (type == "variable") {register(value); return cont(functiondecl);}
|
||||
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, mayberettype, popcontext);
|
||||
if (isTS && value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, functiondecl)
|
||||
}
|
||||
function typename(type, value) {
|
||||
if (type == "keyword" || type == "variable") {
|
||||
cx.marked = "type"
|
||||
return cont(typename)
|
||||
} else if (value == "<") {
|
||||
return cont(pushlex(">"), commasep(typeparam, ">"), poplex)
|
||||
}
|
||||
}
|
||||
function funarg(type, value) {
|
||||
if (value == "@") cont(expression, funarg)
|
||||
if (type == "spread") return cont(funarg);
|
||||
if (isTS && isModifier(value)) { cx.marked = "keyword"; return cont(funarg); }
|
||||
if (isTS && type == "this") return cont(maybetype, maybeAssign)
|
||||
return pass(pattern, maybetype, maybeAssign);
|
||||
}
|
||||
function classExpression(type, value) {
|
||||
// Class expressions may have an optional name.
|
||||
if (type == "variable") return className(type, value);
|
||||
return classNameAfter(type, value);
|
||||
}
|
||||
function className(type, value) {
|
||||
if (type == "variable") {register(value); return cont(classNameAfter);}
|
||||
}
|
||||
function classNameAfter(type, value) {
|
||||
if (value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, classNameAfter)
|
||||
if (value == "extends" || value == "implements" || (isTS && type == ",")) {
|
||||
if (value == "implements") cx.marked = "keyword";
|
||||
return cont(isTS ? typeexpr : expression, classNameAfter);
|
||||
}
|
||||
if (type == "{") return cont(pushlex("}"), classBody, poplex);
|
||||
}
|
||||
function classBody(type, value) {
|
||||
if (type == "async" ||
|
||||
(type == "variable" &&
|
||||
(value == "static" || value == "get" || value == "set" || (isTS && isModifier(value))) &&
|
||||
cx.stream.match(/^\s+[\w$\xa1-\uffff]/, false))) {
|
||||
cx.marked = "keyword";
|
||||
return cont(classBody);
|
||||
}
|
||||
if (type == "variable" || cx.style == "keyword") {
|
||||
cx.marked = "property";
|
||||
return cont(classfield, classBody);
|
||||
}
|
||||
if (type == "number" || type == "string") return cont(classfield, classBody);
|
||||
if (type == "[")
|
||||
return cont(expression, maybetype, expect("]"), classfield, classBody)
|
||||
if (value == "*") {
|
||||
cx.marked = "keyword";
|
||||
return cont(classBody);
|
||||
}
|
||||
if (isTS && type == "(") return pass(functiondecl, classBody)
|
||||
if (type == ";" || type == ",") return cont(classBody);
|
||||
if (type == "}") return cont();
|
||||
if (value == "@") return cont(expression, classBody)
|
||||
}
|
||||
function classfield(type, value) {
|
||||
if (value == "?") return cont(classfield)
|
||||
if (type == ":") return cont(typeexpr, maybeAssign)
|
||||
if (value == "=") return cont(expressionNoComma)
|
||||
var context = cx.state.lexical.prev, isInterface = context && context.info == "interface"
|
||||
return pass(isInterface ? functiondecl : functiondef)
|
||||
}
|
||||
function afterExport(type, value) {
|
||||
if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); }
|
||||
if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); }
|
||||
if (type == "{") return cont(commasep(exportField, "}"), maybeFrom, expect(";"));
|
||||
return pass(statement);
|
||||
}
|
||||
function exportField(type, value) {
|
||||
if (value == "as") { cx.marked = "keyword"; return cont(expect("variable")); }
|
||||
if (type == "variable") return pass(expressionNoComma, exportField);
|
||||
}
|
||||
function afterImport(type) {
|
||||
if (type == "string") return cont();
|
||||
if (type == "(") return pass(expression);
|
||||
return pass(importSpec, maybeMoreImports, maybeFrom);
|
||||
}
|
||||
function importSpec(type, value) {
|
||||
if (type == "{") return contCommasep(importSpec, "}");
|
||||
if (type == "variable") register(value);
|
||||
if (value == "*") cx.marked = "keyword";
|
||||
return cont(maybeAs);
|
||||
}
|
||||
function maybeMoreImports(type) {
|
||||
if (type == ",") return cont(importSpec, maybeMoreImports)
|
||||
}
|
||||
function maybeAs(_type, value) {
|
||||
if (value == "as") { cx.marked = "keyword"; return cont(importSpec); }
|
||||
}
|
||||
function maybeFrom(_type, value) {
|
||||
if (value == "from") { cx.marked = "keyword"; return cont(expression); }
|
||||
}
|
||||
function arrayLiteral(type) {
|
||||
if (type == "]") return cont();
|
||||
return pass(commasep(expressionNoComma, "]"));
|
||||
}
|
||||
function enumdef() {
|
||||
return pass(pushlex("form"), pattern, expect("{"), pushlex("}"), commasep(enummember, "}"), poplex, poplex)
|
||||
}
|
||||
function enummember() {
|
||||
return pass(pattern, maybeAssign);
|
||||
}
|
||||
|
||||
function isContinuedStatement(state, textAfter) {
|
||||
return state.lastType == "operator" || state.lastType == "," ||
|
||||
isOperatorChar.test(textAfter.charAt(0)) ||
|
||||
/[,.]/.test(textAfter.charAt(0));
|
||||
}
|
||||
|
||||
function expressionAllowed(stream, state, backUp) {
|
||||
return state.tokenize == tokenBase &&
|
||||
/^(?:operator|sof|keyword [bcd]|case|new|export|default|spread|[\[{}\(,;:]|=>)$/.test(state.lastType) ||
|
||||
(state.lastType == "quasi" && /\{\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0))))
|
||||
}
|
||||
|
||||
// Interface
|
||||
|
||||
return {
|
||||
startState: function(basecolumn) {
|
||||
var state = {
|
||||
tokenize: tokenBase,
|
||||
lastType: "sof",
|
||||
cc: [],
|
||||
lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
|
||||
localVars: parserConfig.localVars,
|
||||
context: parserConfig.localVars && new Context(null, null, false),
|
||||
indented: basecolumn || 0
|
||||
};
|
||||
if (parserConfig.globalVars && typeof parserConfig.globalVars == "object")
|
||||
state.globalVars = parserConfig.globalVars;
|
||||
return state;
|
||||
},
|
||||
|
||||
token: function(stream, state) {
|
||||
if (stream.sol()) {
|
||||
if (!state.lexical.hasOwnProperty("align"))
|
||||
state.lexical.align = false;
|
||||
state.indented = stream.indentation();
|
||||
findFatArrow(stream, state);
|
||||
}
|
||||
if (state.tokenize != tokenComment && stream.eatSpace()) return null;
|
||||
var style = state.tokenize(stream, state);
|
||||
if (type == "comment") return style;
|
||||
state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type;
|
||||
return parseJS(state, style, type, content, stream);
|
||||
},
|
||||
|
||||
indent: function(state, textAfter) {
|
||||
if (state.tokenize == tokenComment) return CodeMirror.Pass;
|
||||
if (state.tokenize != tokenBase) return 0;
|
||||
var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical, top
|
||||
// Kludge to prevent 'maybelse' from blocking lexical scope pops
|
||||
if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) {
|
||||
var c = state.cc[i];
|
||||
if (c == poplex) lexical = lexical.prev;
|
||||
else if (c != maybeelse) break;
|
||||
}
|
||||
while ((lexical.type == "stat" || lexical.type == "form") &&
|
||||
(firstChar == "}" || ((top = state.cc[state.cc.length - 1]) &&
|
||||
(top == maybeoperatorComma || top == maybeoperatorNoComma) &&
|
||||
!/^[,\.=+\-*:?[\(]/.test(textAfter))))
|
||||
lexical = lexical.prev;
|
||||
if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat")
|
||||
lexical = lexical.prev;
|
||||
var type = lexical.type, closing = firstChar == type;
|
||||
|
||||
if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info.length + 1 : 0);
|
||||
else if (type == "form" && firstChar == "{") return lexical.indented;
|
||||
else if (type == "form") return lexical.indented + indentUnit;
|
||||
else if (type == "stat")
|
||||
return lexical.indented + (isContinuedStatement(state, textAfter) ? statementIndent || indentUnit : 0);
|
||||
else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false)
|
||||
return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
|
||||
else if (lexical.align) return lexical.column + (closing ? 0 : 1);
|
||||
else return lexical.indented + (closing ? 0 : indentUnit);
|
||||
},
|
||||
|
||||
electricInput: /^\s*(?:case .*?:|default:|\{|\})$/,
|
||||
blockCommentStart: jsonMode ? null : "/*",
|
||||
blockCommentEnd: jsonMode ? null : "*/",
|
||||
blockCommentContinue: jsonMode ? null : " * ",
|
||||
lineComment: jsonMode ? null : "//",
|
||||
fold: "brace",
|
||||
closeBrackets: "()[]{}''\"\"``",
|
||||
|
||||
helperType: jsonMode ? "json" : "javascript",
|
||||
jsonldMode: jsonldMode,
|
||||
jsonMode: jsonMode,
|
||||
|
||||
expressionAllowed: expressionAllowed,
|
||||
|
||||
skipExpression: function(state) {
|
||||
var top = state.cc[state.cc.length - 1]
|
||||
if (top == expression || top == expressionNoComma) state.cc.pop()
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
CodeMirror.registerHelper("wordChars", "javascript", /[\w$]/);
|
||||
|
||||
CodeMirror.defineMIME("text/javascript", "javascript");
|
||||
CodeMirror.defineMIME("text/ecmascript", "javascript");
|
||||
CodeMirror.defineMIME("application/javascript", "javascript");
|
||||
CodeMirror.defineMIME("application/x-javascript", "javascript");
|
||||
CodeMirror.defineMIME("application/ecmascript", "javascript");
|
||||
CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
|
||||
CodeMirror.defineMIME("application/x-json", {name: "javascript", json: true});
|
||||
CodeMirror.defineMIME("application/ld+json", {name: "javascript", jsonld: true});
|
||||
CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true });
|
||||
CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });
|
||||
|
||||
});
|
|
@ -0,0 +1,413 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: https://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
var htmlConfig = {
|
||||
autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true,
|
||||
'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true,
|
||||
'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true,
|
||||
'track': true, 'wbr': true, 'menuitem': true},
|
||||
implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true,
|
||||
'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true,
|
||||
'th': true, 'tr': true},
|
||||
contextGrabbers: {
|
||||
'dd': {'dd': true, 'dt': true},
|
||||
'dt': {'dd': true, 'dt': true},
|
||||
'li': {'li': true},
|
||||
'option': {'option': true, 'optgroup': true},
|
||||
'optgroup': {'optgroup': true},
|
||||
'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true,
|
||||
'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true,
|
||||
'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true,
|
||||
'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true,
|
||||
'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true},
|
||||
'rp': {'rp': true, 'rt': true},
|
||||
'rt': {'rp': true, 'rt': true},
|
||||
'tbody': {'tbody': true, 'tfoot': true},
|
||||
'td': {'td': true, 'th': true},
|
||||
'tfoot': {'tbody': true},
|
||||
'th': {'td': true, 'th': true},
|
||||
'thead': {'tbody': true, 'tfoot': true},
|
||||
'tr': {'tr': true}
|
||||
},
|
||||
doNotIndent: {"pre": true},
|
||||
allowUnquoted: true,
|
||||
allowMissing: true,
|
||||
caseFold: true
|
||||
}
|
||||
|
||||
var xmlConfig = {
|
||||
autoSelfClosers: {},
|
||||
implicitlyClosed: {},
|
||||
contextGrabbers: {},
|
||||
doNotIndent: {},
|
||||
allowUnquoted: false,
|
||||
allowMissing: false,
|
||||
allowMissingTagName: false,
|
||||
caseFold: false
|
||||
}
|
||||
|
||||
CodeMirror.defineMode("xml", function(editorConf, config_) {
|
||||
var indentUnit = editorConf.indentUnit
|
||||
var config = {}
|
||||
var defaults = config_.htmlMode ? htmlConfig : xmlConfig
|
||||
for (var prop in defaults) config[prop] = defaults[prop]
|
||||
for (var prop in config_) config[prop] = config_[prop]
|
||||
|
||||
// Return variables for tokenizers
|
||||
var type, setStyle;
|
||||
|
||||
function inText(stream, state) {
|
||||
function chain(parser) {
|
||||
state.tokenize = parser;
|
||||
return parser(stream, state);
|
||||
}
|
||||
|
||||
var ch = stream.next();
|
||||
if (ch == "<") {
|
||||
if (stream.eat("!")) {
|
||||
if (stream.eat("[")) {
|
||||
if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>"));
|
||||
else return null;
|
||||
} else if (stream.match("--")) {
|
||||
return chain(inBlock("comment", "-->"));
|
||||
} else if (stream.match("DOCTYPE", true, true)) {
|
||||
stream.eatWhile(/[\w\._\-]/);
|
||||
return chain(doctype(1));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else if (stream.eat("?")) {
|
||||
stream.eatWhile(/[\w\._\-]/);
|
||||
state.tokenize = inBlock("meta", "?>");
|
||||
return "meta";
|
||||
} else {
|
||||
type = stream.eat("/") ? "closeTag" : "openTag";
|
||||
state.tokenize = inTag;
|
||||
return "tag bracket";
|
||||
}
|
||||
} else if (ch == "&") {
|
||||
var ok;
|
||||
if (stream.eat("#")) {
|
||||
if (stream.eat("x")) {
|
||||
ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";");
|
||||
} else {
|
||||
ok = stream.eatWhile(/[\d]/) && stream.eat(";");
|
||||
}
|
||||
} else {
|
||||
ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";");
|
||||
}
|
||||
return ok ? "atom" : "error";
|
||||
} else {
|
||||
stream.eatWhile(/[^&<]/);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
inText.isInText = true;
|
||||
|
||||
function inTag(stream, state) {
|
||||
var ch = stream.next();
|
||||
if (ch == ">" || (ch == "/" && stream.eat(">"))) {
|
||||
state.tokenize = inText;
|
||||
type = ch == ">" ? "endTag" : "selfcloseTag";
|
||||
return "tag bracket";
|
||||
} else if (ch == "=") {
|
||||
type = "equals";
|
||||
return null;
|
||||
} else if (ch == "<") {
|
||||
state.tokenize = inText;
|
||||
state.state = baseState;
|
||||
state.tagName = state.tagStart = null;
|
||||
var next = state.tokenize(stream, state);
|
||||
return next ? next + " tag error" : "tag error";
|
||||
} else if (/[\'\"]/.test(ch)) {
|
||||
state.tokenize = inAttribute(ch);
|
||||
state.stringStartCol = stream.column();
|
||||
return state.tokenize(stream, state);
|
||||
} else {
|
||||
stream.match(/^[^\s\u00a0=<>\"\']*[^\s\u00a0=<>\"\'\/]/);
|
||||
return "word";
|
||||
}
|
||||
}
|
||||
|
||||
function inAttribute(quote) {
|
||||
var closure = function(stream, state) {
|
||||
while (!stream.eol()) {
|
||||
if (stream.next() == quote) {
|
||||
state.tokenize = inTag;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return "string";
|
||||
};
|
||||
closure.isInAttribute = true;
|
||||
return closure;
|
||||
}
|
||||
|
||||
function inBlock(style, terminator) {
|
||||
return function(stream, state) {
|
||||
while (!stream.eol()) {
|
||||
if (stream.match(terminator)) {
|
||||
state.tokenize = inText;
|
||||
break;
|
||||
}
|
||||
stream.next();
|
||||
}
|
||||
return style;
|
||||
}
|
||||
}
|
||||
|
||||
function doctype(depth) {
|
||||
return function(stream, state) {
|
||||
var ch;
|
||||
while ((ch = stream.next()) != null) {
|
||||
if (ch == "<") {
|
||||
state.tokenize = doctype(depth + 1);
|
||||
return state.tokenize(stream, state);
|
||||
} else if (ch == ">") {
|
||||
if (depth == 1) {
|
||||
state.tokenize = inText;
|
||||
break;
|
||||
} else {
|
||||
state.tokenize = doctype(depth - 1);
|
||||
return state.tokenize(stream, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
return "meta";
|
||||
};
|
||||
}
|
||||
|
||||
function Context(state, tagName, startOfLine) {
|
||||
this.prev = state.context;
|
||||
this.tagName = tagName;
|
||||
this.indent = state.indented;
|
||||
this.startOfLine = startOfLine;
|
||||
if (config.doNotIndent.hasOwnProperty(tagName) || (state.context && state.context.noIndent))
|
||||
this.noIndent = true;
|
||||
}
|
||||
function popContext(state) {
|
||||
if (state.context) state.context = state.context.prev;
|
||||
}
|
||||
function maybePopContext(state, nextTagName) {
|
||||
var parentTagName;
|
||||
while (true) {
|
||||
if (!state.context) {
|
||||
return;
|
||||
}
|
||||
parentTagName = state.context.tagName;
|
||||
if (!config.contextGrabbers.hasOwnProperty(parentTagName) ||
|
||||
!config.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) {
|
||||
return;
|
||||
}
|
||||
popContext(state);
|
||||
}
|
||||
}
|
||||
|
||||
function baseState(type, stream, state) {
|
||||
if (type == "openTag") {
|
||||
state.tagStart = stream.column();
|
||||
return tagNameState;
|
||||
} else if (type == "closeTag") {
|
||||
return closeTagNameState;
|
||||
} else {
|
||||
return baseState;
|
||||
}
|
||||
}
|
||||
function tagNameState(type, stream, state) {
|
||||
if (type == "word") {
|
||||
state.tagName = stream.current();
|
||||
setStyle = "tag";
|
||||
return attrState;
|
||||
} else if (config.allowMissingTagName && type == "endTag") {
|
||||
setStyle = "tag bracket";
|
||||
return attrState(type, stream, state);
|
||||
} else {
|
||||
setStyle = "error";
|
||||
return tagNameState;
|
||||
}
|
||||
}
|
||||
function closeTagNameState(type, stream, state) {
|
||||
if (type == "word") {
|
||||
var tagName = stream.current();
|
||||
if (state.context && state.context.tagName != tagName &&
|
||||
config.implicitlyClosed.hasOwnProperty(state.context.tagName))
|
||||
popContext(state);
|
||||
if ((state.context && state.context.tagName == tagName) || config.matchClosing === false) {
|
||||
setStyle = "tag";
|
||||
return closeState;
|
||||
} else {
|
||||
setStyle = "tag error";
|
||||
return closeStateErr;
|
||||
}
|
||||
} else if (config.allowMissingTagName && type == "endTag") {
|
||||
setStyle = "tag bracket";
|
||||
return closeState(type, stream, state);
|
||||
} else {
|
||||
setStyle = "error";
|
||||
return closeStateErr;
|
||||
}
|
||||
}
|
||||
|
||||
function closeState(type, _stream, state) {
|
||||
if (type != "endTag") {
|
||||
setStyle = "error";
|
||||
return closeState;
|
||||
}
|
||||
popContext(state);
|
||||
return baseState;
|
||||
}
|
||||
function closeStateErr(type, stream, state) {
|
||||
setStyle = "error";
|
||||
return closeState(type, stream, state);
|
||||
}
|
||||
|
||||
function attrState(type, _stream, state) {
|
||||
if (type == "word") {
|
||||
setStyle = "attribute";
|
||||
return attrEqState;
|
||||
} else if (type == "endTag" || type == "selfcloseTag") {
|
||||
var tagName = state.tagName, tagStart = state.tagStart;
|
||||
state.tagName = state.tagStart = null;
|
||||
if (type == "selfcloseTag" ||
|
||||
config.autoSelfClosers.hasOwnProperty(tagName)) {
|
||||
maybePopContext(state, tagName);
|
||||
} else {
|
||||
maybePopContext(state, tagName);
|
||||
state.context = new Context(state, tagName, tagStart == state.indented);
|
||||
}
|
||||
return baseState;
|
||||
}
|
||||
setStyle = "error";
|
||||
return attrState;
|
||||
}
|
||||
function attrEqState(type, stream, state) {
|
||||
if (type == "equals") return attrValueState;
|
||||
if (!config.allowMissing) setStyle = "error";
|
||||
return attrState(type, stream, state);
|
||||
}
|
||||
function attrValueState(type, stream, state) {
|
||||
if (type == "string") return attrContinuedState;
|
||||
if (type == "word" && config.allowUnquoted) {setStyle = "string"; return attrState;}
|
||||
setStyle = "error";
|
||||
return attrState(type, stream, state);
|
||||
}
|
||||
function attrContinuedState(type, stream, state) {
|
||||
if (type == "string") return attrContinuedState;
|
||||
return attrState(type, stream, state);
|
||||
}
|
||||
|
||||
return {
|
||||
startState: function(baseIndent) {
|
||||
var state = {tokenize: inText,
|
||||
state: baseState,
|
||||
indented: baseIndent || 0,
|
||||
tagName: null, tagStart: null,
|
||||
context: null}
|
||||
if (baseIndent != null) state.baseIndent = baseIndent
|
||||
return state
|
||||
},
|
||||
|
||||
token: function(stream, state) {
|
||||
if (!state.tagName && stream.sol())
|
||||
state.indented = stream.indentation();
|
||||
|
||||
if (stream.eatSpace()) return null;
|
||||
type = null;
|
||||
var style = state.tokenize(stream, state);
|
||||
if ((style || type) && style != "comment") {
|
||||
setStyle = null;
|
||||
state.state = state.state(type || style, stream, state);
|
||||
if (setStyle)
|
||||
style = setStyle == "error" ? style + " error" : setStyle;
|
||||
}
|
||||
return style;
|
||||
},
|
||||
|
||||
indent: function(state, textAfter, fullLine) {
|
||||
var context = state.context;
|
||||
// Indent multi-line strings (e.g. css).
|
||||
if (state.tokenize.isInAttribute) {
|
||||
if (state.tagStart == state.indented)
|
||||
return state.stringStartCol + 1;
|
||||
else
|
||||
return state.indented + indentUnit;
|
||||
}
|
||||
if (context && context.noIndent) return CodeMirror.Pass;
|
||||
if (state.tokenize != inTag && state.tokenize != inText)
|
||||
return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0;
|
||||
// Indent the starts of attribute names.
|
||||
if (state.tagName) {
|
||||
if (config.multilineTagIndentPastTag !== false)
|
||||
return state.tagStart + state.tagName.length + 2;
|
||||
else
|
||||
return state.tagStart + indentUnit * (config.multilineTagIndentFactor || 1);
|
||||
}
|
||||
if (config.alignCDATA && /<!\[CDATA\[/.test(textAfter)) return 0;
|
||||
var tagAfter = textAfter && /^<(\/)?([\w_:\.-]*)/.exec(textAfter);
|
||||
if (tagAfter && tagAfter[1]) { // Closing tag spotted
|
||||
while (context) {
|
||||
if (context.tagName == tagAfter[2]) {
|
||||
context = context.prev;
|
||||
break;
|
||||
} else if (config.implicitlyClosed.hasOwnProperty(context.tagName)) {
|
||||
context = context.prev;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (tagAfter) { // Opening tag spotted
|
||||
while (context) {
|
||||
var grabbers = config.contextGrabbers[context.tagName];
|
||||
if (grabbers && grabbers.hasOwnProperty(tagAfter[2]))
|
||||
context = context.prev;
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (context && context.prev && !context.startOfLine)
|
||||
context = context.prev;
|
||||
if (context) return context.indent + indentUnit;
|
||||
else return state.baseIndent || 0;
|
||||
},
|
||||
|
||||
electricInput: /<\/[\s\w:]+>$/,
|
||||
blockCommentStart: "<!--",
|
||||
blockCommentEnd: "-->",
|
||||
|
||||
configuration: config.htmlMode ? "html" : "xml",
|
||||
helperType: config.htmlMode ? "html" : "xml",
|
||||
|
||||
skipAttribute: function(state) {
|
||||
if (state.state == attrValueState)
|
||||
state.state = attrState
|
||||
},
|
||||
|
||||
xmlCurrentTag: function(state) {
|
||||
return state.tagName ? {name: state.tagName, close: state.type == "closeTag"} : null
|
||||
},
|
||||
|
||||
xmlCurrentContext: function(state) {
|
||||
var context = []
|
||||
for (var cx = state.context; cx; cx = cx.prev)
|
||||
if (cx.tagName) context.push(cx.tagName)
|
||||
return context.reverse()
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
CodeMirror.defineMIME("text/xml", "xml");
|
||||
CodeMirror.defineMIME("application/xml", "xml");
|
||||
if (!CodeMirror.mimeModes.hasOwnProperty("text/html"))
|
||||
CodeMirror.defineMIME("text/html", {name: "xml", htmlMode: true});
|
||||
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue