mpvue小程序开发 渲染层网络层错误 问题的解决方法

直接上解决方法:

servicewechat.com 加入防盗链白名单问题解决

问题发现和排查过程

用mpvue开发小程序的时候, 遇到了渲染层网络层错误, 公司静态资源配置了防盗链功能, 使用location.href看到的也是 127.0.0.1 的本地地址, 本以为把127.0.0.1加入防盗链的白名单就可以了, 结果还是报这个错误, 比较扯淡的是这个控制台还看不到真正的图片请求, 最后用 fiddler 抓了一下包, 才发现refresh居然是 https://servicewechat.com/wx7d3c7a42f044667b/devtools/page-frame.html, 最后把 servicewechat.com 加入防盗链白名单问题解决.

调试工具报错信息:

fiddler请求头信息:

小程序客服消息处理

三种消息类型:

  1. // 文本消息
  2. {
  3. "signature": "ee6e400de7972484ec6f3014c2f77504925a4707",
  4. "timestamp": "1552298008",
  5. "nonce": "540004383",
  6. "openid": "oPzrj5EAP25lzOVW6qa0m8MUlLXA",
  7. "encrypt_type": "aes",
  8. "msg_signature": "74c419155e49cefab8ab1dbd440b9389acc47e2a",
  9. "URL": "http:\/\/qiang.lt.ngapp.net\/wechat\/noti/fy",
  10. "ToUserName": "zhaishuaigan",
  11. "FromUserName": "oPzrj5EAP25lzOVW6qa0m8MUlLXA",
  12. "CreateTime": 1552293166,
  13. "MsgType": "text",
  14. "Content": "hello",
  15. "MsgId": 1,
  16. "Encrypt": "PloOW0ucj9SkMpMC...."
  17. }
  18. // 事件消息
  19. {
  20. "signature": "3e5860da822266b9f03f8d5380615f9be0ae2db7",
  21. "timestamp": "1552301914",
  22. "nonce": "693467019",
  23. "openid": "oPzrj5EAP25lzOVW6qa0m8MUlLXA",
  24. "encrypt_type": "aes",
  25. "msg_signature": "eda0cdd567b35df4501ee2041e0d191db81798f2",
  26. "ToUserName": "gh_cc23a0a84984",
  27. "FromUserName": "oPzrj5EAP25lzOVW6qa0m8MUlLXA",
  28. "CreateTime": 1552301914,
  29. "MsgType": "event",
  30. "Event": "user_enter_tempsession",
  31. "SessionFrom": "open-type='contact'",
  32. "Encrypt": "oMP\/xo2RdcbK07vvoxxxx....."
  33. }
  34. // 图片消息
  35. {
  36. "signature": "ebd79f0c44f01dc26ef83ffeeadbdb3af5b960da",
  37. "timestamp": "1552302190",
  38. "nonce": "1322948029",
  39. "openid": "oPzrj5EAP25lzOVW6qa0m8MUlLXA",
  40. "encrypt_type": "aes",
  41. "msg_signature": "06613ca44e189d95140166898f80a27ab10baa80",
  42. "ToUserName": "gh_cc23a0a84984",
  43. "FromUserName": "oPzrj5EAP25lzOVW6qa0m8MUlLXA",
  44. "CreateTime": 1552302190,
  45. "MsgType": "image",
  46. "PicUrl": "http:\/\/mmbiz.qpic.cn\/mmbiz_jpg\/WwPGxFUiaTRZ6zVvTTGVbhfa9Vic801ux7OHobAAiaD3xRMYeJzbic4ISvwn736dpK0OMTlaYvX7GoTRgE8LqObkIQ\/0",
  47. "MsgId": 22223624286040849,
  48. "MediaId": "3WiHLigPtkJtqoLvPD2HgPsAtb3vzDvTWyo0sP5s-dshtS7oZOlCW7c3RuD1nwBt",
  49. "Encrypt": "gictSCCy+Zxxxx...."
  50. }

