下载
环境要求
PHP版本:
安装
1. 服务器:推荐使用阿里云服务器,比较稳定,传送阵:阿里云服务器
2. 环境部署:推荐使用宝塔面板
来安装,一键PHP环境部署,送你10850元礼包,点我领取
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.php
tab.php
search.php
不存在时,将默认调用index.php
模板login.php
不存在时将默认调用/lib/admin/login.php
sidebar.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
12123123
这个程序的亮点不就是不用数据库吗…
有关于excerpt的么,主页想弄个摘要
每篇文章都有摘要,字段是使用的字段是intro,首页可以放置摘要的,
程序非常好
扩展性很强
数据量负载有多大?有mysql版本吗
目前负载量没有限制,只要你的内存够大就行。
666
遇见晚了,哎哎,不然我铁定折腾一波。
趁现在年轻,在折腾折腾吧
找寻多年终于找到一款简洁明了的系统,加油