下载
环境要求
PHP版本:
安装
1. 服务器:推荐使用阿里云服务器,比较稳定,传送阵:阿里云服务器,本链接可领取9折优惠券
2. 环境部署:推荐使用宝塔面板来安装,一键PHP环境部署,传送阵:宝塔服务器面板,一键全能部署
3. 将下载的程序代码解压到你的网站根目录,直接运行你的网站,会自动跳转到安装页面
4. 在安装页面输入相关信息和登录密码,点击提交后会进入首页
警告:本程序严禁用于非法用途,请遵守相关法律法规,使用者因违反本声明的规定而触犯中华人民共和国法律的,一切后果自己负责,本站不承担任何责任。
升级
进入后台,点击网站首页按钮,可查看系统的当前版本号,如果当前版本较低时,会自动弹出新版本更新提示窗,点击更新即可,升级前请做好网站备份,避免升级造成不必要的数据损失。
提示1:系统同步也是更新的一种,会与官网最新的核心文件同步,从而达到系统版本的更新。
提示2:数据库版本同步指的是你当前的数据库版本将与官方最新版本同步,这里的版本主要指的是数据字段结构,系统升级有时候会增加数据库字段,一般在线更新的时候会自动更新,这里的数据库版本并不是指的数据库数据同步哦,官网并不会收集你数据库的数据。
文件结构
┌─index.php 首页入口
│
├─db 数据库
│ ├─article 文章列表数据
│ ├─comment 文章评论数据
│ ├─upload 文章上传数据
│ ├─article.php 文章列表
│ ├─conf.php 网站配置
│ ├─error.php 错误日志列表
│ ├─ini.php 用户配置
│ └─list.php 数据库字段列表
│
├─ext 扩展
│
├─lib 函数类库
│ ├─admin 后台管理页面
│ │ ├─article.create.php 文章创建页
│ │ ├─article.editor.php 文章编辑页
│ │ ├─article.php 文章管理页
│ │ ├─category.php 分类设置页
│ │ ├─error.php 错误日志页
│ │ ├─ext.php 扩展管理页
│ │ ├─footer.php 模板尾部文件
│ │ ├─header.php 模板头部文件
│ │ ├─index.php 后台首页
│ │ ├─install.php 系统安装页
│ │ ├─link.php 友情链接页
│ │ ├─login.php 后台登录页
│ │ ├─navbar.php 后台登录页
│ │ ├─prompt.php 提示页
│ │ ├─setting.php 基础设置页
│ │ └─tpl.php 模板管理页
│ │
│ ├─style 公共样式
│ │ ├─admin.css 后台样式
│ │ ├─fk.css fk样式
│ │ ├─common.js 公共脚本
│ │ └─logo.svg LOGO图标
│ │
│ ├─common.php 公共方法
│ ├─file.util.class.php 文件操作类
│ ├─fk.class.php fk标记语言类
│ ├─function.php 函数方法集合
│ ├─tpl.class.php 模板编译类
│ ├─upload.class.php 上传类
│ └─vcode.class.php 验证码类
│
└─tpl 主题模板
└─default
├─style 样式文件夹
│ └─main.css 样式
│
├─conf.php 模板配置文件
├─footer.php 模板尾部文件
├─header.php 模板头部文件
├─icon.png 模板缩略图
├─index.php 首页
├─page.php 详情页
├─prompt.php 提示页
├─search.php 搜索页
├─category.php 分类页
├─sidebar.php 模板侧栏文件
└─tag.php 标签页
主题模板开发
tpl文件夹下default 主题模板文件夹,名称不能包含中文,不能为纯数字,应放在tpl文件夹中 │----------------------------------------------------------- ├─style 样式文件夹,存放css、js等文件(非必须) ├─img 图片文件夹,存放图片文件(非必须) │----------------------------------------------------------- ├─conf.php 模板配置文件(必须) ├─icon.png 模板缩略图(必须) ├─index.php 首页(必须) ├─page.php 详情页(必须) ├─prompt.php 提示页(必须) ├─header.php 模板头部文件(非必须) ├─footer.php 模板尾部文件(非必须) ├─login.php 登录页(非必须) ├─search.php 搜索页(非必须) ├─category.php 分类页(非必须) ├─tag.php 标签页(非必须) ├─sidebar.php 模板侧栏文件(非必须) ├─install.php 安装文件(非必须) └─uninstall.php 卸载文件(非必须)
style文件夹中img文件夹中footer.php中应注明清雨的版权,指定链接为:https://prain.cn除了文件结构中标注的必须文件,其它的非必须文件不一定要存在,看你如何开发了。
category.phptab.phpsearch.php不存在时,将默认调用index.php模板login.php不存在时将默认调用/lib/admin/login.phpsidebar.php属于页面引用的侧栏公共文件,如果你的模板并不存在侧栏,也无需创建该文件可有可无,如果存在该文件,install安装文件会在使用新主题的时候执行一次,一般用于提前创建好数据库之类的操作。
该文件可有可无,如果存在该文件,uninstall卸载文件会在使用新主题的时候执行一次,一般用于清除旧主题造成的数据垃圾。
<?php
// 配置文件
return array (
'id' => '主题模板的ID',
'type' => '应用类型,主题为tpl,扩展为ext',
'author' => '作者,开发者的账号',
'name' => '主题模板的名称',
'intro' => '主题模板的简述',
'price' => '主题模板价格,0为免费,仅支持2位小数',
'home' => '主题模板的主页',
'version' => '版本号,格式为1.0.0',
'limit' => '限制清雨的最低版本号,格式为1.0.0',
'depend' => '依赖的应用ID,空格隔开'
);
?>
如果清雨提供的默认页面并不能满足你的需求,那么你可以通过main.php文件来设置更多的页面,main.php在主题模板中可有可无,如果存在的话,它会被当做公共文件引入到系统中,不管进入任何页面,都会优先执行main.php的代码,所以main.php中不但可以设置更多的页面,引入更多的自定义类库,你甚至可以完全覆盖prain自身的页面业务逻辑。
如何编写main.php中的代码呢?我们举个例子,当你需要设置某个自定义的页面时,例如一个demo页面,你想访问的链接是:https://example.com/demo,这个时候demo的页面名称可以通过$page来获得,$page是个全局变量,可以获得网址/后面的第一个参数(prain中的url参数是通过/来分割的),有了$page就可以很方便的写demo页面的逻辑,以及引用模板中的demo.php页面,示例如下:
main.php文件
<?php
// demo页面
if($page == 'demo'){
$info = [
'title' => '静默的流年',
'content' => '站在青春的夕阳下,感受着过往,留下浅浅的笑靥,或许这就是青春的流逝吧!'
];
include $tpl->view('demo');
}
?>
demo.php文件
<h3> {$info.title} </h3>
<p> {$info.content} </p>
上面的示例是不是很简单?main.php很强大,可以内置任何你想要的功能或页面,上面我们说到了$page这个变量,默认它获取的是URL第1个参数,如果你需要获取第2个、第3个参数,那么你可以通过get()这个方法来获取,在prain中,get()用来获取url上的参数,post()用于获取form表单提交过来的参数,这两个方法能帮助你便捷的处理表单操作。
这两个方法位于lib/function.php中,function.php包含了开发者常用的封装函数,具体请查阅该文件,也可以查阅本文档中的【函数列表】
主题模板也继承了扩展的开发方式,支持main.php和common.php文件的编写,具体可参考本文档【扩展应用开发】
上面的demo.php示例中我们用到了花括号来输出变量内容,这是prain内置的模板编写能力,{$info.title}等同于<?php echo $info['title'];?>,当然你也可以将{$info.title}写成{$info['title']}(我想没人会这么费力不讨好),具体请看下面的模板语法。
编写模板时我们常常使用花括号来输出内容,它的工作原理是将你的主题模板编译成php原生格式,编译后的文件会放入当前主题模板的compile文件夹中,所以我们在开发主题模板时不要使用compile文件夹来存放主题文件。
下面是关于模板标签的一下用法。
| 标题 | 语法 | 说明 |
|---|---|---|
变量 |
|
|
输出其它内容 |
|
|
插入url链接 |
|
举例: |
IF判断 |
| 简单的if语句无需使用括号,多条件判断可以使用。
|
循环遍历 |
{foreach $articleList}
<p> 条目:{$index} </p>
<p> KEY:{$key} </p>
<p> {$item.title} </p>
<p> {$item.intro} </p>
{/foreach}
{foreach $articleList as $article}
<h3> {$article.title} </h3>
<p> {$article.intro} </p>
{/foreach}
{foreach $articleList as $name => $article}
<h3> URL的名称:{$name} </h3>
<h3> {$article.title} </h3>
<p> {$article.intro} </p>
{/foreach} | 支持这三种遍历方式,这里请注意,只有第一种foreach方式内置三个变量,分别如下:
|
引入模板 |
|
|
原生语句 |
{{
$info = [
'title' => '静默的流年',
'content' => '留下浅浅的笑靥,或许这是青春的流逝!'
];
$info['author'] = '闫立峰';
echo $info['title'];
echo $info['content'];
echo $info['author'];
}}
{{
$info = [
'title' => '静默的流年',
'content' => '留下浅浅的笑靥,或许这是青春的流逝!'
];
$info.author = '闫立峰';
echo $info.title;
echo $info.content;
echo $info.author;
}} | 当我们需要在模板文件中编写很长的php原生代码时,我们可以用两个花括号来写,当然你也可以继续使用原生的 但是,上面的 |
| 标签 | 返回类型 | 说明 |
|---|---|---|
V | | 系统版本号 |
ROOT | | 根目录的绝对路径 |
HOME | | 根目录的相对路径 |
URL | | 根目录的相对路径与伪静态结合 |
DB | | 数据目录的绝对路径 |
EXT | | 扩展目录的绝对路径 |
LIB | | 类库目录的绝对路径 |
TPL | | 当前主题模板的相对路径 |
TPLPATH | | 当前主题模板的绝对路径,包含项目的整个路径 |
TPL_STYLE | | 当前主题模板的style相对路径 |
TPL_IMG | | 当前主题模板的img相对路径 |
LIB_STYLE | | 公共style相对路径 |
LOGIN | | 登陆状态,true为登录 false为未登录 |
DEBUG | | 0:线上模式(无错)1:调试模式(无错+日志)2:开发模式(报错+日志) |
| 标签 | 返回类型 | 说明 |
|---|---|---|
$host | | 博客网址 |
$conf | | 网站配置信息 |
$tpl | | 模板类 |
$util | | 文件操作类 |
$ini | | 用户配置 |
$method | | 请求方式:POST、GET |
$url | | 当前地址 |
$ajax | | 是否为Ajax请求 |
$page | | 当前页面(index、category、tag、search、page、prompt、admin) |
$extList | | 已安装的扩展列表 |
$articleList | | 文章列表 |
$categoryList | | 分类列表 |
$tagList | | 标签列表 |
$navbarList | | 导航菜单列表 |
$linkList | | 友情链接列表 |
$settingPage | | 当前应用的设置页,存在为设置页路径,不存在为空字符串 |
| 标签 | 返回类型 | 说明 |
|---|---|---|
{$conf.title} | | 网站的title标题 |
{$conf.name} | | 网站头部标题 |
{$conf.intro } | | 网站描述 |
{$conf.mood} | | 心情记录 |
{$conf.key} | | SEO网站关键字 |
{$conf.desc} | | SEO网站描述 |
{$conf.brief} | | 新建文章时的描述限制字数 |
{$conf.password} | | 网站登录密码 |
{$conf.tpl} | | 主题模板 |
{$conf.compile} | | 模板自动编译 |
{$conf.debug} | | 等同于全局变量DEBUG |
{$conf.rewrite} | | 伪静态 |
{$conf.article.paging} | | 文章分页设置,每页数量 |
{$conf.article.count} | | 文章总数量 |
{$conf.comment.restrict} | | 评论限制设置,每日限制的条数 |
{$conf.comment.paging} | | 评论分页设置,每页数量 |
{$conf.comment.count} | | 评论总数量 |
{$conf.vcode.open} | | 验证码开启 |
{$conf.vcode.width} | | 验证码宽度 |
{$conf.vcode.height} | | 验证码高度 |
{$conf.vcode.length} | | 验证码字符串数量 |
{$conf.icp} | | ICP备案号 |
{$conf.prn} | | 公安备案号 |
{$conf.views} | | 网站浏览量 |
{$conf.blacklist} | | IP黑名单 |
{$conf.tag} | | 标签集合 |
{$conf.ext} | | 扩展集合 |
{$conf.category} | | 分类集合 |
{$conf.navbar} | | 导航菜单集合 |
{$conf.link} | | 友情链接集合 |
{$conf.js} | | JS脚本代码 |
{$conf.install} | | 系统是否已安装 |
{$conf.db.version} | | 系统db版本 |
| 标签 | 返回类型 | 说明 |
|---|---|---|
{$pageNum} | | 当前页码,默认为1 |
{$pageSize} | | 文章每页条数 |
{$article.count} | | 文章总数量 |
{$article.list} | | 文章列表,字段详情请看下面的【文章详情页】 |
{$article.paging.html} | | 文章分页的HTML |
{$article.paging.count} | | 文章分页的文章总条数 |
{$article.paging.currentPage} | | 文章分页的当前页面 |
{$article.paging.countPage} | | 文章分页的总页码 |
{$article.paging.pageSize} | | 文章分页的每页条数 |
| 标签 | 返回类型 | 说明 |
|---|---|---|
{$cid} | | 分类ID |
{$category} | | 分类对象,字段详情请看下面的【分类】 |
{$pageNum} | | 当前页码,默认为1 |
{$pageSize} | | 文章每页条数 |
{$article.count} | | 文章总数量 |
{$article.paging} | | 文章分页的HTML |
{$article.list} | | 文章列表,字段详情请看下面的【文章详情页】 |
{$article.paging.html} | | 文章分页的HTML |
{$article.paging.count} | | 文章分页的文章总条数 |
{$article.paging.currentPage} | | 文章分页的当前页面 |
{$article.paging.countPage} | | 文章分页的总页码 |
{$article.paging.pageSize} | | 文章分页的每页条数 |
| 标签 | 返回类型 | 说明 |
|---|---|---|
{$tab} | | 标签名 |
{$pageNum} | | 当前页码,默认为1 |
{$pageSize} | | 文章每页条数 |
{$article.count} | | 文章总数量 |
{$article.paging} | | 文章分页的HTML |
{$article.list} | | 文章列表,字段详情请看下面的【文章详情页】 |
{$article.paging.html} | | 文章分页的HTML |
{$article.paging.count} | | 文章分页的文章总条数 |
{$article.paging.currentPage} | | 文章分页的当前页面 |
{$article.paging.countPage} | | 文章分页的总页码 |
{$article.paging.pageSize} | | 文章分页的每页条数 |
| 标签 | 返回类型 | 说明 |
|---|---|---|
{$article.id} | | 文章ID |
{$article.cid} | | 分类ID |
{$article.category} | | 分类对象,字段详情请看下面的【分类】 |
{$article.title} | | 文章标题 |
{$article.intro} | | 文章描述 |
{$article.img} | | 文章中的第一张图片 |
{$article.content} | | 文章内容 |
{$article.isTop} | | 文章是否置顶 |
{$article.isPrivate} | | 文章是否为私密 |
{$article.isComment} | | 文章是否可以评论 |
{$article.isFk} | | 文章内容是否使用的FK标记语言 |
{$article.comments} | | 文章评论数 |
{$article.views} | | 文章浏览数 |
{$article.tag} | | 文章标签对象,字段详情请看下面的【标签】 |
{$article.time} | | 文章创建时间,通createTime |
{$article.updateTime} | | 文章更新时间 |
{$article.createTime} | | 文章创建时间 |
{$article.url} | | 文章的URL链接 |
{$article.prev} | | 上一篇文章,存在时为文章对象,包含$article的当前所有字段,不存在时为false |
{$article.next} | | 下一篇文章,存在时为文章对象,包含$article的当前所有字段,不存在时为false |
{$comment.count} | | 当前文章的评论总数 |
{$comment.list} | | 当前文章评论列表,字段详情请看下面的【评论板页】 |
{$comment.html} | | 当前文章评论列表的HTML |
{$comment.paging.html} | | 评论分页的HTML |
{$comment.paging.count} | | 评论分页的文章总条数 |
{$comment.paging.currentPage} | | 评论分页的当前页面 |
{$comment.paging.countPage} | | 评论分页的总页码 |
{$comment.paging.pageSize} | | 评论分页的每页条数 |
| 标签 | 返回类型 | 说明 |
|---|---|---|
{$category.id} | | 分类ID |
{$category.name} | | 分类名称 |
{$category.intro} | | 分类简介 |
{$category.count} | | 分类下的文章数量 |
{$category.url} | | 分类的链接 |
| 标签 | 返回类型 | 说明 |
|---|---|---|
{$tag.name} | | 标签名称,多标签以空格分割 |
{$tag.html} | | 标签html列表,a标签列表,包含url跳转 |
{$tag.list} | | 标签列表对象,包含:name、html、url字段 |
| 标签 | 返回类型 | 说明 |
|---|---|---|
{$comment.id} | | 评论ID |
{$comment.pid} | | 父级评论的ID,默认为0 |
{$comment.admin} | | 是否为管理员的评论 |
{$comment.content} | | 评论内容 |
{$comment.ip} | | 评论IP |
{$comment.time} | | 评论时间 |
<!DOCTYPE html>
<html lang="zh-Hans">
<head>
<!-- hook.head_header -->
<meta http-equiv="Content-Type" content="text/html" charset="UTF-8"/>
<title>{$conf.title}</title>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"/>
<meta name="keywords" content="{$conf.key}"/>
<meta name="description" content="{$conf.desc}"/>
<!-- hook.meta -->
<link rel="shortcut icon" href="{#LIB_STYLE}logo.png"/>
<link rel="stylesheet" href="{#LIB_STYLE}fk.css"/>
<link rel="stylesheet" href="{#TPL_STYLE}main.css"/>
<!-- hook.css -->
<script src="{#LIB_STYLE}common.js"></script>
<!-- hook.script -->
<!-- hook.head_footer -->
</head>
<body>
<!-- hook.body_header -->
<!-- hook.body_footer -->
</body>
</html>
开发主题模板过程中,页面请务必保留这几个扩展标签,它以html的注释形式存在,前缀为hook.,它的作用是提供扩展机制的支持,每个扩展标签的位置如上面代码所示,说明如下:
<!-- hook.head_header --> 放在head标签中的最上面。
<!-- hook.meta --> 放在head标签中meta标签的下面。
<!-- hook.css --> 放在head标签中link标签的下面。
<!-- hook.script --> 放在head标签中script标签的下面。
<!-- hook.head_footer --> 放在head标签中的最下面。
<!-- hook.body_header --> 放在body标签的最上面。
<!-- hook.body_footer --> 放在body标签的最下面。
扩展应用开发
ext文件夹下default 扩展文件夹,名称不能包含中文,不能为纯数字,应放在ext文件夹中 ├─conf.php 配置文件(必须) ├─icon.png 扩展的图标,300*300(必须) ├─main.php 扩展入口文件(非必须) ├─common.php 扩展公共文件(非必须) ├─setting.php 设置页面文件(非必须) ├─install.php 安装文件(非必须) └─uninstall.php 卸载文件(非必须)
userPassword
<?php
// 配置文件
return array (
'id' => '扩展的ID',
'type' => '应用类型,主题为tpl,扩展为ext',
'author' => '作者,开发者的账号',
'name' => '扩展的名称',
'intro' => '扩展的简述',
'price' => '扩展的价格,0为免费,仅支持2位小数',
'home' => '扩展的主页',
'version' => '版本号,格式为1.0.0',
'limit' => '限制清雨的最低版本号,格式为1.0.0',
'depend' => '依赖的应用ID,空格隔开'
);
?>
该文件可有可无,与tpl中的main.php一个概念,用于配置页面路由和业务逻辑,不同的是该文件的权重要高于tpl的main.php权重,在prain系统中,我们推荐开发独立的功能使用扩展,如果你只是为了丰富页面的多样化,那么建议写在tpl中。
该文件可有可无,common是扩展的公共文件,如果存在该文件,则每个页面都会引用,一般用于操作hook,例如后台添加菜单,那么应当写在common中,而不是main中,这是唯一与main有区别的地方,common的权重要高于main,common适合写hook,main适合写页面路由以及业务逻辑。
该文件可有可无,setting是扩展的配置页面文件,如果存在该文件,扩展管理中的该扩展中会有设置按钮,点击按钮进入该setting页面,一般用于扩展功能所需要配置的设置页面,当然,你也可以直接通过main来自定义页面和路由。
该文件可有可无,如果存在该文件,install安装文件会在扩展安装的时候执行一次,一般用于提前创建好数据库之类的操作。
该文件可有可无,如果存在该文件,uninstall卸载文件会在扩展卸载的时候执行一次,一般用于清除扩展造成的数据垃圾。
通常页面的开发就是引入当前主题的header.php和footer.php文件,这样打开的页面与主题风格会更加贴近,用户体验更好,那么你的页面中可以这么写:
main.php文件
//实例化模板编译
$demoTpl = new Tpl([
'path' => '/ext/demo/',
'name' => 'view',
]);
//demo页面
if($adminPage == 'demo'){
$content = '上善若水';
include $demoTpl->view('manage');
}
demo.php文件,位于文件夹:/ext/demo/view/
{include admin header}
<div class="title">demo页面</div>
<article>{$content}</article>
{include admin footer}
同样利用上面的方式来做,唯一不同的是demo.php文件中include后面的admin改为tpl,如下:
{include tpl header}
<div class="title">demo页面</div>
<article>{$content}</article>
{include tpl footer}
扩展应用的是hook钩子机制,具体hook钩子位置请查看lib/admin视图文件的注视标签,以hook为前缀,例如<!-- hook.admin_index_header--><!-- hook.admin_model_link--><!-- hook.model_index--><!-- hook.css-->,该标签你可以这样来理解,hook为前缀的说明是扩展的hook,点后面的admin_index_header为hook名称。
hook名称多语言以下划线分割,具体含义如下所示:
admin前缀表示后台视图层钩子,用于插入html
admin_model前缀表示后台业务层钩子,用于业务上的操作
model前缀前面无admin,表示前台业务层钩子,用于业务上的操作
其它表示前台视图层钩子,用于插入html
使用hook之前,先了解一下hook函数吧!
$name [string] [必填] hook名称 $html [string|function] html或业务处理方法
//common.php文件
<?php
//前端页面添加a1样式文件
hook('css','/ext/demo/style/a1.css');
//后端页面添加a2样式文件
if($page == 'admin'){
hook('admin_css','/ext/demo/style/a2.css');
}
?>
<?php
//后台侧栏菜单添加一个新菜单
hook('admin_sidebar_menu_3','<a href="/admin/demo">新菜单一</a>');
//也可以通过function来插入一个新菜单
hook('admin_sidebar_menu_3',function(){
return '<a href="/admin/demo">新菜单二</a>';
});
?>
业务层hook钩子,这里需要看index.php文件中的钩子位置,结合当前的环境变量通过global来处理数据。
<?php
//首页文章列表,添加一个序号字段,前端模板的文章列表就可以使用这个字段
//当前环境变量有$article(文章对象)
hook('model_index',function(){
global $article;
//遍历文章列表,添加order字段
$order = 1;
foreach($article['list'] as $key => &$value){
$value['order'] = $order++;
}
});
?>
配置页开发
无论是开发扩展应用还是主题模板,当需要保存配置信息的时候,我们可以使用ini函数来保存或获取配置信息,接下来是关于ini函数的介绍。
$name [string] [必填] 唯一名称,一般为扩展或主题的id $key [string|array] 获取或保存的key $value [mix] 需保存的数据
//设置a=1
ini('demo','a',1);
//如果第二个参数为数组,则以追加的方式设置b=2,c=3
ini('demo',['b'=>2,'c'=>3]);
//设置d=4,e=5,与上面不同的是如果第三个参数为true,则先清空再设置
ini('demo',['d'=>4,'e'=>5],true);
//获取demo中的所有数据
ini('demo');
//获取demo中的a数据
ini('demo','a');
//删除demo
ini('demo',false);
//ini('demo','title','上善若水');
{$ini.demo.title}
//ini('demo','config',['name'=>'濛濛细雨','intro'=>'没有什么不同']);
{$ini.demo.config.name}
{$ini.demo.config.intro}
ini函数一般用在install.php、uninstall.php或main.php中,例如我们开发的主题模板中有广告图,后台需要拥有一个广告图设置功能,那么我们就需要将上传的图片链接保存起来,如下:
第一步:安装时需要增加一个img字段,install.php文件:
<?php
//为你的主题demo增加img轮播图调用字段,用于存储图片列表
ini('demo','img','/tpl/demo/img/1.jpg');
?>
第二步:卸载时需要删除主题demo用到的所有字段,避免数据垃圾的产生,uninstall.php文件:
<?php
//参数为false的时候会清空demo的所有字段
ini('demo',false);
?>
第三步:安装和卸载已经做好img字段的处理,这一步做配置页的表单操作,main.php文件:
<?php
//判断是否为设置页
if($settingPage['demo']){
//POST表单处理
if($method == 'POST'){
//接收由前端传过来的img参数数据
$img = post('img');
if($img){
//保存配置
ini('demo','img',$img);
//如果前端通过ajax提交的,返回数据一般用到 msg(返回成功消息)、err(返回错误消息)、ret(返回数据消息) 这三个函数
msg('保存成功');
}
err('请上传图片');
}
}
?>
第四步:后台配置页面,setting.php文件:
<?php exit('404');?>
{include admin header}
<div class="title">广告图设置</div>
<div id="imgSetting">
<input type="hidden" name="img" value=""/>
<img src="{$ini.demo.img}" alt="广告图"/>
<div class="btn" onclick="uploadImg">上传</div>
<input type="submit" value="提交"/>
</div>
<script>
function uploadImg(){
sx.upload({
success(res){
sx('#imgSetting input').val(res[0]);
sx('#imgSetting img').attr('src',res[0]);
}
})
}
function submit(){
sx.ajax('{$url}','#imgSetting').then(res => {
sx.pop(res.message)
})
}
</script>
{include admin header}
数据库操作
prain中的数据库操作只需要记住db()、dbSave()、dbDelete()、dbUpdate()这四个方法,以下是这些方法的使用说明。
$name [string] [必填] 表名 $cond [array] [选填] 条件查询 $orderby [array] [选填] 排序 $page [int] [选填] 页数 $pagesize [int] [选填] 每页条数
如果该数据库为二维数组,可通过以下条件查询来获取相应的数据
//我们用article表(位于db文件夹中的article.php文件,存放着文章列表数据)来说明db()的用法
//返回文章的所有数据
db('article');
//返回所有被置顶的文章
db('article', ['top' => 1]);
//返回url名称为fk的这篇文章
db('article', ['name' => 'fk']);
//返回所有的文章,并先按照最新的时间排序,再按照置顶排序
db('article', [], ['time' => 1, 'top' => 1]);
//返回文章的第2页数据,条数为30,并先按照最新的时间排序,再按照置顶排序,
db('article', [], ['time' => 1, 'top' => 1], 2, 30);
//相信以上条件查询可以满足大多数业务了,但仍然无法体现到db()方法的优秀,接下来我们玩玩更高级的`$cond`查询方式
//
//搜索文章标题有`雨烟`两字的所有数据
db('article', ['title' => ['LIKE' => '雨烟']]);
//查询文章浏览量大于100的所有数据,支持> < >= <= ==这五种判断
db('article', ['views' => ['>' => 100]]);
//查询文章拥有`笔记`标签的所有数据,说白了就是针对三维数组的查询,tag字段存储的是数组,而上面的LIKE是用于字段为字符串类型的查询
db('article', ['tag' => ['IN' => '笔记']]);
//返回置顶的文章,且浏览量大于100的所有数据
db('article', ['top' => 1, 'views' => ['>' => 100]]);
$name [string] [必填] 表名 $data [string|array] [必填] 保存数据
$arr = [
[
'title' => '半载浮生',
'content' => '既不回头,何必不忘。既然无缘,何须誓言。今日种种,似水无痕。明夕何夕,君已陌路。'
],
[
'title' => '静默的流年',
'content' => '站在青春的夕阳下,感受着过往,留下浅浅的笑靥,或许这就是青春的流逝吧!'
]
];
//如果product表不存在,则会创建
dbSave('product',$arr);
$name [string] [必填] 表名 $data [string|array] [必填] 插入的新数据 $index [bool] [选填] 是否根据索引新增数据
$arr = [
'title' => '半载浮生',
'content' => '既不回头,何必不忘。既然无缘,何须誓言。今日种种,似水无痕。明夕何夕,君已陌路。'
];
//在product表中插入一条新数据
dbInsert('product', $arr);
$name [string] [必填] 表名 $cond [array] [选填] 条件删除,使用方式参考db()的$cond参数 $index [bool] [选填] 是否重新索引,数组的key从0开始,默认不重新索引
//删除整个product表数据
dbDelete('product');
//删除product表id字段为1的这条数据
dbDelete('product', ['id' => 1]);
$name [string] [必填] 表名 $cond [array] [必填] 条件更新 $data [string|array] [必填] 更新数据
$arr = [
'title' => '半载浮生',
'content' => '既不回头,何必不忘。既然无缘,何须誓言。今日种种,似水无痕。明夕何夕,君已陌路。'
];
//更新product表id字段为1的这条数据
dbUpdate('product', ['id' => 1], $arr);
//更新product表所有数据的top为1
dbUpdate('product', [], ['top' => 1]);
如果数据表demo为一维数组,例如为这样的数据格式
[
'title' => '这是标题',
'content' => '今生有缘'
];
那么更新demo表的title字段就可以这样做,第二个参数不再是条件查询,而是指定字段数据更新,第三个参数勿传
dbUpdate('demo', ['title' => '不错,这就是标题']);
前端函数库
common.js的sx是一个快速、简洁的JavaScript框架,它封装了JavaScript常用的功能代码,提供一种简便的JavaScript设计模式,优化HTML文档操作、事件处理、动画设计和Ajax交互,如果你很熟悉jQuery的话,那么本文档对你来说是非常易懂的。
核心特性可以总结为:
在jQuery中选择器用$,为了避免冲突,清雨使用sx,例如给某个元素添加一条class,jQuery中:$('.title').addClass('.light'),那么在清雨中:$('.title').addCss('.light'),有些方法名称是与jQuery一样的,有些则不同,下面介绍一下公共方法。
语法: sx(string,object)
说明: 使用方式与jQuery一样,支持各种组合方式,方法返回文档中匹配指定CSS选择器的所有元素,例如:
//选择器-具体也可参照原生的querySelectorAll
sx('a',object) //获取object对象下的所有a标签对象
sx('.test') //类选择器
sx('#test') //ID选择器
sx('#test span') //组合选择器
sx('#test .demo') //组合选择器
sx('.demo span') //组合选择器
sx('#test .demo a') //组合选择器
sx('div.item') //获取文档中所有 class="item" 的div元素
sx('div[data-active]') //获取文档中包含 "data-active" 属性的div元素
sx('div[data-active="1"]') //获取文档中包含 "data-active=1" 属性的div元素
sx('input[type=text]') //获取文档中type类型为text的input元素
sx('input[name=demo]') //获取文档中name类型为demo的input元素
sx('div > p') //获取每个父元素为div里的p元素
sx('div,p,span') //获取所有的div,p,span元素
//加载完成-页面加载完成后,执行该方法,等同sx.ready()方法!
sx(function(){
console.log('hello world!');
})
语法: sx(selector).each(function(index,element,elementAll,length))
index {int} 选择器的 index 位置
element {node} 当前的元素(也可使用 "this" 选择器)
elementAll {node} 当前遍历的元素集合
length {int} 元素集合的总长度
提示: function的this指的遍历的当前对象
sx('.demo p').each(function(i,el){
//i 为当前对象的索引值
//el 为当前对象,等同于this
//给第三个p元素下的span元素添加点击事件
if(i == 2){
//此时的this为当前的p元素对象
sx('span',this).click(funciton(){
//此时的this为当前的span元素对象,不要混淆哦!
console.log(this);
});
}
});
语法: sx(selector).i(index)
//获取第三个p元素的html
sx('.demo p').i(2).html();
语法: sx(selector).val(value)
提示: 元素的值是通过 value 属性设置的。该方法大多用于 input 元素。但也支持select和textarea!
//获取值
sx('#input').val();
//设置值
sx('#input').val('hello world');
语法: sx(selector).html(content)
提示: 获取集合中第一个匹配元素的HTML内容 或 设置每一个匹配元素的html内容。
//获取该对象的html内容
sx('.demo').html();
//设置该对象的html内容
sx('.demo').html('<div>hello world</div>');
//设置该对象的html内容,可传入动态创建的DOM
var div = sx.c('div');
div.innerHTML = 'hello world';
sx('.demo').html(div);
语法: sx(selector).form()
<div id="demo">
<input type="text" name="title" value="标题"/>
<textarea name="content" rows="3" cols="20">内容</textarea>
</div>
<script>
console.log(sx('#demo').form());
//结果返回json格式对象
//{
// title: "标题",
// content: "内容"
//}
</script>
语法: sx(selector).append(content)
sx('.demo').append('<div>hello world</div>');
语法: sx(selector).prepend(content)
sx('.demo').prepend('<div>hello world</div>');
语法: sx(selector).before(content)
sx('.demo').before('<div>hello world</div>');
语法: sx(selector).after(content)
参数: content {string|object} DOM元素,文本节点,HTML字符串
sx('.demo').after('<div>hello world</div>');
语法: sx(selector).page()
sx('.demo').page();
语法: sx(selector).getCss(attr)
//获取该对象的左外边距
sx('.demo').getCss('marginLeft');
语法: sx(selector).del()
<div class="demo">
<p>我爱php</p>
<p>我爱java</p>
<p>我爱c++</p>
</div>
//删除class为demo元素下的第二个p
sx('.demo p').s(1).del();
//删除class为demo元素下的所有p
sx('.demo p').del();
语法: sx(selector).offset()
返回: object格式 {left:x, top:y}
语法: sx(selector).css(name, value)
语法: sx(selector).attr(name, value)
语法: sx(selector).delAttr(name)
语法: sx(selector).delCss(name)
语法: sx(selector).clone(fn)
语法: sx(selector).click(fn)
语法: sx(selector).on(type, fn, bool)
语法: sx(selector).find(selector)
语法: sx(selector).children(selector)
语法: sx(selector).childrens()
语法: sx(selector).siblings(selector)
语法: sx(selector).prev(selector)
语法: sx(selector).next(selector)
语法: sx(selector).parent()
返回: 所有的祖先元素
语法: sx(selector).parents()
返回: 所有的祖先元素
语法: sx(selector).addCss(name)
语法: sx(selector).hasCss(name)
语法: sx(selector).hasStyle(name)
语法: sx(selector).toggleCss(nameA,nameB,fnA,fnB)
语法: sx(selector).show()
语法: sx(selector).hide()
语法: sx(selector).fadeIn(speed, callback)
语法: sx(selector).fadeOut(speed, callback)
语法: sx(selector).fadeToggle(speed, callback)
语法: sx(selector).slideDown(speed, callback)
语法: sx(selector).slideUp(speed, callback)
语法: sx(selector).slideToggle(speed, callback)
语法: sx.c(tagName)
语法: sx.sp(event)
语法: sx.pd(event)
语法: sx.data(name,value)
提示: 本方法利用sessionStorage存储数据数据,生命周期为当前窗口或标签页,一旦窗口或标签页被永久关闭了,那么所有通过sessionStorage存储的数据也就会被清空。
本方法的设计初衷是为了更加方便的使用,同时最主要的是打破sessionStorage的存储方式界限,可以存储类型的值,而不在是单纯的字符串,可以为number数字,array数组,object对象,function方法,请看下面使用示例:
/**
* 重要提示:赋值完全等于取值,如果赋值数字类型,那么取值时也为数字类型
* 如果赋值为json对象,那么取值也为对象,array数组和function方法同理
*/
//获取整个sessionStorage
sx.data();
//获取t1值
sx.data('t1');
//设置t1值
sx.data('t1',123);
//设置t1值,也可以为function方法,任何数据类型都支持
sx.data('t1',function(e){
console.log(e)
});
sx.data('t1')('我是function'); //打印出:我是function
//设置t1值,获取后也是该对象,不是字符串哦,如果你设置字符串类型的json,那么获取到的也是字符串类型的json
sx.data('t1',{t2:123,t3:456});
//同时设置t1、t2、t3值,
sx.data({t1:123, t2:456, t3:789});
语法: sx.localData(name,value)
提示: 本方法利用localStorage存储数据数据,生命周期是永久的,无时间限制,除非用户手动清除本地缓存。
使用方式同sx.data,数据存储方式也一样的。
语法: sx.isMobile()
语法: sx.obj2str(object)
语法: sx.ready(fn)
语法: sx.ajax(json)
/**
* json说明:
* url:请求地址
* type:请求类型,默认为post,可选值:post,get
* timeout:网络超时,默认为15000毫秒
* async:是否异步请求,默认为true
* header:请求头设置
* form:用于绑定需要提交表单的容器,传class或id,默认绑定id:form
* 如果该参数存在,无需使用data传参
* data:要求为Object或String类型的参数,发送到服务器的数据。
* json:是否将请求过来的数据自动转为json对象,默认为true,
* success:请求成功之后的回调函数
* error:请求失败之后的回调函数
* complete:不管请求成功还是失败,都会调用
*/
//第一种方式
sx.ajax({
url:'login',
data:{password:1234},
success(res){
if(res.error){
sx.pop(res.message);
}else{
sx.jump('admin');
}
}
});
//第二种方式,支持Promise,推荐使用
sx.ajax('login',{password:1234}).then(res=>{
if(res.error){
sx.pop(res.message);
}else{
sx.jump('admin');
}
});
语法: sx.pjax(json)
/**
* json说明:
* el:容器
* url:请求地址
* type:请求类型,默认为post,可选值:post,get
* loading:加载动画
* success:请求成功之后的回调函数
* error:请求失败之后的回调函数
* complete:不管请求成功还是失败,都会调用
*/
sx.pjax({el:'#pjax-content'});
语法: sx.scroll(pos)
返回: {x,y}
语法: sx.scrollAni(el, offset, duration)
语法: sx.keydown(key, callback)
//第一种方式,监听指定的按键
sx.keydown(13,function(){
console.log('我按了回车键');
})
//第一种方式也可以这么来
function hi(a=11){
console.log('我是'+a);
}
sx.keydown(13,hi);
sx.keydown(13,'hi(123)');
//第二种方式,监听所有的按键
sx.keydown(function(key){
console.log('我按了键盘,键盘码为:',key);
})
语法: sx.jump(url)
语法: sx.setDisplay(el)
语法: sx.getDisplay(el)
语法: sx.pop(options, time = 2000)
语法: sx.alert(options, yes, no)
语法: sx.prompt(options, yes, no)
语法: sx.confirm(url, text)
语法: sx.upload(options)
accept:限制的文件类型
url:请求地址,默认为官方的/upload
name:文件名称,默认为时间戳+序号命名
path:上传的路径,默认为/db/upload
absolutePath:返回的路径是否为绝对路径,默认true
success:请求成功之后的回调函数
error:请求失败之后的回调函数
complete:不管请求成功还是失败,都会调用
语法: sx.delArticle(id)
后端函数库
inputName [string] [选填] input控件的name值,如果为多文件上传,name后面要加[],默认为:file path [string] [选填] 上传路径,默认为:db/upload/当前年月/ nameType [string] [选填] 上传成功后的命名方式,可选值为name(以文件名称命名)、time(时间戳命名),默认为:time name [string|bool] [选填] 上传成功后指定命名,默认为:false size [int] [选填] 限制文件大小上传,默认为:100 ext [string|bool] [选填] 限制文件类型上传,多类型用|分割,默认为:false domain [bool] [选填] 上传成功后返回的文件路径是否带网址,默认为:true imgThumb [array] [选填] 如果上传的文件为图片,是否生成缩略图,默认为:false imgThumb['width'] [int] [选填] 缩略图的宽,默认为:300 imgThumb['height'] [int] [选填] 缩略图的高,默认为:300 imgThumb['clip'] [bool] [选填] 是否裁剪,true:裁剪缩略 false:全图等比例缩略,默认为:true
//例如上传图片,前端图片上传的URL地址为:https://xueluo.cn/uploadImage
//后端:你的应用中main.php可以这些来接收图片的上传:
if($page == 'uploadImage'){
$arr = upload([
'name' => 'logo.jpg', //上传的文件命名为logo.jpg,不传该参数将以时间戳命名
'ext' => 'jpg', //限制上传类型为jpg文件
'path' => 'ext/demo/img', //上传到服务期的文件路径,不传该参数默认上传到db/upload中
'imgThumb' => [ //生成缩略图,裁剪缩略
'width' => 300,
'height' => 300
],
]);
exit(type($arr,'json'));
}
//前端:你的应用中setting.php中可以这样写
<script>
//上传图片函数,给按钮添加点击事件,执行upload()即可
function upload(){
sx.upload({
url:'uploadImage', //请求地址,不传默认为upload
success(e){ //上传成功的回调
console.log(e)
}
})
}
</script>
$imgUrl [string] 图片的完整路径 $width [int] 缩略图宽度 $height [int] 缩略图高度 $clip [bool] true:裁剪缩略 false:全图等比例缩略 $pre [string] 缩缩略图前缀
$thumb = imgThumb('/db/upload/20230822155724.jpg', 100, 100);
echo $thumb; //生成的缩略图地址为: /db/upload/thumb_20230822155724.jpg
$data [mixed] [必填] 数据 $type [string] [非必填] 转换类型,有效值:str|string|int|int|float|object|array|bool|json|stripTags|trim|urlencode|urldecode
//获取类型 type($data); //数据转为字符串类型 type($data,'str'); //数据转为数字类型 type($data,'int');
$key [mixed] [必填] 字段 $type [string] [非必填] 转换类型,同上type的$type参数 $def [mixed] [非必填] 数据为空时设置的数据
print_r(get());
/**
* 例子:http://xxx.xxx/index/page.html?a=1&b=2
* 返回
* array(
* 0 => index,
* 1 => page,
* a => 1,
* b => 2,
* )
*/
//返回index
get(0);
//返回1,默认为字符串类型
get(a);
//返回参数a的值,并转为int类型
$num = get('a','int');
//返回参数c的值,并转为int类型,如果改参数不存在,则返回3
$num = get('c','int',3);
使用方式同get(),只不过接收的是post表单提交过来的数据
$key [mixed] [必填] 字段 $type [string] [非必填] 转换类型,同上type的$type参数 $def [mixed] [非必填] 数据为空时设置的数据
$path [string] [必填] 文件路径,不存在则创建 $data [mixed] [必填] 要保存的数据
$error [bool] [必填] 是否报错 $data [mixed] [必填] 打印消息
$str [bool] [必填] 字符串 $type [mixed] [必填] 类型 $min [bool] [非必填] 最小值 $max [mixed] [非必填] 最大值
//示例
if(check($str,'str',5,12)){
echo '不是一个5-12的字符串';
}
type:
str //字母数字 - 支持字符的长度限制
num //纯数字 - 支持字符的长度限制
en //纯字母 - 支持字符的长度限制
zh //中文 - 支持字符的长度限制
name //中英文数字 - 支持字符的长度限制
length //字符串长度 - 支持字符的长度限制
ip //ip
date //日期
url //网址
pc //邮编
mail //邮箱
mp //手机号
tel //座机号
idCard //身份证号
$url [string] [非必填] 页面地址
例如:pages('user/list/{page}.html', 100, 10, 5);
$url [string] [必填] 分页链接 {page}为页码
$total [int] [必填] 数据总数
$page [int] [必填] 当前页
$pagesize [int] [非必填] 每页显示多少条数据
$timestamp [int] [非必填] 时间戳
$timestamp [int] [必填] 时间戳
$size [int] [必填] 字节大小
$path [string] [必填] 文件路径
$len [int] [必填] 字符长度
$arr [array] [必填] 数组 $key [int|string] [必填] 1:一维数组降序,0:一维数组升序,根据指定key排序 $desc [string] [非必填] 正序倒序
$arr [array] [必填] 数组 $cond [array] [必填] 条件 $orderby [array] [非必填] 排序 $page [int] [非必填] 当前页 $pagesize [int] [非必填] 每页数据条数
$url [array] [必填] 网络地址 $params [array] [必填] 参数 $method [array] [非必填] 请求类型 $cookie [int] [非必填] Cookie