ThinkPHP 5.1保存消息代码

  1. $request = request()->param();
  2. $data = [
  3. 'signature' => $request['signature'],
  4. 'timestamp' => $request['timestamp'],
  5. 'nonce' => $request['nonce'],
  6. 'openid' => $request['openid'],
  7. 'encrypt_type' => $request['encrypt_type'],
  8. 'msg_signature' => $request['msg_signature'],
  9. 'to_username' => $request['ToUserName'],
  10. 'from_username' => $request['FromUserName'],
  11. 'msg_type' => $request['MsgType'],
  12. 'msg_id' => isset($request['MsgId']) ? $request['MsgId'] : '',
  13. 'encrypt' => isset($request['Encrypt']) ? $request['Encrypt'] : '',
  14. ];
  15. $msgInfo = [];
  16. switch ($request['MsgType']) {
  17. case 'text':
  18. $msgInfo['text'] = $request['Content'];
  19. break;
  20. case 'image':
  21. $msgInfo['pic'] = $request['PicUrl'];
  22. $msgInfo['media_id'] = $request['MediaId'];
  23. break;
  24. case 'event':
  25. $msgInfo['event'] = $request['Event'];
  26. $msgInfo['session_from'] = $request['SessionFrom'];
  27. break;
  28. }
  29. $data['msg_info'] = json_encode($msgInfo);
  30. CustomerMessage::create($data);
  31. return '';

数据库表结构设计

  1. <?php
  2. use think\migration\Migrator;
  3. use think\migration\db\Column;
  4. class CreateCustomerMessageTable extends Migrator
  5. {
  6. public function change()
  7. {
  8. $this->table('customer_message')
  9. ->addColumn(Column::string('signature')
  10. ->setDefault('')
  11. ->setComment('签名'))
  12. ->addColumn(Column::integer('timestamp')
  13. ->setDefault(0)
  14. ->setComment('时间戳'))
  15. ->addColumn(Column::string('nonce')
  16. ->setDefault('')
  17. ->setComment('随机数'))
  18. ->addColumn(Column::string('openid')
  19. ->setDefault('')
  20. ->setComment('客户id'))
  21. ->addColumn(Column::string('encrypt_type')
  22. ->setDefault('')
  23. ->setComment('加密方式'))
  24. ->addColumn(Column::string('msg_signature')
  25. ->setDefault('')
  26. ->setComment('消息签名'))
  27. ->addColumn(Column::string('to_username')
  28. ->setDefault('')
  29. ->setComment('消息接收者'))
  30. ->addColumn(Column::string('from_username')
  31. ->setDefault('')
  32. ->setComment('消息来源'))
  33. ->addColumn(Column::string('msg_type')
  34. ->setDefault('')
  35. ->setComment('消息类型'))
  36. ->addColumn(Column::text('msg_info')
  37. ->setNull(true)
  38. ->setComment('消息内容'))
  39. ->addColumn(Column::string('msg_id')
  40. ->setDefault('')
  41. ->setComment('消息id'))
  42. ->addColumn(Column::text('encrypt')
  43. ->setNull(true)
  44. ->setComment('加密数据'))
  45. ->addColumn(Column::dateTime('create_time')
  46. ->setDefault('CURRENT_TIMESTAMP')
  47. ->setComment('创建时间'))
  48. ->addColumn(Column::dateTime('update_time')
  49. ->setDefault('CURRENT_TIMESTAMP')
  50. ->setComment('更新时间'))
  51. ->addColumn(Column::dateTime('delete_time')
  52. ->setNull(true)
  53. ->setComment('删除时间'))
  54. ->create();
  55. }
  56. }

关于input file的文件过滤方法

限制只能选择图片: <input type="file" accept="image/*" />
限制只能选择视频: <input type="file" accept="video/*" />
限制只能选择音频: <input type="file" accept="audio/*" />
直接打开摄像头拍照: <input type="file" accept="image/*" capture="camera" />
直接打开摄像头录像: <input type="file" accept="video/*" capture="camera" />

一个markdown编辑器editor.md

Editor.md






Editor.md : The open source embeddable online markdown editor (component), based on CodeMirror & jQuery & Marked.

Features

README & Examples (English)


Editor.md 是一款开源的、可嵌入的 Markdown 在线编辑器(组件),基于 CodeMirror、jQuery 和 Marked 构建。

editormd-screenshot

主要特性

Examples

https://pandao.github.io/editor.md/examples/index.html

Download & install

Github download

Bower install :

  1. bower install editor.md

Usages

HTML:

```html


cms想法

关于字体图标

后台模块或按钮图标采用 http://www.iconfont.cn/ 的图标,
智能解析iconfont.css提取所有图标

关于模块

新建模块需要设置:
模块标识
模块名称
模块字段 (标识, 名称, 类型, 默认值, 新增验证函数, 更新验证函数)
  // 默认值可读取配置
  // 验证函数自动读取验证辅助类的方法和文档
列表显示字段
  // 字段显示有格式化方式, 自动读取格式化辅助类和文档
详情页显示字段
添加数据界面显示字段
编辑页面显示字段

排序字段和方式(asc|desc|自定义)
搜索字段
删除数据方式 (软删除, 直接删除)

验证辅助类

设计思想

  1. class Validate {
  2. public function username($value, $msg, $params...) {
  3. }
  4. }

分两个, 一个系统, 一个用户自定义

想到的辅助方法:
用户名,密码,邮箱,手机号,不能为空,长度限制,只允许英文,正则

通过获取类的注释, 在需要的地方列出验证方式列表

字段和数据格式化辅助类

设计思想

  1. class Format{
  2. public function toImgTag($value, $params....) {
  3. }
  4. }

分两个, 一个系统, 一个用户自定义

想到的辅助方法:
时间,状态,图片,关联表字段 (表名, 字段名, 分隔符)

通过获取类的注释, 在需要的地方列出格式化辅助方法列表

php内置服务器使用方法

1 启动Web服务器

  1. $ cd ~/public_html
  2. $ php -S localhost:8000

终端输出信息:

  1. PHP 5.4.0 Development Server started at Thu Jul 21 10:43:28 2011
  2. Listening on localhost:8000
  3. Document root is /home/me/public_html
  4. Press Ctrl-C to quit

当请求了 http://localhost:8000/http://localhost:8000/myscript.html 地址后,终端输出类似如下的信息:

  1. PHP 5.4.0 Development Server started at Thu Jul 21 10:43:28 2011
  2. Listening on localhost:8000
  3. Document root is /home/me/public_html
  4. Press Ctrl-C to quit.
  5. [Thu Jul 21 10:48:48 2011] ::1:39144 GET /favicon.ico - Request read
  6. [Thu Jul 21 10:48:50 2011] ::1:39146 GET / - Request read
  7. [Thu Jul 21 10:48:50 2011] ::1:39147 GET /favicon.ico - Request read
  8. [Thu Jul 21 10:48:52 2011] ::1:39148 GET /myscript.html - Request read
  9. [Thu Jul 21 10:48:52 2011] ::1:39149 GET /favicon.ico - Request read

2 启动web服务器时指定文档的根目录

  1. $ cd ~/public_html
  2. $ php -S localhost:8000 -t foo/

终端显示信息:

  1. PHP 5.4.0 Development Server started at Thu Jul 21 10:50:26 2011
  2. Listening on localhost:8000
  3. Document root is /home/me/public_html/foo
  4. Press Ctrl-C to quit

如果你在启动命令行后面附加一个php脚本文件,那这个文件将会被当成一个“路由器”脚本。这个脚本将负责所有的HTTP请求,如果这个脚本执行时返回FALSE,则被请求的资源会正常的返回。如果不是FALSE,浏览里显示的将会是这个脚本产生的内容。

3 使用路由器脚本

在这个例子中,对图片的请求会返回相应的图片,但对HTML文件的请求会显示“Welcome to PHP”:

  1. <?php
  2. // router.php
  3. if (preg_match('/\.(?:png|jpg|jpeg|gif)$/', $_SERVER["REQUEST_URI"])) {
  4. return false; // serve the requested resource as-is.
  5. } else {
  6. echo "<p>Welcome to PHP</p>";
  7. }
  8. ?>

执行:

  1. $ php -S localhost:8000 router.php

4 判断是否是在使用内置web服务器

通过程序判断来调整同一个PHP路由器脚本在内置Web服务器中和在生产服务器中的不同行为:

  1. <?php
  2. // router.php
  3. if (php_sapi_name() == 'cli-server') {
  4. /* route static assets and return false */
  5. }
  6. /* go on with normal index.php operations */
  7. ?>

执行:

  1. $ php -S localhost:8000 router.php

这个内置的web服务器能识别一些标准的MIME类型资源,它们的扩展有:.css, .gif, .htm, .html, .jpe, .jpeg, .jpg, .js, .png, .svg, and .txt。对.htm 和 .svg 扩展到支持是在PHP 5.4.4之后才支持的。

5 处理不支持的文件类型

如果你希望这个Web服务器能够正确的处理不被支持的MIME文件类型,这样做:

  1. <?php
  2. // router.php
  3. $path = pathinfo($_SERVER["SCRIPT_FILENAME"]);
  4. if ($path["extension"] == "ogg") {
  5. header("Content-Type: video/ogg");
  6. readfile($_SERVER["SCRIPT_FILENAME"]);
  7. }
  8. else {
  9. return FALSE;
  10. }
  11. ?>

执行:

  1. $ php -S localhost:8000 router.php

如果你希望能远程的访问这个内置的web服务器,你的启动命令需要改成下面这样:

6 远程访问这个内置Web服务器

  1. $ php -S 0.0.0.0:8000

这样你就可以通过 8000 端口远程的访问这个内置的web服务器了。

写了一个ThinkPHP的模板引擎, 仿angular的, 简单版

前段时间学习angularjs, 里面的模板思想和实现方法很酷, 就心血来潮, 想实现一个php版的, 今天试着写了一下, 发现貌似可以, 具体看源码.

./ThinkPHP/Library/Think/Template/Driver/Angular.class.php

  1. <?php
  2. namespace Think\Template\Driver;
  3. use Think\Storage;
  4. /**
  5. * Angular模板引擎驱动
  6. */
  7. class Angular {
  8. private $config = array();
  9. private $tpl_var = array();
  10. /**
  11. * 架构函数
  12. */
  13. public function __construct() {
  14. $this->config['cache_path'] = C('CACHE_PATH');
  15. $this->config['tpl_dir'] = THEME_PATH;
  16. $this->config['cache_path'] = C('CACHE_PATH');
  17. $this->config['template_suffix'] = C('TMPL_TEMPLATE_SUFFIX');
  18. $this->config['cache_suffix'] = C('TMPL_CACHFILE_SUFFIX');
  19. $this->config['tmpl_cache'] = C('TMPL_CACHE_ON');
  20. $this->config['cache_time'] = C('TMPL_CACHE_TIME');
  21. $this->config['attr'] = 'tp-';
  22. }
  23. /**
  24. * 编译模板
  25. * @param type $tpl_file 模板文件
  26. * @param type $tpl_var 模板变量
  27. */
  28. public function fetch($tpl_file, $tpl_var) {
  29. $this->tpl_var = $tpl_var;
  30. $tpl_file = $this->load_template($tpl_file);
  31. Storage::load($tpl_file, $tpl_var, null, 'tpl');
  32. }
  33. /**
  34. * 加载主模板并缓存
  35. * @param string $tpl_file 模板文件名
  36. * @return string 缓存的模板文件名
  37. */
  38. public function load_template($tpl_file) {
  39. if (is_file($tpl_file)) {
  40. // 读取模板文件内容
  41. $tpl_content = file_get_contents($tpl_file);
  42. } else {
  43. $tpl_content = $tpl_file;
  44. }
  45. // 根据模版文件名定位缓存文件
  46. $tpl_cache_file = $this->config['cache_path'] . md5($tpl_file) . $this->config['cache_suffix'];
  47. if (Storage::has($tpl_cache_file) && !APP_DEBUG && $this->config['tmpl_cache']) {
  48. return $tpl_cache_file;
  49. }
  50. // 编译模板内容
  51. $tpl_content = $this->compiler($tpl_content);
  52. Storage::put($tpl_cache_file, trim($tpl_content), 'tpl');
  53. return $tpl_cache_file;
  54. }
  55. /**
  56. * 编译模板内容
  57. * @param string $tpl_content 模板内容
  58. * @return string 编译后端php混编代码
  59. */
  60. protected function compiler($tpl_content) {
  61. //模板解析
  62. $tpl_content = $this->parse($tpl_content);
  63. // 添加安全代码
  64. $tpl_content = '<?php if (!defined(\'THINK_PATH\')) exit();?>' . $tpl_content;
  65. // 优化生成的php代码
  66. $tpl_content = str_replace('?><?php', '', $tpl_content);
  67. return strip_whitespace($tpl_content);
  68. }
  69. /**
  70. * 解析模板标签属性
  71. * @param string $content 要模板代码
  72. * @return string 解析后的模板代码
  73. */
  74. public function parse($content) {
  75. while (true) {
  76. $sub = $this->match($content);
  77. if ($sub) {
  78. $method = 'parse_' . $sub['attr'];
  79. if (method_exists($this, $method)) {
  80. $content = $this->$method($content, $sub);
  81. } else {
  82. E("模板属性" . $this->config['attr'] . $sub['attr'] . '没有对应的解析规则');
  83. break;
  84. }
  85. } else {
  86. break;
  87. }
  88. }
  89. $content = $this->parse_value($content);
  90. return $content;
  91. }
  92. /**
  93. * 解析include属性
  94. * @param string $content 源模板内容
  95. * @param array $match 一个正则匹配结果集, 包含 html, value, attr
  96. * @return string 解析后的模板内容
  97. */
  98. private function parse_include($content, $match) {
  99. $tpl_name = $match['value'];
  100. if (substr($tpl_name, 0, 1) == '$') {
  101. //支持加载变量文件名
  102. $tpl_name = $this->get(substr($tpl_name, 1));
  103. }
  104. $array = explode(',', $tpl_name);
  105. $parse_str = '';
  106. foreach ($array as $tpl) {
  107. if (empty($tpl))
  108. continue;
  109. if (false === strpos($tpl, $this->config['template_suffix'])) {
  110. // 解析规则为 模块@主题/控制器/操作
  111. $tpl = T($tpl);
  112. }
  113. // 获取模板文件内容
  114. $parse_str .= file_get_contents($tpl);
  115. }
  116. return str_replace($match['html'], $parse_str, $content);
  117. }
  118. /**
  119. * 解析if属性
  120. * @param string $content 源模板内容
  121. * @param array $match 一个正则匹配结果集, 包含 html, value, attr
  122. * @return string 解析后的模板内容
  123. */
  124. private function parse_if($content, $match) {
  125. $new = "<?php if ({$match['value']}) { ?>";
  126. $new .= str_replace($match['exp'], '', $match['html']);
  127. $new .= '<?php } ?>';
  128. return str_replace($match['html'], $new, $content);
  129. }
  130. /**
  131. * 解析repeat属性
  132. * @param string $content 源模板内容
  133. * @param array $match 一个正则匹配结果集, 包含 html, value, attr
  134. * @return string 解析后的模板内容
  135. */
  136. private function parse_repeat($content, $match) {
  137. $new = "<?php foreach ({$match['value']}) { ?>";
  138. $new .= str_replace($match['exp'], '', $match['html']);
  139. $new .= '<?php } ?>';
  140. return str_replace($match['html'], $new, $content);
  141. }
  142. /**
  143. * 解析show属性
  144. * @param string $content 源模板内容
  145. * @param array $match 一个正则匹配结果集, 包含 html, value, attr
  146. * @return string 解析后的模板内容
  147. */
  148. private function parse_show($content, $match) {
  149. $new = "<?php if ({$match['value']}) { ?>";
  150. $new .= str_replace($match['exp'], '', $match['html']);
  151. $new .= '<?php } ?>';
  152. return str_replace($match['html'], $new, $content);
  153. }
  154. /**
  155. * 解析hide属性
  156. * @param string $content 源模板内容
  157. * @param array $match 一个正则匹配结果集, 包含 html, value, attr
  158. * @return string 解析后的模板内容
  159. */
  160. private function parse_hide($content, $match) {
  161. $new = "<?php if (!({$match['value']})) { ?>";
  162. $new .= str_replace($match['exp'], '', $match['html']);
  163. $new .= '<?php } ?>';
  164. return str_replace($match['html'], $new, $content);
  165. }
  166. /**
  167. * 解析普通变量和函数{$title}{:function_name}
  168. * @param string $content 源模板内容
  169. * @return string 解析后的模板内容
  170. */
  171. private function parse_value($content) {
  172. $content = preg_replace('/\{(\$.*?)\}/', '<?php echo \1 ?>', $content);
  173. $content = preg_replace('/\{\:(.*?)\}/', '<?php echo \1 ?>', $content);
  174. return $content;
  175. }
  176. /**
  177. * 获取第一个表达式
  178. * @param string $content 要解析的模板内容
  179. * @return array 一个匹配的标签数组
  180. */
  181. private function match($content) {
  182. $reg = '#<(?<tag>[\w]+)[^>]*?\s(?<exp>' . preg_quote($this->config['attr']) . '(?<attr>[\w]+)=([\'"])(?<value>[^\4]*?)\4)[^>]*>#s';
  183. $match = null;
  184. if (!preg_match($reg, $content, $match)) {
  185. return null;
  186. }
  187. $sub = $match[0];
  188. $tag = $match['tag'];
  189. /* 如果是但标签, 就直接返回 */
  190. if (substr($sub, -2) == '/>') {
  191. $match['html'] = $match[0];
  192. return $match;
  193. }
  194. /* 查找完整标签 */
  195. $start_tag_len = strlen($tag) + 1; // <div
  196. $end_tag_len = strlen($tag) + 3; // </div>
  197. $start_tag_count = 0;
  198. $content_len = strlen($content);
  199. $pos = strpos($content, $sub);
  200. $start_pos = $pos + strlen($sub);
  201. while ($start_pos < $content_len) {
  202. $is_start_tag = substr($content, $start_pos, $start_tag_len) == '<' . $tag;
  203. $is_end_tag = substr($content, $start_pos, $end_tag_len) == "</$tag>";
  204. if ($is_start_tag) {
  205. $start_tag_count++;
  206. }
  207. if ($is_end_tag) {
  208. $start_tag_count--;
  209. }
  210. if ($start_tag_count < 0) {
  211. $match['html'] = substr($content, $pos, $start_pos - $pos + $end_tag_len);
  212. return $match;
  213. }
  214. $start_pos++;
  215. }
  216. return null;
  217. }
  218. }

./Application/Home/Controller/TestController.class.php

  1. <?php
  2. namespace Home\Controller;
  3. use Think\Controller;
  4. class TestController extends Controller {
  5. public function index() {
  6. C('SHOW_PAGE_TRACE', true);
  7. C('TMPL_ENGINE_TYPE', 'Angular');
  8. $data = array();
  9. $data['title'] = '标题';
  10. $data['nav'] = array(
  11. array('title' => '首页', 'url' => '/'),
  12. array('title' => '文章', 'url' => '/article'),
  13. array('title' => '图片', 'url' => '/pic'),
  14. array('title' => '新闻', 'url' => '/news'),
  15. );
  16. $data['count'] = 6;
  17. $data['list'] = array(
  18. array('id' => 1, 'title' => '这是标题1', 'create_time' => strtotime('-5 seconds')),
  19. array('id' => 2, 'title' => '这是标题2', 'create_time' => strtotime('-4 seconds')),
  20. array('id' => 3, 'title' => '这是标题3', 'create_time' => strtotime('-3 seconds')),
  21. array('id' => 4, 'title' => '这是标题4', 'create_time' => strtotime('-2 seconds')),
  22. array('id' => 5, 'title' => '这是标题5', 'create_time' => strtotime('-1 seconds')),
  23. array('id' => 6, 'title' => '这是标题6', 'create_time' => NOW_TIME),
  24. );
  25. $this->assign($data);
  26. $this->display('index');
  27. }
  28. }

./Application/Home/View/Test/index.html

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>Angular 模板测试 - {$title}</title>
  5. <meta charset="UTF-8">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <style>
  8. * {
  9. margin: 0px;
  10. padding: 0px;
  11. font-size: 12px;
  12. color: #333;
  13. line-height: 20px;
  14. }
  15. a {
  16. color: #33F;
  17. text-decoration: none;
  18. }
  19. a:hover {
  20. color: #f00;
  21. text-decoration: underline;
  22. }
  23. .center {
  24. text-align: center;
  25. }
  26. h1{
  27. font-size: 30px;
  28. line-height: 50px;
  29. }
  30. .nav {
  31. line-height: 30px;
  32. }
  33. .nav a{
  34. padding: 0px;
  35. margin: 0px 20px;
  36. }
  37. .main table{
  38. width: 500px;
  39. margin: 0px auto;
  40. }
  41. table {
  42. border: 1px solid #666;
  43. }
  44. table td,
  45. table th{
  46. border: 1px solid #666;
  47. line-height: 20px;
  48. padding: 0px 5px;
  49. }
  50. table th{
  51. background: #CCC;
  52. }
  53. #footer p{
  54. text-align: center;
  55. line-height: 30px;
  56. }
  57. </style>
  58. </head>
  59. <body>
  60. <div class="header">
  61. <h1 class="center">Angular 模板测试 - {$title}</h1>
  62. <div class="nav center" tp-if="$nav">
  63. <a tp-repeat="$nav as $vo" href="{$vo['url']}">{$vo['title']}</a>
  64. </div>
  65. </div>
  66. <div class="main">
  67. <table>
  68. <tr>
  69. <th>编号</th>
  70. <th>标题</th>
  71. <th>创建时间</th>
  72. <th>操作</th>
  73. </tr>
  74. <tr tp-if="$list" tp-repeat="$list as $vo">
  75. <td>{$vo['id']}</td>
  76. <td>{$vo['title']}</td>
  77. <td>{:date('Y-m-d H:i:s', $vo['create_time'])}</td>
  78. <td><a href="#del={$vo['id']}">删除</a></td>
  79. </tr>
  80. <tr tp-if="$count">
  81. <td colspan="4" class="center">共 {$count} 条数据</td>
  82. </tr>
  83. <tr tp-hide="$list">
  84. <td colspan="4" class="center">没有数据</td>
  85. </tr>
  86. </table>
  87. </div>
  88. <div tp-include="footer"></div>
  89. </body>
  90. </html>
  1. <footer id="footer">
  2. <div class="foot-warp">
  3. <p>
  4. © 2015 {:C('SITE_TITLE')} zhaishuaigan@qq.com    豫ICP备13012601号
  5. </p>
  6. </div>
  7. </footer>

运行/Test/index, 显示结果

目前只是实现了简单的解析, 还需要进一步完善, 比如配置啊, 扩展更多的标签啊什么的.