<repo_name>yuxianda/phpweb<file_sep>/docs/x.da-build-sh.sh
jekyll b -d ../public/phpweb
echo '...done...'
read -p "Press enter to continue!  --Noticed by thianda"<file_sep>/application/trouble/controller/Common.php
<?php

namespace app\trouble\controller;
use app\common\controller\Common as CCommon;
use think\Controller;
use think\Request;
use think\Config;

class Common extends CCommon{
	
	/**
	 * 判断是否已登录--(初始化函数 _initialize 优先于 $beforeActionList 配置)
	 */

	
}
<file_sep>/application/kxeams/controller/C.php
<?php

namespace app\kxeams\controller;

use app\common\controller\Common;
use think\Controller;
use think\Request;
use think\Db;

class C extends Common {
	
	/**
	 * 获取 todo main表的 detail 信息
	 *
	 * @param unknown $main_id        	
	 */
	protected function todoDetail($main_id) {
		return Db::name ( "detail" )->where ( "main_id", $main_id )->alias ( "dd" )->join ( "__ITEM__ ii", "ii.id=dd.item_id" )->field ( "name,type,maker,units,count,dusage,dremarks" )->select ();
	}
	protected function storeCounts($types = []) {
	}
}
<file_sep>/application/index/controller/Index.php
<?php

namespace app\index\controller;

use think\Controller;
use PHPMailer\PHPMailer\PHPMailer;

class Index extends Controller {
	public function index() {
		if (in_array ( substr ( request ()->ip (), 0, 8 ), [ 
				'10.61.21',
				'0.0.0.0',
				'127.0.0.' 
		] )) {
			return $this->fetch ( 'indexs' );
		} else {
			return $this->fetch ();
		}
	}
	public function testMail() {
		$mail = new PHPMailer();
		$mail->isSMTP (); // Set mailer to use SMTP
		$mail->CharSet = "utf-8";
		$mail->SetLanguage ( 'zh_cn' );
		$mail->SMTPDebug = 1;
		$mail->Host = 'smtp.139.com'; // Specify main and backup SMTP servers
		$mail->SMTPAuth = true; // Enable SMTP authentication
		$mail->Username = "<EMAIL>"; // SMTP username
		$mail->Password = '<PASSWORD>'; // SMTP password
		                                  // $mail->SMTPSecure = 'tls'; // Enable TLS encryption, `ssl` also accepted
		$mail->Port = 25; // TCP port to connect to
		$mail->setFrom ( '<EMAIL>', 'Excel服务器' );
		$mail->addAddress ( '<EMAIL>' ); // Name is optional
		                                             // $mail->addReplyTo ( '<EMAIL>', 'Information' );
		                                             // $mail->addCC ( '<EMAIL>' );
		                                             // $mail->addBCC ( '<EMAIL>' );
		                                             
		// $mail->addAttachment ( '/var/tmp/file.tar.gz' ); // Add attachments
		$mail->addAttachment ( '/aa.jpg', '附件new.jpg' ); // Optional name
		                                                 // 绝对路径从磁盘根目录算起，相对路径从public/idnex.php算起。
		$mail->isHTML ( true ); // Set email format to HTML
		
		$mail->Subject = '测试邮件标题' . date ( "Y-m-d H:i:s" );
		$mail->Body = 'This is the HTML message body <b>in bold!</b><hr>正文是Body';
		$mail->AltBody = 'This is the body in plain text for non-HTML mail clients';
		
		if (! $mail->send ()) {
			echo 'Message could not be sent.';
			echo 'Mailer Error: ' . $mail->ErrorInfo;
		} else {
			echo 'Message has been sent';
		}
		exit ( 0 );
	}
	public function _empty() {
		$dir = APP_PATH . request ()->module () . DS . "view" . DS . request ()->controller () . DS . request ()->action () . "." . config ( 'template.view_suffix' );
		if (file_exists ( $dir ))
			return $this->fetch ( request ()->action () );
		else {
			return $this->error ( "请求未找到(╯﹏╰)", null, null, 60 );
		}
	}
}
<file_sep>/application/kxeams/model/Main.php
<?php

namespace app\kxeams\model;

use think\Model;
use traits\model\SoftDelete;

class Main extends Model {
	
	use SoftDelete;
	protected $deleteTime = 'mdelete_time';
	protected $autoWriteTimestamp = 'datetime';
	protected $createTime = 'mcreate_time';
	protected $updateTime = false;
	protected $type = [ 
			'id' => 'integer',
			'mcreate_time' => 'datetime',
			'mdelete_time' => 'datetime' 
	];
	public function item() {
		return $this->belongsTo ( 'Item' );
	}
}<file_sep>/application/zx_apply/controller/Common.php
<?php

namespace app\zx_apply\controller;

use app\common\controller\Common as CCommon;
use think\Session;
use app\zx_apply\model\Infotables;

class Common extends CCommon
{

	/**
	 * 判断是否已登录--(初始化函数 _initialize 优先于 $beforeActionList 配置)
	 */
	public function _initialize()
	{
		$this->checkAuth();
		parent::_initialize();
		$this->assign("title", "数据专线开通-全流程辅助--X.Da");
	}
	private function checkAuth()
	{
		$u = input('get.y');
		$users = [
			'y' => 'xda',
			'sjbz' => '数据班组'
		];
		foreach ($users as $k => $v) {
			if ($k == $u) {
				session("user", [
					'name' => $v,
					'role' => 'manage'
				]);
			}
		}
	}
	/**
	 * 退出登录
	 */
	public function loginout()
	{
		$this->log("注销登陆", [
			"stauts" => "success",
			"name" => session("user.name")
		]);
		Session::delete("user");
		return $this->success("已注销登录", "index/index#logout", "", 1);
	}
	public function _empty()
	{
		$dir = APP_PATH . request()->module() . DS . "view" . DS . request()->controller() . DS . request()->action() . "." . config('template.view_suffix');
		if (file_exists($dir))
			return $this->fetch(request()->action());
		else {
			return $this->error("页面不在了哦~你猜我给它弄到哪去了？→_→", null, null, 30);
		}
	}
	public function tt()
	{
		$data = null;
		$db = Infotables::find(2273);
		$values = array_values($db->toArray());
		return dump($values);
	}
}
<file_sep>/docs/README.md
# PHPWEB 文档库

请访问：<https://thianda.github.io/phpweb/>

Github：<https://github.com/thianda/phpweb>

[![](images/red_packet.jpg)](images/red_packet.jpg)

模板：[pixyll.com](http://www.pixyll.com)

中文版 <https://github.com/ee0703/pixyll-zh-cn>.
<file_sep>/application/kxeams/model/Mylist.php
<?php

namespace app\kxeams\model;

use think\Model;

class Mylist extends Model {
	
	
}<file_sep>/application/zx_apply/model/Vlantables.php
<?php

namespace app\zx_apply\model;

use think\Model;
use think\Db;
use traits\model\SoftDelete;

class Vlantables extends Model
{
	use SoftDelete;
	protected $deleteTime = 'delete_time';
	protected $autoWriteTimestamp = false;
	/**
	 * 录入/更新/删除vlan
	 *
	 * @param string $aStation        	
	 * @param string $vlan        	
	 * @param string $description        	
	 * @param unknown $infoId        	
	 */
	public static function createVlan($aStation = "", $vlan = "", $description = "", $infoId = null)
	{
		if (is_null($infoId)) {
			return; // 仅内部调用，infoId不会为空。
		}
		$vlantables = new static();
		$aStationConf = config("aStation");
		if (array_key_exists($aStation, $aStationConf)) {
			// 根据a端匹配到9312名，则保存vlan
			$data = [
				"deviceName" => $aStationConf[$aStation],
				"vlan" => $vlan == 0 ? null : $vlan,
				"description" => $description,
				"infoId" => $infoId
			];
			// 根据infoId，如果已存在则更新，否则新增。
			$dbData = $vlantables->where(["infoId" => $infoId])->find();
			if ($dbData) {
				$dbData->isUpdate(true)->save($data);
			} else {
				$vlantables->isUpdate(false)->save($data);
			}
		}
	}
	/**
	 * 自动预分配vlan，返回预分配vlan
	 *
	 * @param string $device        	
	 * @param string $cName        	
	 * @param string $manual
	 *        	0 for write to db(default), 1 for pre-generate.
	 * @return number|string
	 */
	public static function generateVlan($device = "", $cName = "", $manual = false)
	{
		$usedVlans = self::where("deviceName", $device)->distinct(true)->column("vlan");
		for ($vlan = 2111; $vlan < 3071; $vlan++) {
			if (!in_array($vlan, $usedVlans)) {
				$preVlan = $vlan;
				break;
			}
		}
		if ($manual) {
			// 手动分类，推荐空闲vlan
			$result = [
				"preVlan" => $preVlan,
				"usedVlans" => $usedVlans
			];
		} else {
			Db::name("vlantables")->insert([
				"deviceName" => $device,
				"vlan" => $preVlan,
				"description" => $cName
			]);
			$result = $preVlan;
		}
		return $result;
	}
	/**
	 * 导入更新已使用vlan
	 *
	 * @param string $deviceName
	 * @param string $str
	 * @return void|string
	 */
	public static function importUsedVlan($deviceName = "", $str = "")
	{
		preg_match_all('/vlan batch ([\S ]+)/', $str, $str); // 匹配后结果输出到$str
		$str = implode(" ", $str[1]);
		// $str = str_replace ( 'vlan batch ', '', $str );
		// $str = preg_replace ( '/[\r\n]/', '', $str );
		$array = explode(" ", $str);
		$result = [];
		$count = count($array);
		for ($i = 0; $i < $count; $i++) {
			// 遍历数组，替换to，补充连续的vlan
			if ($array[$i] == "to") {
				array_splice($array, $i, 1);
				// 补充连续的vlan
				for ($j = $array[$i - 1] + 1; $j < $array[$i]; $j++) {
					$array[] = $j;
				}
			}
		}
		sort($array, SORT_NUMERIC); // 排序
		$count = 0;
		$validation = 0;
		foreach ($array as $v) {
			if ($v > 2000 && $v < 3071) {
				// 范围之外的vlan，无操作
				$count++;
				// 已有，则放弃
				$vlanInfo = self::where([
					"deviceName" => $deviceName,
					"vlan" => $v
				])->select();
				if (!$vlanInfo) {
					// 无信息，则insert
					$vlanInfo = self::create([
						"deviceName" => $deviceName,
						"vlan" => $v,
						"description" => "手动导入-" . date("Y-m-d h:i:s", time())
					]);
					$validation++;
				}
			}
		}
		return [
			"total" => count($array),
			"count" => $count,
			"validation" => $validation
		];
	}
	/**
	 * 检查vlan是否已分配
	 *
	 * @param string $zxType
	 * @param string $aStation
	 * @param number $vlan
	 * @return array
	 */
	public static function check($zxType = "", $aStation = "", $vlan = 0)
	{
		$data = Infotables::where([
			"zxType" => $zxType,
			"aStation" => $aStation,
			"vlan" => $vlan
		])->field("id,cName")->find();
		if (!$data) {
			$data = self::where("vlan", $vlan);
			$deviceConf = config("aStation");
			if (array_key_exists($aStation, $deviceConf)) {
				$data = $data->where("deviceName", $deviceConf[$aStation]);
				$data = $data->field("id,description as cName")->find();
				if ($data) { // 修改以区别，不同于要查询的数据
					$data = $data->toArray();
					// 随意设置个$data["id"]，非null
					$data["id"] .= "_vlan";
					$data["code"] = 0;
				}
			} else {
				// 此时情况为：vlan存在，aStation不在预设范围内（为空）
				$data = [
					"id" => "",
					"code" => 2,
					"cName" => "请填写正确的<span style='color: red;'>A端基站</span>！"
				];
			}
		} else {
			$data["code"] = 0;
		}
		return $data;
	}
}<file_sep>/application/trouble/config.php
<?php
return [ 
		'version' => "V1.0622-gz",
];<file_sep>/application/kxeams/model/Detail.php
<?php

namespace app\kxeams\model;

use think\Model;

class Detail extends Model {
	protected $type = [
			'id' => 'integer',
			'main_id' => 'integer',
			'item_id' => 'integer',
			'count' => 'integer',
	];
}<file_sep>/application/checkME60/controller/Index.php
<?php

namespace app\checkME60\controller;

use app\common\controller\Common;

class Index extends Common {
	public function index() {
		return $this->redirect ( "main" );
	}

	
	
}
<file_sep>/docs/about.md
---
layout: page
title: 关于
permalink: about/
tags: about
---

# 关于 phpweb文档



文档全部由[YuXianda](https://github.com/yuxianda)撰写。

>  All the posts are written by [YuXianda](https://github.com/yuxianda).

此文档主题fork于[johnotander/pixyll](https://github.com/johnotander/pixyll)。

> This Jekyll theme was crafted with <3 by [<NAME>](http://johnotander.com)
> ([@4lpine](https://twitter.com/4lpine)).

Checkout the [Github repository](https://github.com/johnotander/pixyll) to download it,
request a feature, report a bug, or contribute. It's free, and open source
([MIT](http://opensource.org/licenses/MIT)).

[返回](../)<file_sep>/docs/_posts/2017-12-17-tl-esserver.md
---
layout:         post
title:          Excel服务器辅助
date:           2017-12-17 22:05:15
summary:        本模块可自助找回Excel服务器账号，需服务器连接带外网，需要一个邮箱账号。
categories:     notes
update_date:    2017-12-17 22:42:22
---

### 链接

内网： <http://10.65.187.202/esserver/>；外网： <http://172.16.58.3:800/esserver/>。

### 密码找回

输入用户名以及用户名对应的邮箱，验证成功，即可向该邮箱发送一封验证邮件，访问邮件里的密码重置链接，即可重新设置该账户的密码，非常简单。

> 获取重置密码的链接之前。需要验证登陆名以及邮箱地址。
>
> 若邮箱验证失败或无邮箱，可填写包含登陆名字段的邮箱地址.
>
> 验证信息正确数大于2即可获取重置链接。

### 密码重置

在执行了密码找回后，会在对应的邮箱中收到密码重置的链接，在有效期内访问，即可直接重置Excel服务器的密码。注意设置新密码的密码复杂度，以及不允许与最近2次的密码相同。

### 系统设置

功能不对外开放^_^

### 下载安装谷歌浏览器(chrome)

[本模块](/esserver)在的IE9以下无法正常显示，推荐使用谷歌(chrome)或火狐(firefox)浏览器。
<file_sep>/application/trouble/database.php
<?php
return [
      'prefix' => 'trouble_',
];
<file_sep>/docs/_posts/2017-12-12-template.md
---
layout:         post
title:          new title
date:           2017-12-12 22:22:22--0800
summary:        the summarys of the post
categories:     notes
update_date:    2017-12-17 22:22:22
---

## 我是模板<file_sep>/application/zx_apply/database.php
<?php
return [
      'prefix' => 'zx_',
];
<file_sep>/application/zx_apply/controller/Index.php
<?php
namespace app\zx_apply\controller;

use think\Controller;
use think\Request;
use think\Db;
use think\Cache;
use app\zx_apply\model\Infotables;
use app\zx_apply\model\Vlantables;
use Overtrue\Pinyin\Pinyin;

class Index extends Common
{
	public function ch2arr($str)
	{
		$length = mb_strlen($str, 'utf-8');
		$array = [];
		for ($i = 0; $i < $length; $i++)
			$array[] = mb_substr($str, $i, 1, 'utf-8');
		return $array;
	}

	/**
	 * 首页-登录
	 *
	 * @return mixed|string|void
	 */
	public function index()
	{
		if (request()->isGet()) {
			if (request()->url() != "/zx_apply/index/index.html") {
				return $this->redirect("index");
			}
			return $this->fetch();
		}
		if (request()->isPost()) {
            // post请求 验证登陆
			$user = Db::table("phpweb_check")->where('email', input("post.email"))->order("time desc")->find();
			$msg = "";
			if (!$user) {
				return $this->result(["code" => 0], 0, "该邮箱还未申请验证码");
			}
			if ($user["code"] != input("post.code")) {
				$msg = "验证码错误";
			} else {
                // 验证码正确，继续验证申请人姓名
				if ($user["name"] != input("post.name")) {
					$msg = "申请人姓名与申请时不一致<br />申请时为：<b>" . $user["name"] . "</b><br />申请时间：" . $user["time"];
				}
			}
			if ($msg) {
				$this->writeLog("登陆", "failed", $msg);
				return $this->error($msg, null, input("post."));
			} else if (time() - strtotime($user["time"]) > 3600 * 24 * 15) { // 15天内可直接登陆
				$msg = "登陆超时，请重新获取验证码。";
				$this->writeLog("登陆", "failed", $msg);
				unset($user["code"]);
				return $this->error($msg, "index", $user);
			} else {
				$e = explode("@", $user["email"]);
                // 附加role。
				if ($e[1] == "ln.chinamobile.com" && in_array($e[0], config("manageEmails"))) {
					$user["role"] = "manage";
				} else {
					$user["role"] = "index";
				}
				session("user", $user);
				$msg = "欢迎回来，" . $user["name"] . "。";
				$this->writeLog("登陆", "success", $msg);
				$url = session("to_url") ? session("to_url") : session("user.role") . "/query";
				session("to_url", null);
				return $this->success($msg, $url, $user);
			}
		}
	}

	protected function writeLog($k, $status, $msg)
	{
		$this->log($k, [
			"status" => $status,
			"name" => input("post.name"),
			"email" => input("post.email"),
			"msg" => strip_tags($msg)
		]);
	}

	/**
	 * 数据专线申请开通
	 */
	public function apply()
	{
		if (request()->isGet()) {
			return $this->fetch();
		} else if (request()->isPost()) {
			$data = input("post.");
			$this->checkInstanceID(null, $data["instanceId"]); // 检查instanceId
			$extraHeader = config("extraInfo");
			foreach ($extraHeader as $k => $v) {
				$data["extra"][$v] = $data[$v];
				unset($data[$v]);
			}
			$result = Infotables::createInfo($data, "apply");
            // 发邮件通知
			$subject = "[待办]ip申请-" . ($data["ifOnu"] ? "onu" : "9312") . "-" . $data["cName"] . $data["instanceId"];
			$body = "<p>请登陆系统及时处理：</p><br> 内网： <a href='http://10.65.187.202/zx_apply/index/index.html#Manage/todo'>http://10.65.187.202/zx_apply/index/index.html#Manage/todo</a><br>外网： <a href='http://172.16.31.10:800/zx_apply/index/index.html#Manage/todo'>http://172.16.31.10:800/zx_apply/index/index.html#Manage/todo</a>";
			$this->sendManageNotice($subject, $body, true);
			$v = [
				"username" => session("user.name"),
				"email" => session("user.email"),
				"cName" => $data["cName"],
				"instanceId" => $data["instanceId"]
			];
			$this->log("提交申请", $v);
			$redirectUrl = "../" . session("user.role") . "/query.html";
			return $this->result(null, $result, $redirectUrl);
            // return json_encode ( $data, 256 );
		}
	}

	/**
	 * 修改提交
	 */
	public function re_apply()
	{
		if (request()->isGet()) {
			return $this->error("Nothing here. Do not try again!");
		}
		$data = input("post.");
		$oldData = Infotables::get(input("param.old"))->toArray();
		$updates = []; // 记录有更新的字段
		$change = '';
        /* 不验证实例标识重复与否 */
		$extraHeader = config("extraInfo");
		foreach ($extraHeader as $k => $v) {
			$data["extra"][$v] = $data[$v];
			unset($data[$v]);
			if ($data["extra"][$v] != $oldData["extra"][$v]) {
				$change .= $k . " => [" . $oldData["extra"][$v] . "] 改为 [" . $data["extra"][$v] . "]\r\n";
				$updates[$k] = "[" . $oldData["extra"][$v] . "]=>[" . $data["extra"][$v] . "]";
			}
		}
		// 不验证 instanceID
		$result = Infotables::createInfo($data, "apply");
		$this->queryDelete(["id" => [input("param.old")]]);
        // 发邮件通知
		$subject = "[待办]修改申请-" . $data["aPerson"] . "-" . $data["cName"];
		$oldInfo = "<pre style='color:#088'>A端基站： " . $oldData["aStation"] . "\r\nip: " . $oldData["ip"] . "\r\nvlan: " . $oldData["vlan"] . "</pre>";
		$body = "<p>原分配信息：</p>" . $oldInfo;
		$body .= "<p>修改内容：</p><pre style='color:#088bff'>";
		foreach ($data as $k => $v) {
			if ($k = 'extra') {
				continue;
			}
			if ($v != $oldValue) {
				$change .= $k . " => [" . $oldValue . "] 改为 [" . $v . "]\r\n";
				$updates[$k] = "[" . $oldValue . "]=>[" . $v . "]";
			}
		}
		$change == '' && $change = "无";
		$body .= $change;
		$body .= "</pre><p>请登陆系统及时处理：</p>" . config("systemURLString");
		$this->sendManageNotice($subject, $body, true);
		$v = [
			"username" => session("user.name"),
			"email" => session("user.email"),
			"cName" => $data["cName"],
			"instanceId" => $data["instanceId"],
			"oldInfo" => $oldInfo,
			"updates" => $updates
		];
		$this->log("修改申请", $v);
		$redirectUrl = "../" . session("user.role") . "/query.html";
		return $this->result(null, $result, $redirectUrl);
	}

	/**
	 * 根据label、order 获取表格的 header!
	 * $v为false，获取option(default)；为ture，获取value
	 *
	 * @param String $label            
	 * @param String $order            
	 * @param boolean $v            
	 * @return string
	 */
	public function getHeader($label = "label", $order = "order", $v = false)
	{
		if ($label === "label" || $order === "order") {
			return "{msg:\"你要搞什么？\"}"; // 未输入参数label或order
		}
		$_headerData = Db::table("phpweb_sysinfo")->field("value,option")->where(["label" => $label])->order("id")->select();
		$orderArr = explode(",", $order);
		$headerArr = [];
		$sub = $v ? "value" : "option";
		foreach ($orderArr as $o) {
			$headerArr[] = $_headerData[$o][$sub];
		}
		return implode(",", $headerArr);
	}

	/**
	 * 根据order获取handsontable组件的colWidth
	 *
	 * @param unknown $order            
	 * @return string|void
	 */
	protected function getColWidths($order = null)
	{
		if (!is_null($order)) {
			$orderArr = explode(",", $order);
			$result = [];
			foreach ($orderArr as $v) {
				$result[] = config("colWidth")[$v];
			}
			return implode(",", $result);
		}
		return $this->result(null, 0, "~缺参数~");
	}

	/**
	 * 信息查询
	 */
	public function query()
	{
		if (request()->isGet()) {
            // 访问
			$aStation = array_keys(config("aStation"));
			$zxTitle = [
				"label" => "zx_apply-new-rb",
				"order" => "24,1,2,3,4,5,6,7,8,9,10,12,13,14,15,16,17,18,19,20,29,30,31,32,33,34,35,36,37,38,39,40,41,42,22,23,26"
			];
			$this->assign([
				"aStationData" => implode(",", $aStation),
				"colHeaderData" => $this->getHeader($zxTitle["label"], $zxTitle["order"]),
				"colWidthsData" => $this->getColWidths($zxTitle["order"]),
				"data" => $this->getInfoData()->toJson()
			]);
			return $this->fetch();
		}
		if (request()->isPost()) {
            // 获取台账
			input("post.r") == "info" && $data = $this->getInfoData(input("post.zxType"))->toArray();
			input("post.r") == "search" && $data = $this->querySearch(input("post."));
			input("post.r") == "brief" && $data = $this->querySearchBrief(input("post."));
			input("get.r") == "update" && $data = $this->queryUpdateInfo(input("post."));
			return $data;
		}
		if (request()->isPut()) {
            // 相关操作
			input("post.r") == "copy_one_more" && $data = $this->queryCopyOne(input("post."));
			input("post.r") == "export" && $data = $this->queryExport();
			return $data;
		}
	}

	/**
	 * 获取台账信息
	 *
	 * @param string $zxType            
	 * @param number $limit            
	 * @return \think\model\Collection|\think\Collection
	 */
	protected function getInfoData($zxType = "互联网", $limit = 100)
	{
		$where = "";
		if (session("user.role") != "manage") {
			$where = ["aEmail" => session("user.email")];
		}
		return collection(Infotables::where("zxType", $zxType)->where($where)->order("status,ip desc,create_time desc")->limit($limit)->select());
	}

	/**
	 * 全局查询
	 *
	 * @param unknown $data            
	 * @return array
	 */
	protected function querySearch($data)
	{
		$where = "";
		if (session("user.role") != "manage") {
			$where = ["aEmail" => session("user.email")];
		}
		return collection(Infotables::where("zxType", $data["zxType"])->where($where)->where($data["where"][0], "like", "%" . $data["where"][2] . "%")->order("ip desc")->select())->toArray();
	}

	/**
	 * 基本信息查询
	 *
	 * @param unknown $data            
	 * @return array
	 */
	private function querySearchBrief($data)
	{
		if (!isset($data["where"][2]) || $data["where"][2] == "") {
			return;
		}
		$field = "create_time,instanceId,cName,cAddress,vlan,ip,aPerson,aEmail";
		$result = collection(Infotables::where("zxType", $data["zxType"])->where($data["where"][0], "like", "%" . $data["where"][2] . "%")->field($field)->order("ip desc")->select())->toArray();
		$v = $data;
		$v["resultLen"] = count($result);
		$v["user"] = session("user.name");
		$v["url"] = request()->url(true);
		$this->log("基本信息查询", $v);
		if (Cache::get('querySearchBriefTimes')) {
			Cache::inc('querySearchBriefTimes');
		} else {
			Cache::set('querySearchBriefTimes', 1, 600);
		}
		$querySearchBriefTimes = Cache::get('querySearchBriefTimes');
		if ($querySearchBriefTimes > 12) {
			$address = config("manageEmails");
			foreach ($address as $k => $v) {
				$address[$k] = $v . "@ln.chinamobile.com";
			}
			if ($querySearchBriefTimes > 14) {
				session(null);
				$msg = session("user.name") . "已被系统强制登出，原因：查询频繁，单位时间内累计" . $querySearchBriefTimes . "次";
				$this->log("强制登出", $msg);
				$this->noticeManage("[频繁查询]" . session("user.name") . "-10分钟内：" . $querySearchBriefTimes, null, $address);
				return $this->error("已退出登陆，请勿频繁查询！", "index");
			}
			return $querySearchBriefTimes;
		}
		return $result;
	}

	/**
	 * 从query.html更新台账
	 *
	 * @param unknown $updateData            
	 * @return number|\think\false
	 */
	protected function queryUpdateInfo($updateData)
	{
		$result = 0;
		$dbNew = [];
		foreach ($updateData as $k => $v) {
			$infotables = new Infotables();
			$line_and_id = explode("-", $k);
			$extraHeader = config("extraInfo");
			foreach ($extraHeader as $e => $exH) {
				if (isset($v[$exH])) { // extra 部分有更新，需要先获取完整的
					$v["extra"][$exH] = $v[$exH];
					unset($v[$exH]);
				}
			}
			$result += $infotables->isUpdate(true)->allowField(true)->save($v, ["id" => $line_and_id[1]]);
            // 反查询刚才修改后的数据库里的值，用于前后端数据的一致性
			$data = $infotables->where("id", $line_and_id[1])->find();
			foreach ($v as $kk => $vv) {
				$dbNew[$k][$kk] = $data->$kk;
			}
			$infotables = null;
		}
		return $this->result($dbNew, 1, $result);
	}

	/**
	 * 从query.html删除台账条目
	 *
	 * @param unknown $input            
	 */
	protected function queryDelete($input)
	{
		$result = Infotables::destroy($input["id"]);
        // 操作人记录到备注里
		Infotables::where('id', 2684)->exp('marks', 'concat(marks,\'' . session("user.name") . "已删;" . "')")->update();
        // 同步删除vlantables
		foreach ($input["id"] as $id) {
			Vlantables::destroy(["infoId" => $id]);
		}
		return $result;
	}
	/**
	 * 额外再申请 IP 操作
	 *
	 * @return void
	 */
	protected function queryCopyOne($input)
	{
		if (request()->isGet()) {
			return $this->error("Nothing here. Do not try again!");
		}
		$id = $input["copyOne"];
        /* 不验证实例标识重复与否 */
		$data = Infotables::get($id)->getData();
		// 获取原始数据，去除 id，直接保存
		unset($data["id"]);
		$result = Infotables::createInfo($data, "apply");
        // 发邮件通知
		$subject = "[待办]额外申请-" . $data["aPerson"] . "-" . $data["cName"];
		$oldInfo = "<pre>A端基站： " . $data["aStation"] . "\r\nip: " . $data["ip"] . "\r\nvlan: " . $data["vlan"] . "</pre>";
		$body = "<p>原分配信息：</p>" . $oldInfo;
		$body .= "<p>现申请额外的IP，需要您核实。";
		$body .= "</p><p>请登陆系统及时处理：</p>" . config("systemURLString");
		$this->sendManageNotice($subject, $body);
		$v = [
			"username" => session("user.name"),
			"email" => session("user.email"),
			"cName" => $data["cName"],
			"instanceId" => $data["instanceId"],
			"oldInfo" => $oldInfo
		];
		$this->log("额外申请", $v);
		$redirectUrl = "../" . session("user.role") . "/query.html";
		return $this->result(null, $result, $redirectUrl);
	}
	/**
	 * 导出全量台账-基于专线类型
	 *
	 * @param string $zxType            
	 * @return string[]|array[]
	 */
	protected function queryExport($zxType = "互联网")
	{
		if ($zxType == "互联网") {
			$colHeader = "申请时间,产品实例标识,带宽,网元厂家,A端基站,客户名称,单位详细地址,客户需求说明(选填),VLAN,IP,联系人姓名(客户侧),联系电话(客户侧),联系人邮箱(客户侧)*,负责人姓名(移动侧)*,负责人电话(移动侧)*,负责人邮箱(移动侧)*,备注,是否ONU带\n(默认为否),单位性质*,单位分类*,行业分类*,使用单位证件类型*,使用单位证件号码*,单位所在省*,单位所在市*,单位所在县*,应用服务类型*,单位属性,网络安全责任人,责任人身份证号,责任人电话,责任人邮箱";
			$colName = "create_time,instanceId,bandWidth,neFactory,aStation,cName,cAddress,cNeeds,vlan,ip,cPerson,cPhone,cEmail,mPerson,mPhone,mEmail,marks,ifOnu,extra.unitProperty,extra.unitCategory,extra.industryCategory,extra.credential,extra.credentialnum,extra.province,extra.city,extra.county,extra.appServType,extra.unitAttribute,extra.securityPerson,extra.securityPersonID,extra.securityPhone,extra.securityEmail";
			$field = "";
		} else if ($zxType == "营业厅") {
			$colHeader = "申请时间,产品实例标识,网元厂家,A端基站,客户名称,单位详细地址,VLAN,互联IP,业务IP,联系人姓名(客户侧),联系电话(客户侧)";
			$colName = "create_time,instanceId,neFactory,aStation,cName,cAddress,,vlan,ip,ipB,cPerson,cPhone";
			$field = $colName;
		} else if ($zxType == "卫生网") {
			$colHeader = "申请时间,产品实例标识,网元厂家,A端基站,客户名称,单位详细地址,VLAN,互联IP,业务IP,联系人姓名(客户侧),联系电话(客户侧)";
			$colName = "create_time,instanceId,neFactory,aStation,cName,cAddress,vlan,ip,ipB,cPerson,cPhone";
			$field = $colName;
		} else if ($zxType == "平安校园") {
			$colHeader = "申请时间,产品实例标识,客户名称,单位详细地址,VLAN,监控IP";
			$colName = "create_time,instanceId,cName,cAddress,vlan,ip";
			$field = $colName;
		}
		if (session("user.role") == "manage") {
			$data = collection(Infotables::field($field)->where("zxType", $zxType)->order("ip")->select())->toArray();
		} else {
			$data = collection(Infotables::field($field)->where("aPerson", session("user.name"))->order("ip")->select())->toArray();
		}
		$v = [
			"dataNum" => count($data),
			"zxType" => $zxType,
			"username" => session("user.name"),
			"email" => session("user.email")
		];
		$this->log("导出全量数据", $v);
		return [
			"data" => $data,
			"colHeader" => $colHeader,
			"colName" => $colName
		];
	}

	/**
	 * 检查 instanceId 是否重复
	 * 可输入$data数组或instanceId
	 *
	 * @param unknown $info            
	 * @param unknown $data            
	 */
	protected function checkInstanceID($info, $data)
	{
		if (null == $info) {
			$instanceId = $data;
		} else if ($info["id"] != $data["id"]) {
			$instanceId = $data["instanceId"];
		} else {
			return;
		}
		$info = Infotables::get([
			"instanceId" => $instanceId
		]);
		if ($info) {
			return $this->error("实例标识重复，请重试", null, "该实例标识对应客户名为：<br>" . $info["cName"] . "<br>申请人：" . $info["aPerson"]);
		}
	}

	/**
	 * 给管理员发送通知
	 *
	 * @param string $subject
	 * @param string $body
	 * @param boolean $addCurrentUser
	 */
	protected function sendManageNotice($subject = '', $body = '', $addCurrentUser = false)
	{
		$address = config("manageEmails");
		foreach ($address as $k => $v) {
			$address[$k] = $v . "@ln.chinamobile.com";
		}
		$addCurrentUser && $address["CC1"] = session("user.email");
		$this->sendEmail($address, $subject, $body);
	}
}<file_sep>/application/esserver/model/Log.php
<?php

namespace app\esserver\model;

use think\Model;

class Log extends Model {
	// 未使用
	protected $pk = 'id';
	protected $type = [
			'v' => 'array'
	];
	/*protected $auto = [
	 'value'
	 ];
	 public function setValueAttr($value) {
	 return json_encode ( $value, JSON_UNESCAPED_UNICODE );
	 }*/
}<file_sep>/docs/_posts/2018-01-25-redis-php-extension-installation.md
---
layout:         post
title:          为php安装redis扩展-windows系统
date:           2018-01-25 11:21:32--0800
summary:       在windows中，不同的php版本对应不同的redis扩展版本，安装配置略繁琐，记录一下。
categories:     notes
update_date:    2018-07-12 17:27:55
---

## 0. 安装redis

可以访问redis的[官网](https://redis.io/)，但是官方只提供linux版本的redis。windows版本可以下载由Microsoft Open Tech group 编译好的版本 <https://github.com/MSOpenTech/redis>，不过现在貌似改名了，直接跳转到<https://github.com/MicrosoftArchive/redis/>，直接在[releases](https://github.com/MicrosoftArchive/redis/releases)页面下载即可。下载msi包，安装时可直接安装为windows服务并设置环境变量。很方便。

目前，最新版本一直是[3.2.100Pre-release](https://github.com/MicrosoftArchive/redis/releases/download/win-3.2.100/Redis-x64-3.2.100.msi)。最新稳定版是[3.0.504](https://github.com/MicrosoftArchive/redis/releases/download/win-3.0.504/Redis-x64-3.0.504.msi)。

在cmd执行测试，执行`redis-cli -v`查看版本。或：

```sh
C:\Users\Administrator>redis-cli
127.0.0.1:6379> ping
PONG
```

### 1. 确认php版本

在php安装目录执行`php -v`。或者在php里执行`phpinfo()`。windows 版的php为Thread Safe（ts）。

```sh
C:\Users\Administrator>php -r "phpinfo();">c:\phpinfo.txt
```

上面代码将phpinfo输出到`c:\phpinfo.txt`，可在不架设web的情况下快速查看phpinfo。

### 2. 下载DLL（重点！）

#### 2.1 php_igbinary.dll

访问：<http://windows.php.net/downloads/pecl/releases/igbinary/>，可看见不同的版本，依据版本优先选`a.b.c`中基于`a.b`，选`c`最大的文件夹进入。例如为选择`2.0.x`，当前最新的是`2.0.7`，进入后可看见支持从php5.6到7.2。下载对应版本即可。（选ts、线程安全版本）

| php版本 | 文件夹 | 下载文件                                                     |
| ------- | ------ | ------------------------------------------------------------ |
| 5.6     | 2.0.7  | [php_igbinary-2.0.7-5.6-ts-vc11-x86.zip](https://windows.php.net/downloads/pecl/releases/igbinary/2.0.7/php_igbinary-2.0.7-5.6-ts-vc11-x86.zip) |
| 7.2     | 2.0.7  | [php_igbinary-2.0.7-7.2-ts-vc15-x86.zip](https://windows.php.net/downloads/pecl/releases/igbinary/2.0.7/php_igbinary-2.0.7-7.2-ts-vc15-x86.zip) |

下载的zip包里有俩文件是我们需要的：`php_igbinary.dll`、`php_igbinary.pdb`（可选）。将它们复制到php的ext目录下。

#### 2.2 php_redis.dll

访问：<http://pecl.php.net/package/redis>，可看见Downloads下面有DLL的下载链接，根据`Version`点击`DLL`查看支持的php版本，如点击`2.2.7`后面的DLL，可看见这个版本的DLL支持PHP5.3到PHP5.6。

| php版本 | Version         | 下载文件                                                     |
| ------- | --------------- | ------------------------------------------------------------ |
| 5.6     | 2.2.7           | [5.6 Thread Safe (TS) x86](https://windows.php.net/downloads/pecl/releases/redis/2.2.7/php_redis-2.2.7-5.6-ts-vc11-x86.zip) |
| 7.2     | 4.1.0(当前最新) | [7.2 Thread Safe (TS) x86](https://windows.php.net/downloads/pecl/releases/redis/4.1.0/php_redis-4.1.0-7.2-ts-vc15-x86.zip) |

下载的zip包里有俩文件是我们需要的：`php_redis.dll`、`php_redis.pdb`（可选）。将它们复制到php的ext目录下。

### 3. 配置php.ini

在php安装目录执行`php --ini`，可查看当前加载的php.ini所在位置。在php.ini中添加：

```ini
extension=php_igbinary.dll
extension=php_redis.dll
```

注意`php_igbinary.dll`要放在`php_redis.dll`前面。

### 4. 测试

重启apache。可以在`phpinfo()`中看见redis的配置。

```
php -r "phpinfo();">c:\phpinfo.txt
```

可在`c:\phpinfo.txt`看见如下字样：（用notepad++打开。自带的notepad无换行。）

```
redis

Redis Support => enabled
Redis Version => 2.2.7
```

或写段php代码测试

```php
<?php
//连接本地的 Redis 服务
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
echo "Connection to server sucessfully";
//设置 redis 字符串数据
$redis->set("tutorial-name", "Redis tutorial");
// 获取存储的数据并输出
echo "Stored string in redis:: " . $redis->get("tutorial-name");
?>
```

<file_sep>/application/kxeams/model/TableAttr.php
<?php

namespace app\kxeams\model;

use think\Model;

class TableAttr extends Model {
	
	
}<file_sep>/application/kxeams/controller/Manage.php
<?php

namespace app\kxeams\controller;

use think\Controller;
use think\Request;
use think\Db;
use app\kxeams\model\Main;
use app\kxeams\model\Item;
use app\kxeams\model\Detail;

class Manage extends User {
	protected $beforeActionList = [ 
			'checkAuth' 
	];
	public function checkAuth() {
		// if (strpos ( input ( 'session.user/a' )['role'], '管理员' ) === false) {
		if (strpos ( session ( 'user.role' ), '管理员' ) === false) {
			return $this->error ( "您的权限不足，无法访问。", "user/index" );
		}
	}
	public function index() {
		if (Request::instance ()->isGet ()) {
			return $this->indexGet ();
		} else if (Request::instance ()->isPost ()) {
			return $this->indexPost ();
		}
	}
	/**
	 * 物品总览
	 */
	public function indexGet() {
		$fields = "id,name,type,maker,units,class";
		$col_names = "序号,名称,类型,单位,物品大类";
		$items = db ( "item" )->field ( $fields )->select ();
		$result = [ ];
		for($i = 0; $i < count ( $items ); $i ++) {
			// 根据每一个id，分别查询main明细，并计算数量
			$data = db ( "detail" )->alias ( "dd" )->join ( '__MAIN__ mm', 'dd.main_id = mm.id' )->where ( [ 
					"item_id" => $items [$i] ['id'],
					"moffice" => session ( "user.office" ) 
			] )->field ( "count,form_type,progress,from_dept,from_store,to_dept,to_store" )->select ();
			$sum = 0;
			foreach ( $data as $d ) {
				if (in_array ( $d ["progress"], [ 
						config ( "领用进度.2" ),
						config ( "发放进度.2" ),
						"-" 
				] )) {
					if ($d ["to_dept"] == session ( "user.dept" ))
						$sum += $d ["count"];
					else if ($d ["from_dept"] == session ( "user.dept" )) {
						$sum -= $d ["count"];
					}
				}
			}
			$items [$i] ['sum'] = $sum; // 把sum属性加到每个item里
		}
		
		$items = $this->array_to_json ( $items );
		$this->assign ( [ 
				"Header" => $col_names . ",库存数量",
				"Widths" => "80,150,150,100,100,100,*",
				"ColSorting" => "int,str,str,str,str,str,int",
				"ColTypes" => "ro,ro,ro,ro,ro,ro,ro",
				/*"dd" => json_encode ( [ 
						"data" => $items 
				] ) */
				"dd" => $items 
		] );
		// return dump ( $items );
		return $this->fetch ();
	}
	public function indexPost() {
		$id = input ( "post.del_id" );
		// return dump ( $id );
		$data = Db::name ( "item" )->where ( "id", $id )->find ();
		return $data;
	}
	/**
	 * 出入库查询明细
	 */
	public function loglist() {
		$main = new Main ();
		$headers = "dd.id as ddid,name,type,maker,units,count,dusage,dremarks,mm.id as mmid,mcreate_time,form_type,username,usertel,location,owner,dept";
		$data = $main->alias ( "mm" )->join ( '__DETAIL__ dd', 'mm.id = dd.main_id' )->join ( '__ITEM__ ii', 'ii.id = dd.item_id' )->field ( $headers )->order ( "mmid" )->select ();
		$res = [ ];
		foreach ( $data as $k => $v ) {
			$res [] = $v->toArray ();
		}
		// return dump($res);
		// $csvstr = $this->array_to_json ( $res );
		// return $csvstr;
		$this->assign ( "dd", $this->array_to_json ( $res ) );
		return $this->fetch ();
	}
	public function loglist_v2() { // 测试用,不好用
		$main = new Main ();
		$headers = "name,type,maker,units,class,count,location,mcreate_time,owner,musage,dept,username,usertel,form_type,progress,mremarks";
		$data = $main->alias ( "mm" )->join ( '__DETAIL__ dd', 'mm.id = dd.main_id' )->join ( '__ITEM__ ii', 'ii.id = dd.item_id' )->field ( $headers )->order ( "name desc" )->select ();
		$res = [ ];
		foreach ( $data as $k => $v ) {
			$res [] = $v->toArray ();
		}
		$this->assign ( "dd", json_encode ( $res, 256 ) );
		return $this->fetch ();
	}
	/**
	 * 新到入库
	 */
	public function new_change() {
		if (Request::instance ()->isGet ()) {
			$attr = $this->getTableAttr ( 'asset_main_change' );
			$this->assign ( [ 
					'fields' => $attr ['fields'],
					'headers' => $attr ['headers'],
					'defaultval' => $attr ['defaultval'],
					'sortattr' => $attr ['sortattr'],
					'edattr' => $attr ['edattr'],
					'diswidth' => $attr ['diswidth'],
					'validation' => $attr ['validation'] 
			] );
			return $this->fetch ( "new_change" );
		} else {
			$dataa = input ( 'post.data/a' ); // 获取post数据
			$result = $this->csv_to_array ( explode ( ",", $dataa ['mygrid__ColNames'] ), $dataa ['data'] );
			// return dump ( $dataa['table'] )."<br>".dump ( $result );
			$main = new Main ();
			$main->allowField ( true )->data ( $dataa ['table'], true )->isUpdate ( false )->save ();
			$main_id = $main->id;
			$item = new Item ();
			$detail = new Detail ();
			// 多行数据逐行处理
			foreach ( $result as $data ) {
				$field = [ 
						'name' => $data ['name'],
						'type' => $data ['type'],
						'maker' => $data ['maker'] 
				];
				$iid = $item->where ( $field )->value ( 'id' );
				// item 不存在则新增
				if ($iid == null) {
					// $data ['icreate_time'] = date("Y-m-d H:i:s",time());
					$item->allowField ( true )->data ( $data, true )->isUpdate ( false )->save ();
					$data ['item_id'] = $item->id;
				} else {
					$data ['item_id'] = $iid;
				}
				$data ['main_id'] = $main_id;
				$detail->allowField ( true )->data ( $data, true )->isUpdate ( false )->save ();
			}
			
			return $this->success ( "", url ( 'manage/index' ) );
		}
	}
	public function setting() {
		return $this->fetch ();
	}
	
	/**
	 * 获取表属性
	 *
	 * @param string $table_name        	
	 * @param unknown $uid        	
	 */
	private function getTableAttr($uid) {
		return Db::name ( 'table_attr' )->where ( 'uid', $uid )->find ();
	}
}<file_sep>/application/othermode/controller/Index.php
<?php

namespace app\othermode\controller;

class Index {
	public function index() {
		return "ordermode<br>复制本目录文件需要修改namespace为对应的新建模块名";
	}
}
<file_sep>/README.md
## X.Da小项目:bow:

[![GitHub license](https://img.shields.io/github/license/thianda/phpweb.svg)](https://github.com/thianda/phpweb/blob/master/LICENSE) [![Build Status](https://travis-ci.org/thianda/phpweb.svg?branch=master)](https://travis-ci.org/thianda/phpweb) [![HitCount](http://hits.dwyl.io/thianda/phpweb.svg)](http://hits.dwyl.io/thianda/phpweb)

基于ThinkPHP 5.0.x开发

更新log可查看 [CHANGELOG](CHANGELOG.md)

目前模块有：

| 模块名       | 功能描述                                     |
| --------- | ---------------------------------------- |
| docs      | 开发文档库( *beta*) :fire:                    |
| checkME60 | 城域网BAS设备健康度检查(**done**) :heavy_check_mark: |
| common    | 公共库                                      |
| esserver  | excel服务器辅助(**done**) :heavy_check_mark:  |
| index     | 首页                                       |
| kxemas    | 客响维材管理系统(*shutdown)  :no_entry_sign:     |
| othermode | template模板                               |
| trouble   | 办公终端报修流程(*pause) :wrench:                |
| zx_apply  | 专线业务开通辅助(*alpha*) :fire:                 |

本地 clone 后需要在本目录下执行一次 `composer install`

composer 的安装及使用请查看： [composer 中文网](http://www.phpcomposer.com/ )

> 另：`public/static/`内文件已打包，不保存在repo中。详见[release](https://github.com/thianda/phpweb/releases)。
>
> `docs`需搭环境（*jekyll*）在`docs/`内执行`x.da-build-sh.sh`生成静态文档。

### 生成docs步骤

以windows系统为例，安装git-for-windows后，使用 git-bash，切换到`/docs`目录

 1. 安装 rubyinstaller

   [官网下载](https://rubyinstaller.org/downloads/)安装

 2. 安装 jekyll

   ```sh
   gem install jekyll
   ```
   安装出错请参考[这里](https://thianda.github.io/notes/create-blog-by-using-jekyll.html#%E5%AE%89%E8%A3%85jekyll)。

 3. 安装 bundler

   ```sh
   gem install bundler
   ```

4. 安装依赖

  ```sh
  bundle install
  ```

5. 生成静态文档到`/public/phpweb`

  ```sh
  ./x.da-build-sh.sh
  ```

### LICENSE

MIT<file_sep>/application/kxeams/controller/User.php
<?php

namespace app\kxeams\controller;

use think\Db;

class User extends C {
	/**
	 * 管理条例
	 *
	 * {@inheritdoc}
	 *
	 * @see \app\controller\C::index()
	 */
	public function index() {
		$this->assign ( [ 
				"laws" => $this->getSysInfo ( "laws" ) 
		] );
		return $this->fetch ();
	}
	/**
	 * 资产概况
	 */
	public function main() {
		return $this->fetch ();
	}
	/**
	 * 领用申请
	 */
	public function apply() {
		if (request ()->isGet ()) {
			return $this->fetch ( "apply" );
		} else {
			// 处理提交的数据
			 return dump ( input ());
			$applyFormData = input ( "apply/a" );
			$formAccount = $applyFormData [0] ['formAccount'];
			$applyFormData [0] ['progress'] = config ( "progress.0" );
			$main = model ( "Main" );
			if ($main->allowField ( true )->save ( $applyFormData [0] )) {
				// 保存 main (流水账) 返回的 id 用来保存到 detail 表
				$main_id = $main->id;
				$formName = [ 
						"item_id",
						"count",
						"dusage",
						"dremarks" 
				];
				$list = [ ];
				for($id = 1; $id <= $formAccount; $id ++) {
					// 遍历 领用物品的 Form
					$list [$id - 1] ["main_id"] = $main_id;
					for($i = 0; $i < count ( $formName ); $i ++)
						// 遍历 FormName 取值
						$list [$id - 1] [$formName [$i]] = $applyFormData [$id] [$formName [$i]];
				}
				$detail = model ( "detail" );
				if ($detail->allowField ( true )->saveAll ( $list )) {
					return [ 
							"state" => 1,
							"text" => "提交成功" 
					];
				} else {
					// 保存 detail 数据表出错
					return [ 
							"state" => 0,
							"title" => "保存领用明细数据表出错",
							"type" => "alert-error",
							"text" => "请联系开发者处理。" 
					];
				}
			} else {
				// 保存 main 数据表出错
				return [ 
						"state" => 0,
						"title" => "保存领用表单数据表出错",
						"type" => "alert-error",
						"text" => "请联系开发者处理。" 
				];
			}
		}
	}
	public function todo() {
		if (request ()->isGet ()) {
			$this->assign ( "list", $this->refleshTodoList () );
			return $this->fetch ();
		} else {
			$req = input ( "post.req" );
			$main_id = input ( "post.main_id" );
			if ($req == "getDetail") {
				return $this->todoDetail ( $main_id );
			}
			$form_type = mb_substr ( input ( "post.form_type" ), 2 );
			$from_store = input ( "post.from_store" );
			$mlogs = strlen ( input ( "post.mlogs" ) ) == 0 ? "" : $mlogs . "\n";
			if ($req == "yes") {
				Db::name ( "main" )->where ( "id", $main_id )->update ( [ 
						"from_store" => $from_store,
						"progress" => config ( $form_type . "进度.2" ),
						"mlogs" => $mlogs . config ( $form_type . "进度.2" ) . "，" . session ( ('user.name') ) . "，" . date ( 'Y-m-d H:i:s' ) 
				] );
				return $this->refleshTodoList ();
			} else if ($req == "no") {
				Db::name ( "main" )->where ( "id", $main_id )->update ( [ 
						"from_store" => $from_store,
						"progress" => config ( $form_type . "进度.1" ),
						"mlogs" => $mlogs . config ( $form_type . "进度.1" ) . "，" . session ( ('user.name') ) . "，" . date ( 'Y-m-d H:i:s' ) 
				] );
				return $this->refleshTodoList ();
			} else {
				return $this->error ( "无法执行您的请求。" );
			}
		}
	}
	/**
	 * 刷新 todoList 数据
	 *
	 * @return string
	 */
	protected function refleshTodoList() {
		$where1 = "`progress` = '" . config ( "领用进度.0" ) . "' and `form_type` = '维材领用' and `from_dept` = '" . session ( "user.dept" ) . "'";
		$where2 = "`progress` = '" . config ( "发放进度.0" ) . "' and `form_type` like '%发放' and `from_dept` = '" . session ( "user.dept" ) . "'";
		$data = Db::name ( "main" )->where ( $where1 )->whereOr ( $where2 )->order ( "mcreate_time desc" )->select (  );
		return json_encode ( $data, 256 );
	}
	public function history() {
		$where = [ 
				'to_dept' => session ( "user.dept" ) 
		];
		$list = $this->getMain ( $where );
		$this->assign ( "list", $list );
		return $this->fetch ();
	}
	protected function getMain($where = []) {
		$data = Db::name ( "main" )->where ( $where )->order ( "mcreate_time desc" )->select ();
		return json_encode ( $data, 256 );
	}
	/**
	 * 根据条件查询数据(无用)
	 *
	 * @param string $table        	
	 * @param string $field        	
	 * @param array $where        	
	 */
	public function match_item($field = '', $where = []) {
		if ($field == '')
			$field = input ( "post.field" );
		if ($where == '')
			$where = input ( "post.where/a" );
		return Db::name ( "item" )->where ( $where )->field ( $field )->select ();
	}
	public function t() {
		$a = [ ];
		$a [0] ["name"] = "name0";
		$a [0] ["type"] = "type0";
		$a [1] ["name"] = "name1";
		$a [1] ["type"] = "type1";
		$b = [ 
				[ 
						"name" => "name0",
						"type" => "type0" 
				],
				[ 
						"name" => "name1",
						"type" => "type1" 
				] 
		];
		echo "a<br>" . dump ( $a );
		echo "b<br>" . dump ( $b );
		die ();
		// return dump(json_encode($b));
		
		$field = "name,type,maker,units";
		$where ["class"] = "备品";
		$where ["units"] = "箱";
		return dump ( $this->match_item ( $field, $where ) );
	}
}<file_sep>/application/kxeams/controller/Index.php
<?php

namespace app\kxeams\controller;

use think\Controller;
use think\Session;
use think\Db;
use think\Request;

class Index extends Controller {
	protected function _initialize() {
		// $this->assign("version" , C::getSysInfo ( "version" ));
		$this->assign ( "version", config ( "version" ) );
	}
	public function index() {
		return $this->redirect ( 'default' );
	}
	public function t() {
		return $this->replace ( "aaa'aa" );
		// return $this->fetch("t",[],Config::get('view_replace_str'));
		// return $this->fetch ( "t" );
	}
	public function login() {
		$info ["txtUser"] = input ( "post.txtUser" );
		$info ["txtPwd"] = input ( "post.txtPwd" );
		$info ["txtUser"] = $this->replace ( $info ["txtUser"] );
		$info ["txtPwd"] = $this->replace ( $info ["txtPwd"] );
		if ($info ['txtUser'] == null || $info ['txtPwd'] == null) {
			return $this->redirect ( 'default' );
		}
		// 首先验证kxeam用户
		$user = Db::query ( "select `name`,`role`,`dept`,`office`,`tel`,`v_no` from asset_user where `loginname`=? and password=?", [ 
				$info ["txtUser"],
				$info ["txtPwd"] 
		] );
		if (null == $user) {
			// 若kxeam验证失败，验证ESserver用户
			$sql = "select UserLogin,UserName from [ESApp1].[dbo].[ES_User] where UserLogin = '" . $info ["txtUser"] . "' and UserPwd = '" . $info ["txtPwd"] . "'";
			$ESuser = $this->query ( $sql );
			if (null == $ESuser) {
				return $this->error ( "账号或密码错误，请重试！", "index", "", 10 );
			} else {
				$user = Db::query ( "select `name`,`role`,`dept`,`office`,`tel`,`v_no` from asset_user where `loginname`=?", [ 
						$ESuser [0] ['UserLogin'] 
				] );
			}
		}
		if ($user != [ ]) {
			$user = $user [0]; 
			// 同步一下密码
			db ( "user" )->where ( 'loginname', $info ["txtUser"] )->setField ( 'password', $info ["txtPwd"] );
			foreach ( $user as $k => $u ) {
				Session::set ( "user." . $k, $u );
			}
			if (strpos ( $user ['role'], "管理员" ) !== false) {
				return $this->success ( "登录成功，" . $user ['name'] . "，正在跳转", "Manage/index", "", 1 );
			} else if (strpos ( $user ['role'], "用户" ) !== false) {
				return $this->success ( "登录成功，" . $user ['name'] . "，正在跳转", "User/index", "", 1 );
			} else {
				// return dump ( $user );
				return $this->error ( "权限获取异常，请联系管理员！", "", "", 10 );
			}
			// return dump ( Session::get ( 'user' ) );
		} else {
			return $this->error ( "您无权登陆维材管理，请联系管理员！", "index", "", 10 );
		}
		
		// 默认 密码 _<PASSWORD> md5: 65<PASSWORD>54e3e3051651cb7<PASSWORD>4db2
		// 123456 md5: e10adc3949ba59abbe56e057f20f883e
		// $this->success ( "登录成功，正在跳转...", "Manage/index" );
	}
	/**
	 * 执行Sql语句，$encoding 为 true，将执行结果的数组key转换为UTF-8（处理sqlServer表列名为中文时乱码的情况）
	 *
	 * @param unknown $sql        	
	 * @param string $encoding        	
	 * @return mixed|NULL
	 */
	protected function query($sql, $encoding = false) {
		$res = Db::connect ( "db_esserver" )->query ( $sql );
		if ($encoding) {
			$ans = [ ];
			for($i = 0; $i < count ( $res ); $i ++) {
				foreach ( $res [$i] as $k => $v ) {
					$ans [$i] [iconv ( "GB2312", "UTF-8//IGNORE", $k )] = $v;
				}
			}
			$res = null;
			return $ans;
		} else {
			return $res;
		}
	}
	protected function replace($str) {
		return preg_replace ( "/\'/", "", $str );
	}
	public function _empty() {
		$request = Request::instance ();
		$dir = APP_PATH . $request->module () . DS . "view" . DS . $request->controller () . DS . $request->action () . "." . config ( 'template.view_suffix' );
		if (file_exists ( $dir ))
			return $this->fetch ( $request->action () );
		else {
			return $this->error ( "请求未找到，将返回上一页...(kxeams/controller/Index.php->_empty())" );
		}
	}
}
<file_sep>/application/zx_apply/model/Infotables.php
<?php

namespace app\zx_apply\model;

use think\Model;
use traits\model\SoftDelete;

class Infotables extends Model
{
	use SoftDelete;
	protected $deleteTime = 'delete_time';
	protected $autoWriteTimestamp = true;
	protected $dateFormat = 'Y-m-d';
	protected $type = [ 
			// "aDate" => "date",
		"extra" => "array"
	];
	public function setIpAttr($value)
	{
		if (is_int($value)) {
			return $value;
		} else {
			return Iptables::ip_parse($value)[2];
		}
	}
	public function getIpAttr($value, $data)
	{
		return Iptables::ip_export($value, isset($data["ipMask"]) ? $data["ipMask"] : -1);
		return $value ? long2ip($value) : null;
	}
	public function setIpBAttr($value)
	{
		if (is_int($value) || "" == $value) {
			return $value;
		} else {
			return Iptables::ip_parse($value)[2];
		}
	}
	public function getIpBAttr($value, $data)
	{
		return Iptables::ip_export($value, isset($data["ipBMask"]) ? $data["ipBMask"] : -4);
	}
	public function setNeFactoryAttr($value)
	{
		// if (preg_match_all ( "/[0-9]/", $tt ) == strlen ( $tt )) {
		if (is_numeric($value) && floor($value) == $value) {
			// 是数字，且是整数
			return $value;
		} else {
			$ne = array_search($value, [
				"华为",
				"中兴",
				"ONU"
			]);
			return is_int($ne) ? $ne : null;
		}
	}
	public function getNeFactoryAttr($value)
	{
		$zx_nefactory = [
			0 => "华为",
			1 => "中兴",
			2 => "ONU",
			3 => null
		];
		return is_null($value) ? null : $zx_nefactory[$value];
	}
	// public function getStatusAttr($value) {
	// $statusArr = [
	// 0 => "申请",
	// 1 => "预分配",
	// 2 => "已流程",
	// 3 => "已备ip",
	// 4 => "已做数据",
	// 9 => "历史导入"
	// ];
	// return array_key_exists ( $value, $statusArr ) ? $statusArr [$value] : "";
	// }
	/**
	 * 新增Info，type可选导入、申请
	 *
	 * @param string $data        	
	 * @param string $type        	
	 * @return number[]|\think\false[]
	 */
	public static function createInfo($data = "", $type = "")
	{
		$result = [];
		if ($type == "import") {
			foreach ($data as $k => $d) {
				$infotables = new static();
				$data[$k] = array_merge([
					"tags" => "导入",
					"status" => 9
				], $data[$k]);
				// 清除空元素
				$data[$k] = array_filter($data[$k]);
				$infotables->isUpdate(false)->allowField(true)->save($data[$k], []);
				$result[$k] = $infotables->id;
				if (isset($data[$k]["vlan"]) && isset($data[$k]["aStation"])) {
					// 如果vlan不为空，则记录vlan表
					Vlantables::createVlan($data[$k]["aStation"], $data[$k]["vlan"], $data[$k]["cName"], $result[$k]);
				}
			}
		}
		if ($type == "apply") {
			$infotables = new static();
			$data["tags"] = "申请";
			$data["status"] = 0;
			$result = $infotables->isUpdate(false)->allowField(true)->save($data, []);
		}
		return $result;
	}
}<file_sep>/application/trouble/controller/Index.php
<?php

namespace app\trouble\controller;

use app\trouble\controller\Common;
use think\Db;
use app\trouble\model\Forms;
use think\Validate;
use think\Session;

class Index extends Common {
	public function t() {
		return base64_decode ( input ( "t" ) );
	}
	/**
	 * 登陆
	 *
	 * @return mixed|string
	 */
	public function index() {
		if (input ( "?param.u" ) && input ( "?param.p" )) {
			return $this->loginIn ( input ( "param.u" ), input ( "param.p" ), input ( "param.e/d" ), input ( "param.m/d" ) );
		} else {
			if (request ()->isMobile ()) {
				return $this->redirect ( "main" );
			} else {
				return $this->fetch ( "main_pc" );
			}
		}
	}
	private function loginIn($userLogin = null, $userPwd = null, $encrypt, $email = false) {
		if (preg_match ( "/[^A-Za-z0-9=]+/", $userLogin )) {
			return $this->error ( "参数格式错误" );
		}
		// 获取用户名
		$u = base64_decode ( $userLogin );
		if (! Validate::max ( $u, 20 )) {
			return $this->error ( "输入参数异常：Too many..." );
		}
		
		// 根据登录方式登录
		if ($email) {
			return $this->loginEmail ( $u, base64_decode ( $userPwd ) );
		} else {
			// 根据密码格式获取密码
			if ($encrypt) {
				// $encrypt大于 1 表示账号登录提交的base64_decode『基于window.btoa的 utf8_to_b64()』。
				// $encrypt等于 1 表示明文提交（测试用）
				// $encrypt等于 false 表示提交的MD5(书签访问)
				$userPwd = $encrypt > 1 ? base64_decode ( $userPwd ) : $this->pwd ( $userPwd );
			}
			return $this->loginES ( $u, $userPwd );
		}
	}
	private function pwd($p = '') {
		return strtoupper ( md5 ( $p ) );
	}
	private function loginEmail($email, $vcode) {
		$info = Db::name ( "check" )->where ( "loginName", $email )->order ( "time desc" )->limit ( 1 )->find ();
		if ($info) {
			if (sprintf ( "%04s", $info ['code'] ) == $vcode) {
				if (time () - strtotime ( $info ['time'] ) < 1800) { // 30分钟之内
					$user = $this->getLocalinfo ( null, $email );
					foreach ( $user as $k => $u ) {
						Session::set ( "user." . $k, $u );
					}
					return $this->success ( "登录成功", "view", null, 1 );
				} else {
					return $this->error ( "验证码已过期，请重新获取" );
				}
			} else {
				return $this->error ( "验证码错误" );
			}
		} else {
			return $this->error ( "请先获取验证码" );
		}
	}
	private function loginES($u, $userPwd) {
		$field = 'UserLogin,UserName,UserPwd,Email,MobilePhone';
		$user = Db::connect ( config ( "db_esserver" ) )->name ( "User" )->field ( $field )->where ( "UserLogin", $u )->find ();
		if (empty ( $user )) {
			return $this->error ( '用户[' . $u . ']不存在<br>请重试' );
		}
		if ($user ['UserPwd'] == $userPwd) {
			$user = $this->getLocalinfo ( $user );
			foreach ( $user as $k => $u ) {
				if ($k != 'UserPwd') {
					Session::set ( "user." . $k, $u );
				}
			}
			if (! request ()->isMobile () && ! request ()->isAjax ()) {
				return $this->display ( "<h1>登陆成功，<a href='view.html'>跳转</a></h1>" );
			}
			return $this->success ( "登录成功", "view", null, 1 );
		} else {
			return $this->error ( "密码错误" );
		}
	}
	private function getLocalinfo($u, $e = null) {
		$field = 'UserName,UserDept,UserDept2,UserRole';
		if ($u) {
			$where = [ 
					"UserLogin" => $u ['UserLogin'] 
			];
		} else {
			$u = [ 
					'Email' => $e 
			];
			$where = $u;
		}
		$info = Db::name ( "user" )->field ( $field )->where ( $where )->find ();
		if ($info) {
			foreach ( $info as $k => $i ) {
				$u [$k] = $i;
			}
		}
		return $u;
	}
	protected function sendEmail($address = '', $subject = '', $body = '', $url = "") {
		$mail = new \PHPMailer ();
		$mail->isSMTP (); // Set mailer to use SMTP
		$mail->CharSet = "utf-8";
		$mail->SetLanguage ( 'zh_cn' );
		// $mail->SMTPDebug = 1;
		$mail->Host = 'smtp.139.com'; // Specify main and backup SMTP servers
		$mail->SMTPAuth = true; // Enable SMTP authentication
		$mail->Username = "<EMAIL>"; // SMTP username
		$mail->Password = '<PASSWORD>'; // SMTP password
		                                  // $mail->SMTPSecure = 'tls'; // Enable TLS encryption, `ssl` also accepted
		$mail->Port = 25; // TCP port to connect to
		$mail->setFrom ( '<EMAIL>', '终端故障报修' );
		$mail->addAddress ( $address ); // Name is optional
		                                // $mail->addReplyTo ( '<EMAIL>', 'Information' );
		                                // $mail->addCC ( '<EMAIL>' );
		                                // $mail->addBCC ( '<EMAIL>' );
		                                // $mail->addAttachment ( '/var/tmp/file.tar.gz' ); // Add attachments
		                                // $mail->addAttachment ( '/aa.jpg', '附件new.jpg' ); // Optional name
		                                // 绝对路径从磁盘根目录算起，相对路径从public/idnex.php算起。
		$mail->isHTML ( true ); // Set email format to HTML
		$mail->Subject = $subject;
		$mail->Body = $body;
		$mail->AltBody = '您的邮件客户端不支持查看HTML格式的邮件正文。请换个方式查看此邮件';
		
		if (! $mail->send ()) {
			return $mail->ErrorInfo;
		} else {
			return true;
		}
	}
	/**
	 * 获取邮件验证码
	 *
	 * @param string $e        	
	 * @return void|string
	 */
	public function getVcode($e = '', $ttl = 120) {
		if (preg_match ( '/[^A-Za-z._]+/', $e )) {
			return $this->error ( '非法邮箱地址哦' );
		} else {
			$e = strtolower ( $e );
		}
		// 检查30分钟内是否已申请
		$data = Db::name ( "check" )->whereTime ( 'time', '-31 min' )->where ( "loginName", $e )->order ( "time desc" )->find ();
		if ($data) {
			// if (time () - strtotime ( $data ['time'] ) < 1800) {
			return $this->success ( "距离上一次申请间隔小于30分钟，请勿重复操作。" );
			// }
		} else {
			// 检查生成的验证码是否与生效中的其他用户的相同
			$data = Db::name ( "check" )->field ( "code" )->whereTime ( 'time', '-31 min' )->select ();
			$codes = [ ];
			foreach ( $data as $d ) {
				$codes [] .= $d ['code'];
			}
			$vcode = rand ( 0, 9999 );
			while ( in_array ( $vcode, $codes ) ) {
				$vcode = rand ( 0, 9999 );
			}
			// 存入数据库
			$insertData = [ 
					'code' => $vcode,
					'loginName' => $e 
			];
			Db::name ( "check" )->insert ( $insertData );
		}
		$address = $e . '@ln.chinamobile.com';
		$subject = '-终端故障报修-您的登录验证码为：【' . sprintf ( "%04s", $vcode ) . '】请在30分钟内使用。';
		$body = '<p>您申请了邮箱登录的验证码，若本次非本人操作，请忽略本邮件</p><hr><br>
				<p style="text-align:right;">Powered by <a href="")">Thianda</a></p>';
		// $sendEmail = $this->sendEmail ( $address, $subject, $body );
		$sendEmail = true;
		if (is_bool ( $sendEmail )) {
			$msg = "验证码已通过邮件发送，请到邮箱内查收来自<b><EMAIL></b>的邮件。";
			return $this->success ( $msg, null, 2 * $vcode );
		} else {
			Db::name ( "check" )->where ( 'code', $vcode )->delete ();
			return $this->error ( '邮件发送未成功：' . $sendEmail );
		}
	}
	/**
	 * 首页 查看所有历史申请单
	 *
	 * @param number $page        	
	 * @return mixed|string
	 */
	public function view() {
		return $this->fetch ();
	}
	public function getView($page = 1, $listRows = 3) {
		$field = "id,applicant,createTime,troubleDescrition,status";
		$data = Db::name ( "forms" )->field ( $field )->order ( 'createTime desc' )->page ( $page, $listRows )->select ();
		$data = Forms::beautify ( $data );
		return json ( $data );
	}
	public function detail($id = 0) {
		$data = Db::name ( "forms" )->find ( $id );
		$data = Forms::beautify ( $data, true );
		$this->assign ( "data", $data );
		return $this->fetch ();
	}
	public function more($id = 0) {
		return $this->detail ( $id );
	}
	public function sendList() {
		$list = db ( "user" )->field ( [ 
				'concat(UserName," ",MobilePhone)' => "UserName" 
		] )->where ( "UserRole", 4 )->select ();
		return json ( $list );
	}
	public function getDetail($id = 0) {
		// $fieldMode = [
		// // 流水查看
		// 'flow' => "id,applicant,createTime,troubleDescrition,status",
		// // 部门审批详细页
		// 'deptA' => "id,applicant,emailAddr,createTime,dept2,dept,troubleType,troubleDescrition,logs,status",
		// // 客响审批详细页
		// 'kxA' => "id,applicant,emailAddr,createTime,dept2,dept,troubleType,troubleDescrition,applicationApproval,applicationApprovalTime,logs,status",
		// // 受理派单详细页
		// 'receive' => "id,applicant,emailAddr,createTime,dept2,dept,troubleType,troubleDescrition,applicationApproval,applicationApprovalTime,approvalOpinion,approvalTime,logs,status",
		// // 代维受理详细页
		// 'accept' => "id,applicant,emailAddr,createTime,dept2,dept,troubleType,troubleDescrition,applicationApproval,applicationApprovalTime,approvalOpinion,approvalTime,receiver,dispatchTime,logs,status",
		// // 申请人确认详细页
		// 'confirm' => "id,applicant,emailAddr,createTime,dept2,dept,troubleType,troubleDescrition,applicationApproval,applicationApprovalTime,approvalOpinion,approvalTime,receiver,dispatchTime,acceptanceTime,results,logs,status"
		// ];
		$field = "";
		$data = Db::name ( "forms" )->field ( $field )->find ( $id );
		$data = Forms::beautify ( $data, true );
		return json ( $data );
		session ( "user.UserRole" );
	}
	// 修改报修单信息(无安全验证)
	public function handle() {
		$data = input ();
		if (! empty ( $data ) && ! empty ( $data ['id'] )) {
			Forms::update ( $data, [ 
					"id" => $data ['id'] 
			], true );
			return $this->getDetail ( $data ['id'] );
		} else {
			return $this->display ( "<h1>传入为空</h1>" );
		}
	}
	// 修改报修单信息(验证权限)
	public function handlec() {
		$field = 'logs,status';
		$fieldAttr = [ 
				0 => 'marks',
				1 => 'applicationApproval,applicationApprovalTime',
				2 => 'approvalOpinion,approvalTime',
				3 => 'receiver,dispatchTime,managerConfirm',
				4 => 'acceptanceTime,results' 
		];
		$userRole = session ( "user.UserRole" ) ? session ( "user.UserRole" ) : 0;
		$field .= $fieldAttr [$userRole];
		$data = input ();
		if (! empty ( $data ) && ! empty ( $data ['id'] )) {
			Forms::update ( $data, [ 
					"id" => $data ['id'] 
			], $field );
			return $this->getDetail ( $data ['id'] );
		} else {
			return $this->display ( "<h1>传入为空</h1>" );
		}
	}
	public function todo() {
		$d = Db::name ( "check" )->find ( [ 
				"code" => "0123" 
		] );
		return dump ( time () - strtotime ( $d ['time'] ) );
	}
	public function getUser() {
	}
	/**
	 * 填写申请单
	 *
	 * return mixed|string
	 */
	public function apply() {
		return $this->fetch ();
	}
	/**
	 * 我的
	 *
	 * @return mixed|string
	 */
	public function my() {
		return $this->display ( implode ( session ( "user" ), "," ) );
	}
	/**
	 * 我的申请单
	 *
	 * @return mixed|string
	 */
	public function mine() {
		return $this->fetch ();
	}
}
<file_sep>/docs/404.md
---
layout: center
permalink: 404.html
---

```
404


      (_)                  | |       
__  __ _   __ _  _ __    __| |  __ _ 
\ \/ /| | / _` || '_ \  / _` | / _` |
 >  < | || (_| || | | || (_| || (_| |
/_/\_\|_| \__,_||_| |_| \__,_| \__,_|

                          
\_' o  __   __   __|  __  
/ \ | (__( |  ) (__| (__( 
                          
```

Sorry, we can't seem to find this page's pixylls.

<div class="mt3">
  <a href="{{ site.baseurl }}/" class="button button-blue button-big">Home</a>
  <a href="{{ site.baseurl }}/contact/" class="button button-blue button-big">Contact</a>
</div>
<file_sep>/application/kxeams/config.php
<?php
return [ 
		'version'=>"V1.0417-kx",
		'领用进度'=>[
				0=>"待审批",
				1=>"审批未通过",
				2=>"审批通过",
		],
		'发放进度'=>[
				0=>"待知晓",
				1=>"拒绝接收",
				2=>"同意接收",
		],
];<file_sep>/docs/_posts/2017-12-17-zx-apply.md
---
layout:         post
title:          专线开通全流程辅助工具
date:           2017-12-17 20:24:02
summary:        记录需求，开发思路等
categories:     notes
update_date:    2018-05-18 16:15:34
---

## 工作流程



```flow
st=>start: 0.申请
e=>end: 结束
op1=>operation: 1.审核并分配ip、vlan
op2=>operation: 2.发起资管流程
op3=>operation: 3.资管录ip等客户信息
op4=>operation: 4.工信部、集团ip备案
op5=>operation: 5.数据制作
op6=>operation: 6.资管录vlan信息
st->op1->op2->op3->op4->op5->op6->e
```

## 一、功能模块

### 首页登录

用户无需注册，在首页使用邮箱加验证码登录。登录后默认为前台用户。后台权限账号需修改配置预设，无法在前台系统中设置。

> 为防止滥发邮件验证码，可限制登录邮箱的域名，限制发送验证码频率。单位时间内超过预设频次可触发相应操作，如记录系统日志并发送告警邮件。

用户获取验证码操作和登录操作会记录系统日志。 

### IP/VLAN 申请

前台用户根据预设的表单填写客户信息。提交后，可触发相关操作，如记录系统日志，给后台账号发送待办邮件通知等。

表单数据可按专线类别区分。不同类别的专线可设置不同的填写字段， IP 地址彼此不冲突，可独立分配，计算。

### IP/VLAN 查询

前台用户可查询并导出自己申请过的客户信息为 excel。不可以直接修改，右键可以修改后重新提交申请，可以相同客户信息再申请多个 ip。

> `当前页搜索`和`全局搜索`可查询当前用户申请过的所有信息，`历史信息查询`可查询所有信息，但仅显示部分非敏感信息，每次`历史信息查询`均记录到系统日志。`历史信息查询`限制查询频率，单位时间内查询次数超过预设频次会触发发送告警邮件，并强制将前台用户踢下线。

### 待办

前台提交申请后，管理员可在待办中查看申请信息，审核后可参考预分配信息分配 ip、vlan，提交时会可触发相关操作，如输出某种格式的模板数据（excel 文件）并以邮件附件发送。

### 台账导入

历史数据可按模板修改为符合录入的格式，导入到系统。

> 导入操作无数据验证，需谨慎操作
>
> - 无有效性验证，即：不会验证 ip、vlan、实例标识等是否与已有数据冲突。
> - 无合法性验证，即：A 端基站等字段若不是枚举值，依然可录入系统。

### 数据查询

使用类 excel 的界面展示，每行一条数据。前台用户不可直接修改，但可以修改后重新申请。后台账号可查询、修改，删除，导出 excel 等其他操作。

附加功能不限于下列内容

- 可根据某字段设置不同行的背景色
- 可根据不同预设格式导出不同格式的 excel
- 可根据数据制作的逻辑生成脚本（客户名转拼音生成`description`）
- 导出全量台账，格式兼容旧台账。信息较全。

### 系统设置

设置最后一次分配的 ip，用于预分配推荐。

查看/补充 vlan 使用情况，可手动补充新增已使用 vlan，但不可删除。

## 二、使用帮助

> todo


## 三、数据结构

根据实际需求，数据产生的先后顺序，设计如下几张数据表：

### IP 模型

无对应数据表。模型内函数仅在处理时调用。

> IP地址存储类型：int 
> 1. 利用 MySQL 函数进行处理。可以采用 INET_ATON，INET_NTOA 函数进行转换。
> 2. 利用开发语言的函数进行处理，以 php 进行举例。可以采用 ip2long，long2ip 函数进行转换。

```php
# PHP存入时：
$ip = ip2long($ip);
# MySQLl取出时：
SELECT INET_ATON(ip) FROM table 
# PHP取出时，多一步：
$ip = long2ip($ip);
```

**主要函数功能**

预分配推荐、设置最后已分配 ip、检查 ip / ipB 是否冲突、ip 是否可用、ip 的字符串格式与 int 格式的相互转换。

### vlan 模型

同一台交换机内 vlan 不可重复

**主要函数功能**

录入 vlan、自动预分配、导入已使用 vlan、检查 vlan 是否冲突。

### 申请信息表模型

申请开通时提供的信息，基于模板 excel 表格，都是必填项。

除 `extra`字段均为普通字段，大部分与表单的字段对应。个别字段为系统生成，用于逻辑判断与数据计算。

extra 字段为附加字段，json 格式，可根据实际需求拓展。

### 系统日志模型

保存系统日志，任意操作可触发记录日志。

### 邮件验证码模型

记录发送的邮件验证码，验证时使用。

### 系统参数模型

预设某些系统参数。

## 四、功能逻辑

###　首页登陆

 <https://exceltl.tk/zx_apply/>

- 根据邮箱验证码登录，基于 localStorage 保存会话。有效期15天。
- 有效期内免验证登陆。通过 URL 访问会直接跳回登陆页面并自动登录。
- 登陆后判断权限，跳转至对应页面
- 域名已启用 TLS_AES 加密。已申请 https 证书
- 为防止滥发邮件验证码，可限制登录邮箱的域名，限制发送验证码频率。
- 单位时间内超过预设频次记录可触发响应操作，如记录系统日志并发送告警邮件。

### 用户-申请

- 填写信息，数据校验后可提交。并发送待办邮件给后台用户。

### 用户-信息查询

- 基于邮箱筛选，仅可查看/修改自己邮箱申请过的信息。查询结果使用 ajax 返回，并在前端重新渲染。
- 重新申请
- 同客户信息申请多个 ip
- 导出为 excel

### 管理-待办

- 显示申请后未自动分配的列表。点击显示详细信息，内容均可修改，填写 ip、vlan 后可提交
- ip 录入格式：`xxx.xxx.xxx.xxx[/mask]`，如：`172.16.17.32`或`172.16.32.24/30`


- 提交时更新数据表`infotables`。并返回提交结果和最新的待办列表
- 提交时自动判断录入的 ip、vlan 是否重复，重复则返回失败的提示，并自动关联`vlantables`

### 管理-导入历史数据

- 导入历史数据 （强校验）
  - 根据模板复制粘贴（可多条）
  - 单条填表录入（暂不支持，可使用`用户-申请 ip、vlan`代替）

### 管理-查询

- 展示台账，可全局按列模糊搜索

  > 仅`客户名`、`产品示例标识`、`vlan`列


- 点击单条台账，显示详细信息

- 直接编辑台账信息，提示是否同步至服务器的确认对话框，此设置当前回话内有效

  > 此处修改时不做数据冲突验证，需注意

- 部分字段涉及数据校验，当前页不可编辑，需在详细信息窗口标记为`刚申请`，然后回到待办页修改并提交

- 右键点击台账，发现更多操作


### 管理-设置

- 更新最后一次分配的 ip
- 更新录入 sw 交换机的已分配 vlan，如已存在则忽略，并给出执行结果。

## 五、需求分析

- 不需要显示所有信息，查询可由服务器完成。全局查询或导出全部台账为 excel 下载后手动查询。
- 数据呈现与数据存储格式不一致，存储格式可扩展，前后端数据同步操作需**额外开销**。
- 显示带有不同**标签**的数据的数量，点击显示详细条目信息。
- 需一眼看出分配到哪个 ip 了
- 独立的 vlan 预分配记录表
- 数据表设计字段类型需考虑历史数据迁移问题
  方案 1：数据表字段类型不严格限制，均为 varchar。  
  方案 2：设计两张数据表，保存旧数据的采用方案1设计，新数据符合规范，严格设置字段类型及大小。  
  方案 3：按照新数据严格限制，历史数据迁移前整改历史数据的数据规范，直至符合数据规范。

若方案 1，查询搜索方便，但性能有损。前端严格验证，数据格式、数据安全不可控。  
若方案 2，新数据格式规范无隐患，且便于将来大数据分析。 但查询需指定数据表，设计复杂， 
若方案 3，最优方案，整改耗时耗力。

## 六、使用场景涉及的操作备忘录

### 预分配ip、vlan--manage/todo

```
Manage/todo()
checkInstanceId()
checkAndSetIp()
checkAndSetVlan()->Vlantables::createVlan()
updateInfo()->Iptables::setLastIp()
refleshTodoList()
```

### 查询页面生成制作脚本--manage/query

```
Tool/_script.html
Magage/_getDevice9312Info()
```

### 查询页面修改/导出信息--manage/query

```
Manage/query()
getInfoData()
queryDelete()
Index/querySearch()
Index/queryUpdateInfo()
queryExport()
```

### 管理-导入历史数据--manage/import

```
Manage/import()
checkDateIsValid()
_ht_apply()->Infotables::createInfo()->->Vlantables::createVlan()
```

### 管理-设置-manage/settings

```
Manage/settings()
->Iptables::setLastIp()
->Vlantables::importUsedVlan()
```

<file_sep>/application/zx_apply/controller/Manage.php
<?php
namespace app\zx_apply\controller;

use think\Controller;
use think\Db;
use app\zx_apply\model\Vlantables;
use app\zx_apply\model\Infotables;
use app\zx_apply\model\Iptables;
use Overtrue\Pinyin\Pinyin;

class Manage extends Index
{

	protected $beforeActionList = [
		'checkAuth'
	];

	protected function checkAuth()
	{
		if (session("user.role") != "manage") {
			return $this->redirect("index/query");
		}
	}

	public function index()
	{
		return $this->redirect("todo");
	}

	/**
	 * 待办
	 *
	 * @param boolean $ignoreCheck
	 * @return void
	 */
	public function todo($ignoreCheck = false)
	{
		if (request()->isGet()) {
			$this->assign("list", $this->refleshTodoList());
			return $this->fetch();
		}
		if (request()->isPost()) {
			$req = input("get.req");
			// $infotables = new Infotables ();
			// 无论是什么请求，先获取数据库的 info
			$info = Infotables::get(input("post.id")); // 获取器数据
			$data = input("post.");
			if ($info) {
                // 设置不需要输出的字段
				$detail = $info->hidden([
					'aDate',
					'tags',
					'ipMask',
					'ipBMask',
					'create_time',
					'update_time',
					'delete_time'
				])->toArray();
			} else {
				return $this->success("这条待办找不到啦！");
			}
			$extra = $detail["extra"];
			if (is_array($extra)) {
				foreach ($extra as $k => $v) {
					$detail[$k] = $v;
				}
			}
			unset($detail["extra"]);
			$detail["ip"] = $info->ip; // 更正ip为 str 形式
			$detail["ipB"] = $info->ipB; // 更正ipB
			if ($req == "getDetail") {
                // 返回单条数据及同客户名的信息摘要
				$cName = mb_substr($info->cName, 2, 10, "utf-8");
				$field = "id,instanceId,cName,create_time,neFactory,vlan,aStation,ip,aPerson,aEmail,delete_time";
				$relative = collection(Infotables::withTrashed()->where("cName", "like", "%" . $cName . "%")->where("id", "<>", $info->id)->field($field)->select())->toArray();
				$result = [
					"related" => $relative,
					"detail" => $detail,
					"string" => $cName
				];
				return json($result);
			} else if ($req == "auto_pre") {
				// 若已有ip则不再分配ip
				if ($detail["ip"] == "") {
					$genIp = Iptables::generateIP($data["zxType"]);
				} else {
					$genIp = $detail["ip"];
				}
				// 若无A端基站则不分配vlan
				if ($data["aStation"] == "") {
					return [
						"genIp" => $genIp,
						"device" => null
					];
				} else {
					$device = config("aStation")[$data["aStation"]];
					$genVlan = Vlantables::generateVlan($device, null, 1);
					return [
						"genIp" => $genIp,
						"preVlan" => $genVlan["preVlan"],
						"usedVlans" => $genVlan["usedVlans"],
						"device" => $device
					];
				}
			} else if ($req == "distribution") {
				// 判断是否有变化
				$ifChanged = false;
				foreach ($detail as $k => $v) {
					if ($v != $data[$k]) {
						$ifChanged = true;
						break;
					}
				}
				if ($ifChanged) {
					$this->checkInstanceID($info, $data);
					$data = $this->checkAndSetIp($info, $data);
					$data = $this->checkAndSetVlan($data, $ignoreCheck);
					$this->updateInfo($data);
					// vlan 不为空，且 status 为 0 或者 3，则 status +1
					if (isset($data["vlan"]) && ($info->status == 0 || $info->status == 3)) {
						Infotables::where("id", $data["id"])->setInc("status");
					}
					return $this->result($this->refleshTodoList(), 1, "操作成功。<br>是否发送邮件通知给申请人？");
				} else {
					return $this->result(null, 2, "本次提交无修改");
				}
			}
		}
	}

	public function generateVlan()
	{
		$aStation = "";
		$aStationConf = config("aStation");
		if (array_key_exists(input("get.d"), $aStationConf)) {
			$device = $aStationConf[input("get.d")];
			return Vlantables::generateVlan($device, "预分配", 1);
		}
	}

	protected function updateInfo($data)
	{
		$extraHeader = config("extraInfo");
		foreach ($extraHeader as $k => $v) {
			$data["extra"][$v] = $data[$v];
			unset($data[$v]);
		}
        // unset ( $data ["delete_time"] );
		$infotables = new Infotables();
        // 更新单条数据
		$result = $infotables->isUpdate(true)->save($data, [
			"id" => $data["id"]
		]);
        // 更新最后分配的IP
		isset($data["ip"]) && Iptables::setLastIp($data["ip"]);
		$infotables->find($data["id"]);
		return $result;
	}

	protected function refleshTodoList()
	{
		$where = ["neFactory" => 2, "vlan" => null];
		$whereor = ["status" => 0];
		$field = "id,cName,create_time,aPerson,ifOnu,instanceId,zxType,aStation";
		// 显示 ONU 业务 vlan 为空的，和状态为 0 的
		$data = Infotables::where($where)->whereOr($whereor)->field($field)->order("create_time desc")->select();
		return json_encode($data, 256);
	}

	/**
	 * 发邮件告知申请已处理，并发送 IDC 备案信息给厂家联系人
	 */
	public function sendResultEmail()
	{
		if (request()->isGet() || !input("?post.id")) {
			return $this->result("Wrong Req", 0);
		}
		$address = []; // 忽略送来的参数，从数据库获取
		$order = "1,2,3,6,4,5,9,10,17,23,19";
		// instanceId,zxType,bandWidth,cName,vlan,ip,ipB,mEmail,aEmail
		$field = $this->getHeader("zx_apply-new-rb", $order, true);
		$items = explode(",", $this->getHeader("zx_apply-new-rb", $order));
		array_pop($items);
		$db = Infotables::field($field)->find(input("post.id"));
		$values = array_values($db->toArray());
		// $title = '[ip已分配]-' . $db->bandWidth . "M-" . $db->cName;
		$title = '铁岭IDC.ISP-' . $db->ip . "-" . $db->cName;
		$contact = base64_decode('a2FuZ2xpQGV2ZXJzZWMuY24=');
		$body = "";
		// $body .= "<p>请根据下面的ip字段，填写IDC/ISP备案信息，最新的要求和模板可在内网访问： <a href='http://10.65.187.202/i/isp'>http://10.65.187.202/i/isp</a> 查看和下载。</p>";
		// $body .= "<pre style='color:#000;background-color:#ccc;padding:15px;'><p style='color:red'>目前已实现自动生成 idc.isp 备案信息。若本邮件存在附件，";
		// $body .= "请<span style='color:blue'>客户经理</span>检查附件的内容是否准确、完整。<br>补充修改后请直接发送给厂家：</p>" . $contact . "<p style='color:red'>并抄送给:</p>" . $CC . "</pre>";
		$body .= "<br><table style='border-collapse:collapse;border:none;'>";
		for ($i = 0; $i < count($items); $i++) {
			$body .= "<tr>";
			$body .= "<th style='width:200px;border:solid #666 1px;'>" . $items[$i] . "</th><td style='width:500px;border:solid #666 1px;'>" . $values[$i] . "</td>";
			$body .= "</tr>";
		}
		$body .= "</table>";
		$address['Addr1'] = $contact;
		$address['CCm'] = $db->mEmail;
		$address['CCa'] = $db->aEmail;
		if($db->ifOnu){
			$CCs= config("manageEmails");
			foreach ($CCs as $k => $v) {
				$address['CC'.$k] = $v . "@ln.chinamobile.com";
			}
		}else{
			$address['CCu'] = session("user.email");
		}
		$attachment = Generator::generateIDCinfoFiles(input("post.id"));
		$result = $this->sendEmail($address, $title, $body, $attachment);
		unlink($attachment);
		return $this->result($result, is_bool($result) ? 1 : 0);
	}

	/**
	 * 信息查询
	 */
	public function query()
	{
		if (request()->isGet()) {
            // 访问
			$aStation = array_keys(config("aStation"));
			$zxTitle = [
				"label" => "zx_apply-new-rb",
				"order" => "24,1,4,5,6,9,10,19,22,23,26"
			];
			$this->assign([
				"aStationData" => implode(",", $aStation),
				"colHeaderData" => $this->getHeader($zxTitle["label"], $zxTitle["order"]),
				"colWidthsData" => $this->getColWidths($zxTitle["order"]),
				"data" => $this->getInfoData()->toJson()
			]);
			return $this->fetch();
		}
		if (request()->isPost()) {
            // 获取台账
            // return $this->getInfoData();
			input("post.r") == "info" && $data = $this->getInfoData(input("post.zxType"))->toArray();
			input("post.r") == "detail" && $data = Infotables::get(input("post.id"))->toJson();
			input("post.r") == "search" && $data = $this->querySearch(input("post."));
			input("get.r") == "update" && $data = $this->queryUpdateInfo(input("post."));
			input("post.r") == "delete" && $data = $this->queryDelete(input("post."));
			return $data;
		}
		if (request()->isPut()) {
            // 相关操作
			input("post.r") == "script" && $data = Generator::generateScript(input("post.id/a")[0]);
			input("post.r") == "export_zg" && $data = Generator::generateZgWorkflow(input("post.id"));
			input("post.r") == "export_jtip" && $data = Generator::generateJtIp(explode(",", input("post.id")));
			input("post.r") == "export_gxbip" && $data = Generator::generateGxbIp(explode(",", input("post.id")));
			input("post.r") == "export" && $data = $this->queryExport(input("post.zxType"));
			return $data;
		}
	}


	public function _getDevice9312Info()
	{
		return base64_decode(config("device9312"));
	}

	/**
	 * get:加载数据到handsontable并验证,
	 * post:上传,后台处理入库
	 * 1.默认post为新申请,移除ip、vlan信息再保存,
	 * 严格验证，不合规不许提交，标记status：0
	 * 2.带post参数type=import,视为旧信息导入,
	 * 生成ip表（全）和vlan表信息（不全）。直接入库，并标记tags:导入
	 * 3.为了新增字段不修改数据库，将新增字段用json保存到一列。
	 * 在csv_to_array时，需要获取额外的字段
	 *
	 * @return void|string|mixed|string
	 */
	public function _ht_apply()
	{
		$zxInfoTitle = [
			"label" => "zx_apply-new-rb",
			"order" => "0,1,2,3,4,5,6,7,8,9,10,12,13,14,15,16,17,18,19,29,30,31,32,33,34,35,36,37"
		];
		if (request()->isPost()) {
			$postData = input("post.data");
			$type = input("post.type");
			$dataHeader = $this->getHeader($zxInfoTitle["label"], $zxInfoTitle["order"], true);
            // 获取数据库的列名
			$dataHeader = explode(",", $dataHeader);
            // 根据列名和数据转成php数组
            // $postData = substr ( $postData, 3 ); // 莫名奇妙的前三个字节是垃圾数据。3天才研究出来，只能这样解决！！！(目前已在前端解决)
			$data = $this->csv_to_array($dataHeader, $postData);
            // return dump($data);
            // 获取额外的字段
			$extraHeader = config("extraInfo");
			foreach ($data as $k => $v) {
                // 清除空元素
				$data[$k] = array_filter($v);
				$temp = [];
				foreach ($extraHeader as $kk => $vv) {
					if (isset($data[$k][$vv])) {
						$data[$k]["extra"][$vv] = $data[$k][$vv];
						unset($data[$k][$vv]);
					}
				}
				if (isset($v["aStation"]) && $v["aStation"] == "柴河局") {
					$data[$k]["neFactory"] = isset($data[$k]["neFactory"]) ? $data[$k]["neFactory"] : "";
					$data[$k]["aStation"] .= "-" . $data[$k]["neFactory"];
				}
				$data[$k]["aDate"] = isset($data[$k]["aDate"]) ? $data[$k]["aDate"] : "";
				if ($this->checkDateIsValid($data[$k]["aDate"])) {
                    // 申请时间转存到 create_time
					$data[$k]["create_time"] = strtotime($data[$k]["aDate"]);
				}
			}
            // 若导入，ip/vlan信息要单独存储。
			$result = Infotables::createInfo($data, $type);
            // $result = $info->save($data[0]);
			return dump($result);
		}
		if (request()->isGet()) {
			if (input('?get.t')) {
				$aStation = array_keys(config("aStation"));
				$this->assign([
					"aStation" => implode(",", $aStation),
					'colHeaderData' => $this->getHeader($zxInfoTitle["label"], $zxInfoTitle["order"]),
					"colWidthsData" => $this->getColWidths($zxInfoTitle["order"])
				]);
				return $this->fetch("_ht_apply");
			}
		}
	}

	/**
	 * ip、vlan申请
	 *
	 * @return mixed|string
	 */
	public function import()
	{
        // post数据传给 _ht_apply()
		return $this->fetch();
	}

	/**
	 * 系统设置
	 *
	 * @return mixed|string
	 */
	public function settings()
	{
		if (request()->isGet()) {
			if (!strpos(request()->header("referer"), request()->action())) {
				session("settings_back_url", request()->header("referer"));
			}
			$lastIp = Iptables::ip_export(Db::table("phpweb_sysinfo")->where("label", "zx_apply-lastIP")->value("value"));
			$this->assign([
				"lastIp" => $lastIp
			]);
			return $this->fetch();
		} else if (request()->isPost()) {
			if (input("post.exec") == "ok_ip") {
				return Iptables::setLastIp(input("post.lastIpStr"));
			}
			if (input("post.exec") == "cal_ip") {
				$unusedIps = [];
                // todo
				return $this->result($unusedIps, 1);
			}
			if (input("post.exec") == "ok_vlan") {
				return Vlantables::importUsedVlan(input("post.device"), input("post.vlanImport"));
			}
		}
	}

	/**
	 * todo预分配时检查获取的数据与数据库中的ip/ipB是否重复
	 *
	 * @param unknown $info            
	 * @param unknown $data            
	 * @return void|NULL|unknown
	 */
	protected function checkAndSetIp($info, $data)
	{
		if ($info["ip"] != $data["ip"]) { // 获取的ip有变化，则检查是否冲突
			$ip = Iptables::check($data["ip"], "ip", $data["zxType"]);
			if ($ip)
				return $this->error("互联ip冲突，", null, $ip["cName"]);
		}
        // 设置ipMask
		$ip_array = Iptables::ip_parse($data["ip"]);
		$data["ip"] = $ip_array[2];
		$data["ipMask"] = $ip_array[1];
		if ($data["ipB"] == "") {
            // 设置 ipB为null
			$data["ipB"] = null;
			$data["ipBMask"] = null;
			return $data;
		}
		if ($info["ipB"] != $data["ipB"]) {
			$ipB = Iptables::check($data["ipB"], "ipB", $data["zxType"]);
			if ($ipB)
				return $this->error("业务ip冲突，", null, $ipB["cName"]);
		}
        // 设置ipBMask
		$ipB_array = Iptables::ip_parse($data["ipB"]);
        /* 若未提供ipBMask，默认强制设置ipBMask为-8 */
		$ipB_array[1] == -1 && $ipB_array = Iptables::ip_parse(Iptables::ip_export($ipB_array[0], -8));
        /* 修正ip为ip_start */
		$data["ipB"] = $ipB_array[2];
		$data["ipBMask"] = $ipB_array[1];
		return $data;
	}

	/**
	 * todo预分配时检查获取的vlan是否已分配，并记录/更新
	 *
	 * @param Infotables $data
	 * @param boolean $check
	 * @return Infotables $data
	 */
	protected function checkAndSetVlan($data, $ignoreCheck)
	{
		if ($data["vlan"] == "") {
			$data["vlan"] = null;
			Vlantables::createVlan($data["aStation"], $data["vlan"], $data["cName"], $data["id"]);
			return $data;
		}
		if ($ignoreCheck == false) {
			// default : $ignoreCheck = false
			$vlan = Vlantables::check($data["zxType"], $data["aStation"], $data["vlan"]);
			if ($vlan && $vlan["id"] != $data["id"]) {
				if ($vlan["code"]) {
					// (code = 2) vlan 存在但 aStation 不在预设范围内(为空)
					return $this->result(null, $vlan["code"], $vlan["cName"]);
				} else {
					// (code = 0) 找到 vlan 且 vlan 的 id 与自己的id不同
					return $this->result("是否强制分配？", $vlan["code"], $vlan["cName"]);
				}
			} else {
				// vlan 不存在或 vlan id 与自己的 id 相同，则返回执行 infotables 的更新
			}
		} else {
			// 基于 Id 更新,若更新 0 条，则新增
		}
		Vlantables::createVlan($data["aStation"], $data["vlan"], $data["cName"], $data["id"]);
		return $data;
	}

	/**
	 * 校验日期格式是否正确
	 *
	 * @param string $date
	 *            日期
	 * @param string $formats
	 *            需要检验的格式数组
	 * @return boolean
	 */
	protected function checkDateIsValid($date, $formats = array("Y-m-d", "Y/m/d"))
	{
		$unixTime = strtotime($date);
		if (!$unixTime) { // strtotime转换不对，日期格式显然不对。
			return false;
		}
        // 校验日期的有效性，只要满足其中一个格式就OK
		foreach ($formats as $format) {
			if (date($format, $unixTime) == $date) {
				return true;
			}
		}
		return false;
	}

}<file_sep>/application/esserver/controller/Index.php
<?php

namespace app\esserver\controller;

use app\common\controller\Common;
use think\Db;

class Index extends Common {
	public function index() {
		return $this->redirect ( "main" );
	}
	public function main() {
		if (request ()->isGet ()) {
			return $this->fetch ();
		} else {
			// 密码重置申请
			$input = input ( "post." ) ['data'];
			// return dump($input);
			$sql = "select * from [ESApp1].[dbo].[ES_User] where UserLogin = '" . $input ["UserLogin"] . "'";
			$res = $this->query ( $sql );
			if ($res == null) {
				$v = array_merge ( [ 
						"status" => "failed" 
				], $input );
				$this->log ( "Excel密码重置", $v );
				// 账户不存在
				return [ 
						"auth" => [ 
								"vUserLogin" => false 
						] 
				];
			} else {
				// 登录名存在
				$res = $res [0];
				$auth = [ 
						"vUserLogin" => true,
						"vUserName" => $res ['UserName'] == $input ['UserName'],
						"vMobilePhone" => $res ['MobilePhone'] == $input ['MobilePhone'] 
					// "newEmail" => false
				];
				$auth ["EmptyMsg"] = '';
				if ($res ['MobilePhone'] == '') { // 预留电话为空
					$auth ["vMobilePhone"] = false;
					$auth ["EmptyMsg"] .= "预留MobilePhone为空<br>";
				}
				$mail = $input ['Email'] . $input ['Email2'];
				if ($res ['Email'] == '') {
					// Email 为空
					$auth ["vEmail"] = false;
					$auth ["EmptyMsg"] .= "预留Email为空";
				} else if ($res ['Email'] == $mail) {
					// Email 相等
					$auth ["vEmail"] = true;
					$auth ["newEmail"] = "完全匹配";
				} else if (strpos ( $mail, $input ['UserLogin'] ) !== false) {
					// Email 包含 UserLogin 字段
					$auth ["vEmail"] = true;
					$auth ["newEmail"] = "与登陆名符合";
				} else {
					$auth ["vEmail"] = false;
				}
				$flag = '';
				$e = '';
				$num = 0;
				$aInfo = null;
				if ($auth ["vEmail"] == true) {
					$num ++;
					$flag = 'email';
					$e = $res ['Email'];
				}
				if ($auth ["vMobilePhone"] == true)
					$num ++;
				if ($flag == '')
					$e = $res ['MobilePhone'] . "@139.com";
				
				if ($num > 0) {
					$aInfo = base64_encode ( json_encode ( [ 
							"u" => $res ['UserLogin'],
							"p" => $res ['UserPwd'],
							"e" => $e 
					] ) );
				}
				return [ 
						"auth" => $auth,
						"e" => $e,
						"num" => $num,
						"aInfo" => $aInfo 
				];
			}
		}
	}
	public function resetpwd() {
		// 通过邮件链接访问
		if (request ()->isGet ()) {
			$r = input ( "param.r" );
			if ($r == null) {
				return $this->error ( "访问无效。", "main" );
			}
			$r = json_decode ( base64_decode ( $r ), true );
			// 若获取的 base64 编码不合法，则解析为null，计算的时间差为1900年到发信时间的时间差。
			if (time () - $r ["t"] > 86400) {
				$msg = "此链接已过期，您可重新申请找回。";
				return $this->error ( $msg, "main", '', 10 );
			}
			$sql = "select UserLogin,UserName from [ESApp1].[dbo].[ES_User] where UserLogin = '" . $r ["u"] . "' and UserPwd = '" . $r ["p"] . "'";
			$res = $this->query ( $sql );
			$flag = $res == null;
			if ($flag) {
				$status = "failed";
				$msg = "参数不对，请检查是否已通过其他途径修改过密码，并重新申请找回。";
			} else {
				$this->assign ( [ 
						'UserLogin' => $r ["u"],
						'UserPwd' => base64_encode ( $r ["p"] ) 
				] );
				$status = "success";
				$msg = "通过邮件链接访问";
			}
			// 记录log
			$v = [ 
					"status" => $status,
					"UserLogin" => $r ["u"],
					"e" => $r ["e"],
					"msg" => $msg 
			];
			$this->writeLog ( "Excel密码重置申请", $v );
			return $flag ? $this->error ( $msg, "main", null, 10 ) : $this->fetch ();
		}
		// main页面点击发送邮件按钮
		if (request ()->isPost ()) {
			$input = input ( 'post.excel_data_for_resetpwd' );
			if ($input == null) {
				// 若没有传值，则直接访问。
				return $this->fetch ();
			}
			$info = json_decode ( base64_decode ( $input ), true );
			$info ['t'] = time ();
			$r = base64_encode ( json_encode ( $info ) );
			// return dump ( $r );
			$subject = "【ESWeb】您刚刚申请Excel服务器账号" . $info ['u'] . "找回密码服务，请按正文操作。";
			$url ['out'] = "http://172.16.58.3:800/esserver/index/resetpwd.html?r=" . $r;
			$url ['in'] = "http://10.65.187.202/esserver/index/resetpwd.html?r=" . $r;
			$body = "<p>如果您未自己进行申请重置，请忽略此邮件。</p><hr>";
			$body .= "<p style='color: blue;'>请访问下面的链接进行重置密码（此链接有效期24小时）：</p>";
			$body .= "<p>内网>>><br><span style='font-size:10px;'><a href='" . $url ['in'] . "'>" . $url ['in'] . "</a>" . "</span></p>";
			$body .= "<p>外网>>><br><span style='font-size:10px;'><a href='" . $url ['out'] . "'>" . $url ['out'] . "</a></span></p>";
			
			$sendEmail = $this->sendEmail ( $info ['e'], $subject, $body );
			if (is_bool ( $sendEmail )) {
				$re = [ 
						'status' => true,
						'msg' => '相关邮件已发送，请到邮箱内查收主题包含<b>【ESWeb】</b>的邮件。' 
				];
			} else {
				$re = [ 
						'status' => false,
						'msg' => '邮件发送未成功：' . $sendEmail 
				];
			}
			$v = array_merge ( [ 
					"status" => is_bool ( $sendEmail ) ? "success" : "failed",
					"msg" => $re ["msg"] 
			], $info );
			$this->log ( "Excel密码重置发邮件", $v );
			return $re;
		}
		// 重置密码操作，保存新密码
		if (request ()->isPut ()) {
			$input = input ( "post." );
			if ($input ["UserPwd"] != $input ["UserPwd2"]) {
				return $this->error ( "两次密码不一致" );
			} else {
				$cipherPwd = strtoupper ( md5 ( $input ["UserPwd"] ) );
				if (! $this->validPwd ( $input ["UserPwd"] )) {
					return $this->error ( "必须包含大写字母、小写字母、数字三种组合在一起，且长度最少为8。请重试。", null, "密码复杂度不符合要求" );
				} else if ($this->validPwdHis ( $input ["UserLogin"], $cipherPwd )) {
					return $this->error ( "密码不能与最近2次历史密码重复，请重试。", null, "新密码不符合要求" );
				} else {
					// 更新密码操作。
					$p = base64_decode ( $input ["p"] );
					$sql = "update [ESApp1].[dbo].[ES_User] set UserPwd = '" . $cipherPwd . "',LstLoginTime = GETDATE(),PwdDate = GETDATE(),AccState = 0 where UserLogin = '" . $input ["UserLogin"] . "' and UserPwd = '" . $p . "'";
					$res = Db::execute ( $sql );
					$sql2 = "insert into [ESApp1].[dbo].[ES_UserPwdHis] values ((select UserId from [ESApp1].[dbo].[ES_User] where UserLogin = '" . $input ["UserLogin"] . "'), GETDATE(), '" . $cipherPwd . "')";
					Db::execute ( $sql2 );
					// $sql = "select * from [ESApp1].[dbo].[ES_User] where UserLogin = '" . $input ["UserLogin"]. "' and UserPwd = '" . $p. "'";
					// $res = $this->query ( $sql );
					// return dump($sql);
					if ($res > 0) {
						$msg = "密码重置成功。";
						$this->writeLog ( "Excel密码重置操作", [ 
								"status" => "success",
								"msg" => $msg,
								"name" => $input ["UserLogin"] 
						] );
						$this->success ( $msg, "main" );
					} else {
						$msg = "请检查是否已通过其他途径修改过密码或重试。";
						$this->writeLog ( "Excel密码重置操作", [ 
								"status" => "failed",
								"msg" => $msg,
								"name" => $input ["UserLogin"] 
						] );
						$this->error ( $msg, null, "未能重置密码" );
					}
				}
			}
		}
	}
	protected function query($sql, $encoding = false) {
		$res = Db::query ( $sql );
		if ($encoding) {
			$ans = [ ];
			for($i = 0; $i < count ( $res ); $i ++) {
				foreach ( $res [$i] as $k => $v ) {
					$ans [$i] [iconv ( "GB2312", "UTF-8//IGNORE", $k )] = $v;
				}
			}
			$res = null;
			return $ans;
		} else {
			return $res;
		}
	}
	protected function validPwd($pwd) {
		if (preg_match_all ( '/[A-Z]/', $pwd ) < 1)
			return false;
		else if (preg_match_all ( '/[a-z]/', $pwd ) < 1)
			return false;
		else if (preg_match_all ( '/[0-9]/', $pwd ) < 1)
			return false;
		else if (strlen ( $pwd ) < 7)
			return false;
		else
			return true;
	}
	/**
	 * 检查是否与近N此密码相同。
	 *
	 * @param unknown $u        	
	 * @param unknown $p        	
	 * @return boolean
	 */
	public function validPwdHis($u, $p, $t = 2) {
		$sql = "select top " . $t . " Pwd from [ESApp1].[dbo].[ES_UserPwdHis] where UserId = (select UserId from [ESApp1].[dbo].[ES_User] where UserLogin = '" . $u . "') order by CreTime desc";
		$PwdHis = Db::query ( $sql );
		$PwdHisArray = [ ];
		foreach ( $PwdHis as $v ) {
			$PwdHisArray [] = $v ['Pwd'];
		}
		return in_array ( $p, $PwdHisArray );
	}
	/**
	 * 反馈-密码找回
	 */
	public function reportLog() {
		if (request ()->isPost ()) {
			// return dump(input("param.UserLogin"));
			if (! input ( "?param.UserLogin" ) || ! input ( "?param.str" )) {
				return $this->error ( "参数无效。" );
			}
			$where = '{"UserLogin":"' . input ( "param.UserLogin" ) . '"%';
			$log = db ( "log", 'logDatabase', false )->where ( "v", "like", $where )->find ();
			if ($log == null) {
				// 不存在记录（从未反馈过）
				$logData = json_decode ( htmlspecialchars_decode ( input ( "param.str" ) ), 256 );
				$this->writeLog ( "找回密码一键反馈", $logData );
				$sendEmail = $this->sendEmail ( "<EMAIL>", "Excel服务器账户重置密码反馈-" . input ( "param.UserLogin" ), "提交信息如下：<br>" . dump ( $logData ) );
				if (is_bool ( $sendEmail )) {
					$re = [ 
							'type' => 'alert',
							'title' => '已成功反馈',
							'msg' => '有进展会通过邮件回复你。感谢配合。' 
					];
				} else {
					$re = [ 
							'type' => 'alert-error',
							'title' => '反馈操作异常，请通过其他方式反馈',
							'msg' => '通知邮件自动发送未成功：' . $sendEmail 
					];
				}
			} else {
				// 已有记录（点击过一键反馈按钮）
				$time = $log ["time"];
				// $time = json_decode ( $log ['v'], true ) ['time'];
				$re = [ 
						'type' => 'alert-warning',
						'title' => '不要重复反馈',
						'msg' => '之前反馈过下面账户的找回密码的情况<br><b>' . input ( "param.UserLogin" ) . "</b><br>反馈时间：" . $time . '<br>请勿重复操作。' 
				];
			}
			return $re;
		} else { // 默认get请求，测试用
			$logData = [ 
					'k' => "找回密码一键反馈-" . input ( "param.UserLogin" ),
					'v' => input ( "param.str" ) 
			];
			$log = db ( "log", 'logDatabase', false )->where ( "k", $logData ['k'] )->find ();
			
			$logArray = explode ( "\n", $log ['v'] );
			return dump ( $logArray );
			// return $this->error ( "不能如此访问" );
		}
	}
	/**
	 * 系统设置，基于IP限制访问
	 *
	 * @return mixed|string|void
	 */
	public function setting() {
		return $this->error ( "您无权访问哦。", null, null, 10 );
	}
	/**
	 * 记录log
	 *
	 * @param string $k        	
	 * @param array $v        	
	 */
	private function writeLog($k = "", $v = []) {
		$logData = [ 
				"k" => $k,
				"v" => json_encode ( $v, 256 ),
				'module' => request ()->module (),
				'ip' => request ()->ip ( 1 ) 
		];
		db ( "log", 'logDatabase', false )->insert ( $logData );
	}
	/**
	 * 强制重置密码
	 *
	 * @param string $UserLogin        	
	 */
	private function fourceReset($UserLogin = "") {
		if ($UserLogin) {
			// 设置密码为姓名首拼大小写加123456
			
			// 清空历史密码
			
			return $this->success ( "密码强制重置完毕" );
		}
		return $this->error ( "UserLogin为空" );
	}
	/**
	 * $md5 为 true 则不转为MD5（默认为false）
	 *
	 * @param string $name        	
	 * @param string $pwd        	
	 * @param string $md5        	
	 * @return void|string
	 */
	protected function SignIn($name = '', $pwd = '', $md5 = false) {
		if (! $md5)
			$pwd = strtoupper ( md5 ( $pwd ) );
		$sql = "select * from [ESApp1].[dbo].[ES_User] where UserLogin = '" . $name . "' and UserPwd = '" . $pwd . "'";
		$res = $this->query ( $sql );
		return dump ( $res );
	}
}<file_sep>/docs/_posts/2017-12-21-handsontable-usage-notes.md
---
layout:         post
title:          handsontable 用法笔记
date:           2017-12-21 20:56:07--0800
summary:        开发中用到了handsontable，官网的demos、usage和api查阅起来不是很方便，在学习过程中自己整理了一下。官方版本目前已更新到5.0。
categories:     notes
update_date:    2018-07-27 18:15:44
---

{:TOC}

##  简介

handsontable-JavaScript Spreadsheet Component For Web Apps，这是官网的title。它是一个component。作为JavaScript组件，handsontable的特点是大数据高性能，样式与Excel非常相似。可以在web中呈现和Excel里一样的表格数据。并可在前端完成一系列复杂的数据分析及操作功能。

版本发布：

- Handsontable Community Edition（Open Source）
- Handsontable Pro（commercial license）

两个版本均托管在[github](https://github.com/handsontable/)。pro版在使用时会在html中以及console中添加显示如下内容：（v1.15.0）

> Evaluation version of Handsontable Pro. Not licensed for use in a production environment

如使用pro版建议购买license尊重作者的劳动成果。

{% if  site.url=="https://{{ site.github_username }}.github.io" %}
<link rel="STYLESHEET" type="text/css" href="https://docs.handsontable.com/bower_components/handsontable-pro/dist/handsontable.min.css">
<script src="https://docs.handsontable.com/bower_components/handsontable-pro/dist/handsontable.min.js"></script>
{% else %}
<link rel="STYLESHEET" type="text/css" href="/static/handsontable-pro/handsontable.full.min.css">
<script src="/static/handsontable-pro/handsontable.full.min.js"></script>
{% endif %}

## 干货写前面

### 方法调用

先构造一个handsontable，然后直接使用，或者使用jQuery的封装形式。

```javascript
// all following examples assume that you constructed Handsontable like this
var ht = new Handsontable(document.getElementById('example1'), options);

// now, to use setDataAtCell method, you can either:
ht.setDataAtCell(0, 0, 'new value');

$('#example1').handsontable('setDataAtCell', 0, 0, 'new value');
```

### 构造函数的options参数

``` javascript
data:               # 数据源（二维数组、对象）
startRows:          # 初始行数
startCols:          # 初始列数
rowHeaders: true    # 行标 []
colHeaders: true    # 列名 可以设置为自定义数组 []
colWidths:          # 列宽度 integer:均等 or []
autoColumnSize:true # 列宽自适应
filters:            # 过滤
dropdownMenu:true   # 下拉菜单
minSpareRows:       # 最小多余的行数
minSpareCols:       # 最小多余的列数
afterChange: function () {}, # 修改显示后的回调函数
manualColumnMove：  # 列移动 
manualRowMove: true # 行移动 
manualColMove: true # 列移动 true
// https://docs.handsontable.com/demo-moving.html
stretchH:           # 拉伸高度 默认:"none" 可选 "last" "all" 用于父级不可滚动时
// https://docs.handsontable.com/demo-stretching.html
copyPaste:          # 复制粘贴选项
search:             # 启动搜索插件
contextMenu: true   # 右键菜单 可以自定义数组 []
selectionMode:      # 选择模式 可选 range(默认) single multiple 
```

# 基本用法

## 快速开始

1. 在网页中引入`js`和`css`文件。推荐引入`handsontable.full.min.js`和`handsontable.full.min.css`。
2. 在html中添加容器`<div id="example"></div>`。
3. 用`JavaScript`初始化

```javascript
var data = [
  ["", "Ford", "Tesla", "Toyota", "Honda"],
  ["2017", 10, 11, 12, 13],
  ["2018", 20, 11, 14, 13],
  ["2019", 30, 15, 12, 13]
];

var container = document.getElementById('example');
var hot = new Handsontable(container, {
  data: data,
  rowHeaders: true,
  colHeaders: true,
  filters: true,
  dropdownMenu: true
});
```

浏览器打开即可查看效果：

<div id="example"></div>
<style>
table.htCore{font-size:12px;font-family: Verdana, Helvetica, Arial, FreeSans, sans-serif;}
table.htCore input[type="text"]{height:15px;font-size:12px;margin-bottom:0;}
table.htCore a{background-image: none;}
table.htCore label{font-size:15px;}
// 调整css防blog样式污染。
</style>
<script>
var data = [
  ["", "Ford", "Tesla", "Toyota", "Honda"],
  ["2017", 10, 11, 12, 13],
  ["2018", 20, 11, 14, 13],
  ["2019", 30, 15, 12, 13]
];

var container = document.getElementById('example');
var hot = new Handsontable(container, {
  data: data,
  rowHeaders: true,
  colHeaders: true,
  filters: true,
  dropdownMenu: true
});
</script>

## 数据绑定

### 理解作为引用的绑定

Handsontable通过引用与你的数据源(数组或对象)绑定。因此，特点有二：

- 在网格中输入的所有数据都将改变原始数据源。
- 从Handsontable外部更改数据源。除非在屏幕上使用呈现方法`render()`刷新网格，否则将不会在屏幕上显示更改。

```javascript
 var
    data1 = [
      ['', 'Tesla', 'Nissan', 'Toyota', 'Honda', 'Mazda', 'Ford'],
      ['2017', 10, 11, 12, 13, 15, 16],
      ['2018', 10, 11, 12, 13, 15, 16],
      ['2019', 10, 11, 12, 13, 15, 16],
      ['2020', 10, 11, 12, 13, 15, 16],
      ['2021', 10, 11, 12, 13, 15, 16]
    ],
    container1 = document.getElementById('example1'),
    settings1 = {
      data: data1
    },
    hot1;

  hot1 = new Handsontable(container1, settings1);
  data1[0][1] = 'Ford'; // change "Kia" to "Ford" programmatically
  hot1.render();

```

### 更改数据源而不更改显示

可以在引用数据时，引用数据源的一个单独的副本。这样数据源变化就不会影响数据显示了。

```javascript
...

hot2 = new Handsontable(container2, {
    data: JSON.parse(JSON.stringify(data2))
  });
```

### 更改显示而不更改数据源

保存之前先克隆，`tmpData`即是显示修改之前的数据副本。

```javascript
...

hot3 = new Handsontable(container3, {
    data: data3,
    afterChange: function () {
      var tmpData = JSON.parse(JSON.stringify(data3));
      // now tmpData has a copy of data3 that can be manipulated
      // without breaking the Handsontable data source object
    }
  });
```

## 数据来源

Handsontable是如何使用各种数据源的？

### 数组类型数据源

```javascript
   var
    data = [
      ['', 'Tesla', 'Nissan', 'Toyota', 'Honda', 'Mazda', 'Ford'],
      ['2017', 10, 11, 12, 13, 15, 16],
      ['2018', 10, 11, 12, 13, 15, 16],
      ['2019', 10, 11, 12, 13, 15, 16],
      ['2020', 10, 11, 12, 13, 15, 16],
      ['2021', 10, 11, 12, 13, 15, 16]
    ],
    container1 = document.getElementById('example1'),
    hot1;

  hot1 = new Handsontable(container1, {
    data: data,
    startRows: 5,
    startCols: 5,
    colHeaders: true,
    minSpareRows: 1
  });
```

### 含隐藏列的数组类型数据源

```javascript
  var
    hiddenData = [
      ['', 'Tesla', 'Nissan', 'Toyota', 'Honda', 'Mazda', 'Ford'],
      ['2017', 10, 11, 12, 13, 15, 16],
      ['2018', 10, 11, 12, 13, 15, 16],
      ['2019', 10, 11, 12, 13, 15, 16],
      ['2020', 10, 11, 12, 13, 15, 16],
      ['2021', 10, 11, 12, 13, 15, 16]
    ],
    container = document.getElementById('example2'),
    hot2;

  hot2 = new Handsontable(container, {
    data: hiddenData,
    colHeaders: true,
    minSpareRows: 1,
    columns: [
      {data: 0},
      {data: 2},
      {data: 3},
      {data: 4},
      {data: 5},
      {data: 6}
    ]
  });
```

### 对象数据源

```javascript
  var
    objectData = [
      {id: 1, name: '<NAME>', address: ''},
      {id: 2, name: '<NAME>', address: ''},
      {id: 3, name: '<NAME>', address: ''},
      {id: 4, name: '<NAME>', address: ''},
      {id: 5, name: '<NAME>', address: ''},
    ],
    container3 = document.getElementById('example3'),
    hot3;

  hot3 = new Handsontable(container3, {
    data: objectData,
    colHeaders: true,
    minSpareRows: 1
  });

```

### 用列做函数的对象数据源

```javascript
  var nestedObjects = [
        {id: 1, name: {first: "Ted", last: "Right"}, address: ""},
        {id: 2, address: ""}, // HOT will create missing properties on demand
        {id: 3, name: {first: "Joan", last: "Well"}, address: ""}
      ],
      container4 = document.getElementById('example4'),
      hot4;

  hot4 = new Handsontable(container4, {
    data: nestedObjects,
    colHeaders: true,
    columns: function(column) {
      var columnMeta = {};
      if (column === 0) {
        columnMeta.data = 'id';
      } else if (column === 1) {
        columnMeta.data = 'name.first';
      } else if (column === 2) {
        columnMeta.data = 'name.last';
      } else if (column === 3) {
        columnMeta.data = 'address';
      } else {
        columnMeta = null;
      }
      return columnMeta;
    },
    minSpareRows: 1
  });
```

其实上面的闭包也可以写成这样：

```javascript
  columns: [
    {data: 'id'},
    {data: 'name.first'},
    {data: 'name.last'},
    {data: 'address'}
  ],
```

### 自定义数据结构及列名的对象数据源

```javascript
dataSchema: {id: null, name: {first: null, last: null}, address: null},
colHeaders: ['ID', 'First Name', 'Last Name', 'Address'],
```
> 官网有个高级用法，感兴趣可自己阅读理解一下：
>
> <https://docs.handsontable.com/tutorial-data-sources.html#page-property-schema>



## 数据的加载与保存

### 使用onChange回调保存数据

定义在初始化Handsontable时`option`选项参数：`afterChange`

```javascript
 afterChange: function (change, source) {
      if (source === 'loadData') {
        return; //don't save this change
      }
      if (!autosave.checked) {
        return;
      }
      clearTimeout(autosaveNotification);
      ajax('scripts/json/save.json', 'GET', JSON.stringify({data: change}), function (data) {
        exampleConsole.innerText  = 'Autosaved (' + change.length + ' ' + 'cell' + (change.length > 1 ? 's' : '') + ')';
        autosaveNotification = setTimeout(function() {
          exampleConsole.innerText ='Changes will be autosaved';
        }, 1000);
      });
    }
  });

  Handsontable.dom.addEvent(load, 'click', function() {
    ajax('scripts/json/load.json', 'GET', '', function(res) {
      var data = JSON.parse(res.response);

      hot.loadData(data.data);
      exampleConsole.innerText = 'Data loaded';
    });
  });

  Handsontable.dom.addEvent(save, 'click', function() {
    // save all cell's data
    ajax('scripts/json/save.json', 'GET', JSON.stringify({data: hot.getData()}), function (res) {
      var response = JSON.parse(res.response);

      if (response.result === 'ok') {
        exampleConsole.innerText = 'Data saved';
      }
      else {
        exampleConsole.innerText = 'Save error';
      }
    });
  });

  Handsontable.dom.addEvent(autosave, 'click', function() {
    if (autosave.checked) {
      exampleConsole.innerText = 'Changes will be autosaved';
    }
    else {
      exampleConsole.innerText ='Changes will not be autosaved';
    }
  });
```

### 本地保存

启用数据存储机制，可在初始化时设置`persistentState`参数为`true`，或者使用`updateSettings `方法。

```javascript
persistentStateSave (key: String, value: Mixed)
# 在浏览器本地存储保存给定键的值
persistentStateLoad (key: String, valuePlaceholder: Object)
# 根据已知键读取值，值被保存valuePlaceholder.value
persistentStateReset (key: String)
# 清除保存的值，无指定key则清除所有
```

使用这三个钩子的api，是为了防止同页面有2个以上实例，数据只能保存一份的尴尬。

## 设置选项

单元格选项，在构造时定义配置

```javascript
var hot = new Handsontable(document.getElementById('example'), {
  cell: [
    {row: 0, col: 0, readOnly: true}
  ]
});
```

或者定义函数属性

```javascript
var hot = new Handsontable(document.getElementById('example'), {
  cells: function (row, col, prop) {
    var cellProperties = {}

    if (row === 0 && col === 0) {
      cellProperties.readOnly = true;
    }

    return cellProperties;
  }
})
```

级联的配置模型，在构造函数使用第一级提供的配置选项或者`updateSettings`方法，使用二级对象提供的配置选项，具体阅读官方版：<https://docs.handsontable.com/tutorial-setting-options.html>，关键部分已摘抄至下面。

### How does the Cascading Configuration work?

Since Handsontable 0.9 we use Cascading Configuration, which is a fast way to provide configuration options      for the whole table, along with its columns and particular cells.

Consider the following example:

```javascript
var hot = new Handsontable(document.getElementById('example'), {
  readOnly: true,
  columns: [
    {readOnly: false},
    {},
    {}
  ],
  cells: function (row, col, prop) {
    var cellProperties = {}

    if (row === 0 && col === 0) {
      cellProperties.readOnly = true;
    }

    return cellProperties;
  }
});
```

The above notation will result in all TDs being read only, except for first column TDs      which will be editable, except for the TD in top left corner which will still be read only.

### The cascading configuration model

The Cascading Configuration model is based on prototypal inheritance. It is much faster and memory      efficient compared to the previous model that used jQuery extend. See it yourself:      <http://jsperf.com/extending-settings>

- **Constructor**

  Configuration options that are provided using first-level

  ```javascript
  new Handsontable(document.getElementById('example'), {
    option: 'value'
  });
  ```

  and `updateSettings` method.

- **Columns**

  Configuration options that are provided using second-level object

  ```javascript
  new Handsontable(document.getElementById('example'), {
    columns: {
      option: 'value'
    }
  });
  ```

- **Cells**

  Configuration options that are provided using second-level function

  ```javascript
  new Handsontable(document.getElementById('example'), {
    cells: function(row, col, prop) {

    }
  });
  ```

## Using callbacks

Please visit：<https://docs.handsontable.com/tutorial-using-callbacks.html>

## Styling

> <https://docs.handsontable.com/tutorial-styling.html>

Commonly used styles:

​      There is very little you can't do with Handsontable. As it doesn't impose any specific theme,      you can play with CSS however you like. Keep in mind that Handsontable needs to calculate the      width and height of elements inside it to control the scrollbar, so the complex styling rules      may affect the performance.    

​      Some of the recipes listed below need an additional parent class/id or other modifications      to override the default values. Also, the styles might slightly vary depending on your configuration.      The below examples were tested with a 10x10 grid with both row and column headers turned on.    

### Table

​        **Background**      

```css
.ht_master tr td {
  background-color: #F00;
}
```

### Headers

​        **Background**      

```css
/* All headers */
.handsontable th {
  background-color: #F00;
}

/* Row headers */
.ht_clone_left th {
  background-color: #F00;
}

/* Column headers */
.ht_clone_top th {
  background-color: #F00;
}
```

​        **Borders**      

```css
/* Row headers */
/* Bottom */
.ht_clone_top_left_corner th {
  border-bottom: 1px solid #F00;
}

/* Left and right */
.ht_clone_left th {
  border-right: 1px solid #F00;
  border-left: 1px solid #F00;
}

/* Column headers */
/* Top, bottom and right */
.ht_clone_top th {
  border-top: 1px solid #F00;
  border-right: 1px solid #F00;
  border-bottom: 1px solid #F00;
}

/* Left */
.ht_clone_top_left_corner th {
  border-right: 1px solid #F00;
}
```

### Corner

​        **Background**      

```css
.ht_clone_top_left_corner th {
  background-color: #F00;
}
```

​        **Borders**      

```css
.ht_clone_top_left_corner th {
  border: 1px solid #F00;
}
```

### Rows

​        **Background**      

```css
/* Every odd row */
.ht_master tr:nth-of-type(odd) > td {
  background-color: #f00;
}

/* Every even row */
.ht_master tr:nth-of-type(even) > td {
  background-color: #f00;
}

/* Selected row  */
/* Add a custom class name in the configuration: currentRowClassName: "foo"; */
.ht_master tr.foo > td {
  background-color: #f00;
}

/* Specific row (2) */
.ht_master tr:nth-child(2) > td {
  background-color: #f00;
}
```

​        **Borders**      

```css
/* Bottom */
.ht_master tr > td {
  border-bottom: 1px solid #F00;
}

/* Right */
.ht_master tr > td {
  border-right: 1px solid #F00;
}
```

### Columns

​        **Background**      

```css
/* Every odd column */
.ht_master tr > td:nth-of-type(odd) {
  background-color: #f00;
}

/* Every even column */
.ht_master tr > td:nth-of-type(even) {
  background-color: #f00;
}

/* Selected column  */
/* Add a custom class name in the configuration: currentColClassName: "foo"; */
.ht_master tr > td.foo {
  background-color: #f00;
}

/* Specific column (B) */
.ht_master tr > td:nth-child(3) {
  background-color: #f00;
}
```

### Cell

​        **Background**      

```css
/* Selected cell */
.ht_master tr > td.current {
  background-color: #F00;
}

/* Specific cell (B2) */
.ht_master tr:nth-child(2) > td:nth-child(3) {
  background-color: #F00;
}

/* Edit mode */
.handsontableInput {
  background-color: #F00!important;
}
```

### Selection

​        **Background**      

```css
.handsontable td.area {
  background-color: #F00;
}
```

### Notice

​        Be careful when using Handsontable with popular CSS frameworks.        They not only modify the style of all DOM elements, including textareas and inputs,        but also add some transition properties which may negatively affect the performance.        Make sure you add styles carefully and selectively or use the official Bootstrap integration.      

​        The selection border color has been hard-coded. It can be changed using JavaScript, or alternatively        you can postscript your CSS values with an (ugly) "!important" rule.      

​        **JavaScript**      

```css
var borders = document.querySelectorAll('.handsontable .wtBorder');
for (var i = 0; i < borders.length; i++) {
  borders[i].style.backgroundColor = 'red';
}

```

​        **CSS**      

```
.wtBorder {
  background-color: #F00!important;
}
```

# 开发者指南

## 搜索 

初始化时在`option`里可以配置`search: true`开启。

v4.0版本开始需要使用插件形式。原有项目升级增加下面第一句即可。

```javascript
hot.search = hot.getPlugin('search');
var queryResult = hot.search.query(value);
```

queryResult 格式：

```javascript
{
  {row: 0, col:0, data: "xxxxx"}，
  {row: x, col:y, data: "xxxxx"},
  length: n
}
```

##  单元格数据类型

默认定义了9种类型：

- `autocomplete` for `Handsontable.cellTypes.autocomplete`
- `checkbox` for `Handsontable.cellTypes.checkbox`
- `date` for `Handsontable.cellTypes.date`
- `dropdown` for `Handsontable.cellTypes.dropdown`
- `handsontable` for `Handsontable.cellTypes.handsontable`
- `numeric` for `Handsontable.cellTypes.numeric`
- `password` for `Handsontable.cellTypes.password`
- `text` for `Handsontable.cellTypes.text`
- `time` for `Handsontable.cellTypes.time`

更多直接查看官方：<https://docs.handsontable.com/tutorial-cell-types.html>

`columns`参数详解

```javascript
hot = new Handsontable(, {
  columns: [
    xxx： yyy
  ]
});
/////////////////////
data // 在获取hot数据时，作为数据的下角标
type // 数据类型
// 其他使用详见下面例子
```

初始化的`options`中配置举例

[autocomplete](https://docs.handsontable.com/0.35.0/demo-autocomplete.html)

```javascript
hot = new Handsontable(container, {
  columns: [
    {
      type: 'numeric',
      numericFormat: {
      pattern: '$0,0.00',
      culture: 'en-US' // this is the default culture, set up for USD  # zh-CN
      },
      allowEmpty: false
    },
    {
      type: 'date',
      dateFormat: 'MM/DD/YYYY',
      correctFormat: true,
      defaultDate: '01/01/1900',
    },
```

> 时间格式列的数据可能不合法，记得在初始化后调用`hot.validateCells()`方法。

```javascript
    {
     type: 'time',
      timeFormat: 'h:mm:ss a',
      correctFormat: true
    },
    {
      type: 'checkbox'
    }，
    {
      editer: 'select',
      selectOptions:['互联网','IMS'],
    },
    {
      type: 'dropdown',
      source: ['yellow', 'red', 'orange', 'green']
    }，
```

> Internally, cell `{type: "dropdown"}` is equivalent to cell` {type: "autocomplete", strict: true, filter: false}`. Therefore you can think of `dropdown` as a searchable `<select>`.

``` javascript
    {
      type: 'autocomplete',
      source: ['yellow', 'red', 'orange and another color', 'green', 'blue', 'gray', 'black', 'white', 'purple', 'lime', 'olive', 'cyan'],
      visibleRows: 4,           // 下拉菜单可见列数
      strict: false,            // 是否严格模式（是否验证）
      trimDropdown: false,      // 宽度自适应下拉内容
      allowInvalid: true        // 严格模式开启时，是否允许存在非法值
    },
    {
      type: 'handsontable',
      handsontable: {
        colHeaders: ['Marque', 'Country', 'Parent company'],
        autoColumnSize: true,
        data: manufacturerData,
        getValue: function() {
          var selection = this.getSelected();
          // Get always manufacture name of clicked row
          return this.getSourceDataAtRow(selection[0]).name;
       },
      }
    },
  ]
});
hot.validateCells();
```

## 右键菜单 

初始化时在`option`里可以配置`contextMenu:true`开启，或者配置为如下选项：

如：`contextMenu：[row_above,row_below,undo,redo]`

- row_above
- row_below
- hsep1
- col_left
- col_right
- hsep2
- remove_row
- remove_col
- hsep3
- undo
- redo
- make_read_only
- alignment
- borders (with Custom Borders turned on)
- commentsAddEdit, commentsRemove (with Comments turned on)

[自定义菜单](https://docs.handsontable.com/demo-context-menu.html#page-custom)：

``` javascript
var
    example3 = document.getElementById('example3'),
    settings3,
    hot3;

  settings3 = {
    data: getData(),
    rowHeaders: true,
    colHeaders: true
  };
  hot3 = new Handsontable(example3, settings3);

  hot3.updateSettings({
    contextMenu: {
      callback: function (key, options) {
        if (key === 'about') {
          setTimeout(function () {
            // timeout is used to make sure the menu collapsed before alert is shown
            alert("This is a context menu with default and custom options mixed");
          }, 100);
        }
      },
      items: {
        "row_above": {
          disabled: function () {
            // if first row, disable this option
            return hot3.getSelected()[0] === 0;
          }
        },
        "row_below": {},
        "hsep1": "---------",
        "remove_row": {
          name: 'Remove this row, ok?',
          disabled: function () {
            // if first row, disable this option
            return hot3.getSelected()[0] === 0
          }
        },
        "hsep2": "---------",
        "about": {name: 'About this menu'}
      }
    }
  })
```

# API参考

```
hot.loadData()
hot.getData()             // 数组格式
got.getSourceData()       // JSON格式（columns里配置data）
hot.search.query()        // 全局搜索并高亮，返回 [{row,col,data},...]
```

调用方法

```javascript
// all following examples assume that you constructed Handsontable like this
var ht = new Handsontable(document.getElementById('example1'), options);

// now, to use setDataAtCell method, you can either:
ht.setDataAtCell(0, 0, 'new value');

# Alternatively, you can call the method using jQuery wrapper (obsolete, requires initialization using our jQuery guide

$('#example1').handsontable('setDataAtCell', 0, 0, 'new value');
```

## 核心

### 核心方法

#### updateSettings

在初始化之后，更新handsontable的设置

```javascript
var hot = new Handsontable(example, settings);
hot.updateSettings(Settings);
// Settings 是 json 结构 {} 如：
hot.updateSettings({
  contextMenu: {
    callback: function (key, options){
      xxxxxx
    },
    items:{
      xxx:{},
      yyy:{}
    }
  }  
});
```

### 钩子事件

#### afterChange

Parameters:

| Name      | Type   | Description                              |
| --------- | ------ | ---------------------------------------- |
| `changes` | Array  | 2D array containing information about each of the edited cells `[[row, prop, oldVal, newVal], ...]`. |
| `source`  | String | optionalString that identifies source of hook call([list of all available sources](http://docs.handsontable.com/tutorial-using-callbacks.html#page-source-definition)). |

#### afterValidate

如果定义了validator函数，执行之后会触发该事件。并执行响应函数，函数的第一个参数是验证结果，用来确定是否验证通过。

Parameters:

| Name      | Type            | Description                                                  |
| --------- | --------------- | ------------------------------------------------------------ |
| `isValid` | Boolean         | `true` if valid, `false` if not.                             |
| `value`   | *               | The value in question.                                       |
| `row`     | Number          | Row index.                                                   |
| `prop`    | String \|Number | Property name / column index.                                |
| `source`  | String          | optionalString that identifies source of hook call([list of all available sources](http://docs.handsontable.com/tutorial-using-callbacks.html#page-source-definition)). |

#### afterSelectionEndByProp

表格单元数据被选择后触发。

Parameters:

| Name                  | Type   | Description                                                  |
| --------------------- | ------ | ------------------------------------------------------------ |
| `r`                   | Number | Selection start visual row index.                            |
| `p`                   | String | Selection start data source object property index.           |
| `r2`                  | Number | Selection end visual row index.                              |
| `p2`                  | String | Selection end data source object property index.             |
| `selectionLayerLevel` | Number | The number which indicates what selection layer is currently modified. |

#### afterOnCellMouseOver

鼠标在单元格上移动时触发。

> 另有`afterOnCellMouseDown`：鼠标按下时触发(不区分单击 右击)
>
> `afterOnCellMouseOut`：鼠标移出单元格时触发

Parameters:

| Name     | Type    | Description                              |
| -------- | ------- | ---------------------------------------- |
| `event`  | Object  | `mouseover` event object.                |
| `coords` | Object  | Hovered cell's visual coordinate object. |
| `TD`     | Element | Cell's TD (or TH) element.               |

#### afterScrollVertically

垂直滚动后触发。无参数。

#### afterRender

Callback fired after the Handsontable table is rendered.

（滚动后均会触发）

Parameters:

| Name       | Type    | Description                                                  |
| ---------- | ------- | ------------------------------------------------------------ |
| `isForced` | Boolean | Is `true` if rendering was triggered by a change of settings or data; or `false` ifrendering was triggered by scrolling or moving selection. |

### 选项可选成员

这里介绍构造时，`var hot = new Handsontable(container, settings);`settings的选项的成员。

#### columns

官网例子：

<https://docs.handsontable.com/Options.html#columns>

数据类型例子：

>  具体数据类型可参见 [Developer guide/Cell types](https://docs.handsontable.com/tutorial-cell-types.html)

```javascript
columns : [ {
	editor : 'select',
	selectOptions : [array],
}, {
	strict : true,
	type : 'autocomplete',
	source : [array],
},{
    data: 'name.first'
}
```

数据加载例子（json）：

<https://docs.handsontable.com/tutorial-data-sources.html#page-nested>

#### validator

使用时可以是函数、正则表达式、字符串。提供的可选字符串有

- autocomplete,
- date,
- numeric,
- time.

可以自己[注册](http://docs.handsontable.com/demo-data-validation.html)一个验证器。

```javascript
Handsontable.validators.registerValidator('my.credit-card', creditCardValidator);
```

注册时最好使用别名：

```javascript
(function(Handsontable){
  function customValidator(query, callback) {
    // ...your custom logic of the validator

    callback(/* Pass `true` or `false` based on your logic */);
  }

  // Register an alias
  Handsontable.validators.registerValidator('my.custom', customValidator);

})(Handsontable);
```

这样就可以像下面一样使用 `customValidator` 了：

```javascript
var hot = new Handsontable(document.getElementById('container'), {
  data: someData,
  columns: [
    {
      validator: 'my.custom'
    }
  ]
}); 
```

下面是例子

```javascript
// as a function
columns: [ {
	validator: function(value, callback) { // validation rules }
}，
// as a regexp
{
	validator: /^[0-9]$/ // regular expression
}，
// as a string
{
	validator: 'numeric'
}
]
```



## 插件

### 获取自动调整的宽度

```javascript
// ...
hot.updateSettings({autoColumnSize:true}); 
// ...
// Access to plugin instance:
var colSizePlugin = hot.getPlugin('autoColumnSize');
var result="";
for(var ii=0;ii<30;ii++){
    result+=colSizePlugin.getColumnWidth(ii)+",";
}
console.info(result);
```

### 导出到文件

```javascript
var exportPlugin = hot.getPlugin('exportFile');

// Export as a string:
exportPlugin.exportAsString('csv');

// Export as a Blob object:
exportPlugin.exportAsBlob('csv');

// Export to downloadable file (MyFile.csv):
exportPlugin.downloadFile('csv', {filename: 'MyFile'});
```



<file_sep>/application/kxeams/model/Item.php
<?php

namespace app\kxeams\model;

use think\Model;
use traits\model\SoftDelete;

class Item extends Model {
	use SoftDelete;
	protected $deleteTime = 'idelete_time';
	protected $autoWriteTimestamp = 'datetime';
	protected $createTime = 'icreate_time';
	protected $updateTime = false;
	protected $type = [
			'id' => 'integer',
			'icreate_time' => 'datetime',
			'idelete_time' => 'datetime'
	];
}<file_sep>/application/zx_apply/config.php
<?php
return [
	'version' => "V0.7.19-zx",
	// 'aStation' => '{"柴河局09":"CHJ-09","西丰新局":"XF-10","昌图新局":"CT-11","昌图老局":"CT-12","西丰老局":"XF-13","开原老局":"KY-14","铁岭县":"TLX-15","清河局":"QH-16","新台子":"XTZ-17","调兵山新局":"DBS-18","调兵山老局":"DBS-19","开原新局":"KY-20","柴河局-华为":"CHJ-21","柴河局-中兴":"CHJ-22","银州区":"YZL-23"}',
	'systemURLString' => "<br> 内网： <a href='http://10.65.178.202/zx_apply/index/index.html#Manage/todo'>http://10.65.178.202/zx_apply/index/index.html#Manage/todo</a><br>外网： <a href='http://172.16.58.3:800/zx_apply/index/index.html#Manage/todo'>http://172.16.58.3:800/zx_apply/index/index.html#Manage/todo</a>",
	'aStation' => [
		"请选择" => null,
		"柴河局09" => "CHJ-09",
		"西丰新局" => "XF-10",
		"昌图新局" => "CT-11",
		"昌图老局" => "CT-12",
		"西丰老局" => "XF-13",
		"开原老局" => "KY-14",
		"铁岭县" => "TLX-15",
		"清河" => "QH-16",
		"新台子" => "XTZ-17",
		"调兵山新局" => "DBS-18",
		"调兵山老局" => "DBS-19",
		"开原新局" => "KY-20",
		"柴河局-华为" => "CHJ-21",
		"柴河局-中兴" => "CHJ-22",
		"银州区" => "YZL-23",
		"其他" => null
	],
	'device9312' => '<KEY>',
	// 额外信息，用于IP备案
	'extraInfo' => [
		"单位性质" => "unitProperty",
		"单位分类" => "unitCategory",
		"行业分类" => "industryCategory",
		"单位证件类型" => "credential",
		"单位证件号" => "credentialnum",
		"所在省" => "province",
		"所在市" => "city",
		"所在县区" => "county",
		"应用服务类型" => "appServType",
		"单位属性" => "unitAttribute",
		"网络安全责任人" => "securityPerson",
		"责任人身份证号" => "securityPersonID",
		"责任人电话" => "securityPhone",
		"责任人邮箱" => "securityEmail",
		"邮政编码" => "zipCode",
	],
	// 登陆后跳转到manage模块的账号
	'manageEmails' => [
		"yuxianda.tl",
		"buyu.tl",
		"liuyutl.tl"
	],
	// 列宽度
	'colWidth' => [
		0 => 98,
		1 => 96,
		2 => 65,
		3 => 50,
		4 => 65,
		5 => 100,
		6 => 240,
		7 => 220,
		8 => 450,
		9 => 50,
		10 => 116,
		11 => 116,
		12 => 107,
		13 => 107,
		14 => 164,
		15 => 93,
		16 => 96,
		17 => 164,
		18 => 200,
		19 => 75,
		20 => 200,
		21 => 60,
		22 => 107,
		23 => 164,
		24 => 98,
		25 => 98,
		26 => 50,
		27 => 80,
		28 => 0,
		29 => 76,
		30 => 76,
		31 => 76,
		32 => 132,
		33 => 107,
		34 => 62,
		35 => 62,
		36 => 76,
		37 => 104,
		38 => 90,
		39 => 90,
		40 => 107,
		41 => 96,
		42 => 164,
		43 => 62,
	],
		/* 生成IDC备案信息使用（与 index/apply.html 里定义的一致）*/
		// 单位性质
	'unitProperty' => [
		1 => '军队',
		2 => '政府机关',
		3 => '事业单位',
		4 => '企业',
		5 => '个人',
		6 => '社会团体',
		7 => '国瑞',
		8 => '个体工商户',
		9 => '民办非企业',
		10 => '基金会',
		11 => '律师事务所',
		12 => '外国文化中心',
		99 => '未知'
	],
		// 单位分类
	'unitCategory' => [
		1 => 'ISP',
		2 => 'IDC',
		3 => 'ISP和IDC',
		4 => '公益性互联网络单位',
		5 => '其他',
		11 => '电信业务经营者',
		12 => '公益性互联网络单位',
		14 => '管局单位',
		15 => '前置单位',
		999 => '未知'
	],
		// 行业分类
	'industryCategory' => [
		1 => '农、林、牧、渔业',
		2 => '采矿业',
		3 => '制造业',
		4 => '电力、燃气及水的生产和供应业',
		5 => '建筑业',
		6 => '交通运输、仓储和邮政业',
		7 => '信息传输、计算机服务和软件业',
		8 => '批发和零售业',
		9 => '住宿和餐饮业',
		10 => '金融业',
		11 => '房地产业',
		12 => '租赁和商务服务业',
		13 => '科学研究、技术服务和地质勘查业',
		14 => '水利、环境和公共设施管理业',
		15 => '居民服务和其他服务业',
		16 => '教育',
		17 => '卫生、社会保障和社会福利业',
		18 => '文化、体育和娱乐业',
		19 => '公共管理与社会组织',
		20 => '国际组织',
		21 => 'example',
		999 => '未知'
	],
		// 使用单位证件类型
	'credential' => [
		1 => '工商营业执照',
		2 => '身份证',
		3 => '组织机构代码证书',
		4 => '事业法人证书',
		5 => '军队代号',
		6 => '社团法人证书',
		7 => '护照',
		8 => '军官证',
		11 => '台胞证',
		13 => '统一社会信用代码证书',
		14 => '港澳居民来往内地通行证',
		17 => '民办非企业单位登记证书',
		19 => '基金会法人登记证书',
		21 => '律师事务所执业许可证',
	],
	'province' => [
		210000 => '辽宁省'
	],
	'city' => [
		211200 => '铁岭市'
	],
	'county' => [
		211201 => '市辖区',
		211202 => '银州区',
		211204 => '清河区',
		211221 => '铁岭县',
		211223 => '西丰县',
		211224 => '昌图县',
		211281 => '调兵山市',
		211282 => '开原市'
	],
	'appServType' => [
		1 => '企事业单位专线接入',
		2 => '个人专线接入',
		3 => '固定宽带接入',
		4 => '移动网接入',
		5 => '网络设备',
		6 => '网站专线接入',
		7 => 'IDC服务类',
		8 => '物联网'
	],
];<file_sep>/CHANGELOG.md
## 更新日志

#### v0.7.17(2018-09-10)

[zx_apply]模块修复 bug

- 更新 docs
- 修复`修改申请`报错 bug
- 修复`todo.html`分配后`vlan`无记录的 bug
- 修复其他若干 bugs

#### v0.7.12(2018-08-27)

[zx_apply]模块修复 bug

- `todo.html`和`index/query.html`编辑旧数据时自动修补新增的额外字段信息
- 修改自动修补新增的额外字段的  bugs

#### v0.7.11(2018-08-23)

[zx_apply]模块更新

- 自动生成 idc.isp 模板数据，并做为邮件附件发送
- 导出 excel 功能可选择使用前端或者后端
- 调整 LECENSE
- fix [#3](https://github.com/thianda/phpweb/issues/3), fix [#4](https://github.com/thianda/phpweb/issues/4)
- 修改若干 bugs
- `index/query.html`右键新增`额外再申请1个IP`功能
- 同 vlan 提醒后确认，可强制录入
- ONU 业务在`todo.html`可显示当前所处状态（在 vlan 字段上方）

#### v0.7.3(2018-08-05)

[zx_apply]模块更新

- 完善前端导出 excel 功能
- 分离部分`Manage.php`的操作到`Generator.php`
- `Infotables`新增 5 个额外字段（extra）
- 调整`apply.html`表单字段位置，新增字段 4 个，隐藏无需编辑的字段 4 个

### v0.7.0(2018-08-03)

[zx_apply]模块更新

- 导出 excel 功能全部迁移到前端

TODO

-  php 文件读写，发邮件带附件
-  同 vlan 强制录入

#### v0.6.7(2018-08-03)

[zx_apply]模块更新

- todo提交后发送邮件给客户经理和申请人
- close [#6](https://github.com/thianda/phpweb/issues/6)

#### v0.6.6(2018-08-01)

[zx_apply]模块更新

- 代码仓库脱敏处理，永久删除敏感信息
- 细节调整

#### v0.6.2(2018-07-27)

[zx_apply]模块更新

- 修复搜索bug [#5](https://github.com/thianda/phpweb/issues/5).

### v0.6.0(2018-07-13)

[zx_apply]模块更新

- 实现ONU业务申请时无需`A端基站`，先预分配ip不分配vlan，待做ONU上线数据时再选择`A端基站`并分配`vlan`。
- apply.html申请、query.html修改申请、todo代办页面同步修改表单动态逻辑
- 修改`Vlantables::check()`逻辑，优化存在`vlan`但不存在`aStation`时的结果

#### v0.5.5(2018-07-13)

[zx_apply]模块更新

- 调整：申请时若为ONU业务则不需要填写`A端基站`，此规则在`修改申请`时亦生效

- 修复`manage/query.html`。在此之前，批量删除时前端显示与实际不符
- 完善vlan删除与修改功能：修改状态`[0]申请`，到待办里直接修改提交即可，若不填vlan即为删除
- 修复其他若干bug

#### v0.5.1(2018-07-12)

[zx_apply]模块代码格式化

- 格式化`zx_apply`模块的代码（php、html），遵循PSR-2
- 未知原因导致部分代码丢失，已补回

### v0.5.0(2018-07-11)

[zx_apply]模块部分重构

- 前端部分重构
- 修复申请填表单的bug
- 需进一步调整

#### v0.4.18(2018-07-11)

[zx_apply]模块更新

- 更新资管导入模板
- 优化`XLSX`组件的加载逻辑
- 计划将`导出excel操作`移至前端，加速导出速度，兼容性更强
- 修复细节bug

#### v0.4.16(2018-07-05)

[zx_apply]模块更新

- 修改`index/query.html`中`afterChange`事件的bug
- 新增ip列表生成：`tool/swscript.html`

#### V0.4.13(2018-06-28)

[zx_apply]模块更新

- `manage/query.html`新增批量已完成功能，批量选取设置状态为[4]:已完成
- 导出资管模板数据时提示批量修改状态为[2]:已录资管等待IP备案

TODO：`更多操作`页面新功能：统计空闲ip

> 需基于专线类型，细节有待研究

#### V0.4.12(2018-06-26)

[zx_apply]模块修复若干bug

- 完善细节功能，修复大量bug
- 支持按照`专线类型`进行`全局搜索`以及`基本信息查询`

#### v0.4.9(2018-06-12)

- 用户可右键选择`修改申请`，修改个别字段信息后重新提交
- 用户申请提交前自动清除每个字段里的空格并给出提示

#### v0.4.8(2018-06-06)

[zx_apply]模块bug修复

- 累计优化多处UI布局
- `todo`页提示同客户名的台账信息
- `apply`页提交前过滤空格符
- 模板导出时文件名的时间字段放后面

#### v0.4.6(2018-05-29)

[zx_apply]模块更新

- 视图显示优化、安全事件逻辑优化
- 显示和导出全量数据可基于专线类别筛选，申请与待办暂未实现
- 查询、导出操作均记录log

#### V0.4.5(2018-05-24)

[zx_apply]模块bug修复

- 修复ip分配时的小bug
- 调整查询页面排序为`status,create_time desc`
- 更新资管系统导出模板
- 调整各种模板导出时文件名的命名规则

#### v0.4.4(2018-05-18)

[zx_apply]模块大量更新。

- 待办页`vlan`字段不再是必填
- 待办页“自动预分配”结果显示由`alert`改为`window`
- 查询页显示详细信息布局优化
- 查询页取消显示备注列
- 调整待办通知的邮件内容
- 申请人查询页可查询历史数据的基本信息（去除客户联系方式等敏感信息，并限制查询次数）

#### v0.4.3(2018-05-16)

- [zx_apply]模块预分配ip后给申请人自动发送邮件提醒（手动判断是否发送）

#### v0.4.2(2018-05-11)

#### v0.4.1(2018-05-04)

[zx_apply]模块开始公测使用

- 添加`jsSheet`组件，用于zx_apply前端导出全量台账的xlsx文件
- 敏感配置信息独立存储

### v0.4.0(2018-04-02)

[zx_apply]模块大量更新。

- `Infotables`增加 ip掩码和ipB掩码，互联网ip掩码默认32，其他默认30，所有ipB掩码默认29
- 取消`Iptables`数据表，保留其model类及方法
- 定位为辅助平台，提供有限的台账维护功能。主要功能为限制数据录入的规范性。导出为各项工作所需的数据。包含但不限于：
  - 台账格式
  - 资管流程模板格式
  - 做数据脚本
  - ip备案模板格式（2种）
- 管理员查询页面可右键调用数据制作脚本生成、资管系统录入模板、集团IP备案导入模板、工信部IP备案导入模板功能。
- 更新`handsontable`组件到v1.18.1/v0.38.1

### v0.3.2(2018-01-31)

- 更新`dhtmlx`组件到5.1.0
- 修复esserver的bug
- 添加系统log记录功能
- 添加zx_apply模块的登陆控制

#### v0.2.2.0108(2018-01-08)

- 更新`/docs/_posts/2017-12-21-handsontable-usage-notes.md`内容

> 总结的自己脑袋都乱了。官网这个[docs](https://docs.handsontable.com)真的一点也不友好。

- 更新zx_apply核心代码

#### v0.2.2.0107(2018-01-07)

- 更新`/docs/_posts/2017-12-21-handsontable-usage-notes.md`内容
- 添加`handsontable`作为grid组件。与`dhtmlXGrid`相比的优点：
  - 有配置默认值，上手更简单
  - UI更接近Excel，功能也更近似Excel
  - 可全局搜索，性能良好

#### v0.2.2(2017-12-22)

- 更新docs入口为项目名，可在github pages自动发布
- 添加静态资源handsontable
- 更新docs开发文档样式，修复几处bug

### v0.2.0(2017-12-18)

- 添加docs，记录开发文档
- 大量微调整

### v0.1.0(2017-11-16)

开始托管于github，整合多个小项目（checkME60、esserver、kxeams、trouble、zx_apply）为本项目的子模块。

- initial release<file_sep>/application/zx_apply/model/Iptables.php
<?php

namespace app\zx_apply\model;

use think\Model;
use think\Db;

class Iptables extends Model
{

	/**
	 * 自动预分配ip，并确保未使用
	 *
	 * @param string $zxType        	
	 * @return number|string
	 */
	public static function generateIP($zxType = "互联网")
	{
		// $lastIp = Db::name ( "infotables" )->where ( "zxType", $zxType )->where ( "ip", "NOT NULL" )->order ( "create_time desc" )->limit ( 1 )->value ( "ip" );
		$lastIp = Db::table("phpweb_sysinfo")->where("label", "zx_apply-lastIP")->value("value");
		if ($lastIp) {
			// lastIp检测是否已使用，已使用则自增，直至返回未使用的ip，并记录
			do {
				$nextIpStr = self::ip_export($lastIp++);
				$valid = self::check($nextIpStr, "ip", $zxType);
			} while ($valid); // 有返回则表示冲突或不可用
			self::setLastIp($nextIpStr);
			return $nextIpStr;
		} else {
			return "暂无参考，请在系统设置中设置或手动分配";
		}
	}
	/**
	 * 设置最后分配的ip
	 *
	 * @param string $ipStr        	
	 * @return number
	 */
	public static function setLastIp($ipStr = "")
	{
		if (is_numeric($ipStr)) {
			$ip = $ipStr;
		} else {
			$ip = self::ip_parse($ipStr)[2];
		}
		if (!strlen($ip)) {
			return;
		}
		// 已存在则更新，不存在则新增
		if (Db::table("phpweb_sysinfo")->where("label", "zx_apply-lastIP")->find()) {
			$result = Db::table("phpweb_sysinfo")->where("label", "zx_apply-lastIP")->setField("value", $ip);
		} else {
			$data = [
				"label" => "zx_apply-lastIP",
				"value" => $ip
			];
			$result = Db::table("phpweb_sysinfo")->insert($data);
		}
		return $result;
	}
	/**
	 * 是否已分配
	 *
	 * @param unknown $zxType        	
	 * @param string $ip_str        	
	 * @param string $filed
	 *        	ip (defalut) or ipB
	 * @return array|\think\db\false|PDOStatement|string|\think\Model
	 */
	public static function check($ip_str = "", $filed = "ip", $zxType = "互联网")
	{
		if ($ip_str == "") {
			return;
		}
		list($ip, $mask, $ip_start, $ip_end) = self::ip_parse($ip_str);
		if (!self::ifCanUse($zxType, $ip_start)) {
			return "no";
		}
		$infotables = new Infotables();
		$data = $infotables->where([
			"zxType" => $zxType,
			$filed => $ip_start 
			// $filed . "Mask" => $mask
		])->field("id,cName")->find();
		return $data;
	}
	/**
	 * 判断ip是否可用
	 *
	 * @param unknown $zxType        	
	 * @param unknown $long        	
	 * @param unknown $mask        	
	 * @return boolean
	 */
	public static function ifCanUse($zxType = '互联网', $long, $mask = -1)
	{
		if ($long < 0) {
			// 2^31-(-x) 设计负数如何转化并计算。
			$array = explode(",", "-255,0,-1");
		} else {
			$array = explode(",", "0,1,255");
		}
		return !in_array($long % 256, $array);
	}
	/**
	 * ip转换
	 * ip字符串10.10.10.10/32转成ip2long形式的数组:ip/mask/ip_start/ip_end
	 *
	 * @param unknown $ip_str        	
	 * @return boolean[]|number[]
	 */
	public static function ip_parse($ip_str = "")
	{
		if ($ip_str == "") {
			return [
				null,
				null,
				null,
				null
			];
		}
		$mask_len = 32;
		if (strpos($ip_str, "/") > 0) {
			list($ip_str, $mask_len) = explode("/", $ip_str);
		}
		$ip = ip2long($ip_str);
		$mask = 0xFFFFFFFF << (32 - $mask_len) & 0xFFFFFFFF;
		$ip_start = $ip & $mask;
		$ip_end = $ip | (~$mask) & 0xFFFFFFFF;
		return array(
			$ip,
			$mask,
			$ip_start,
			$ip_end
		);
	}
	/**
	 * ip、掩码转换成ip字符串10.10.10.10/32
	 *
	 * @param long $long        	
	 * @param long $subnet_mask        	
	 * @return string
	 */
	public static function ip_export($long, $subnet_mask = "")
	{
		if (!$long) {
			return null;
		}
		if ($subnet_mask == -1 | $subnet_mask == "") {
			return long2ip($long);
		} else {
			$suffix = strlen(preg_replace("/0/", "", decbin($subnet_mask))); // 十进制转二进制 统计32-bits的二进制中1的个数
			return long2ip($long) . "/" . $suffix;
		}
	}
}<file_sep>/application/checkME60/config.php
<?php
return [ 
		'version' => "V1.0426-ck60" 

];<file_sep>/application/zx_apply/controller/Tool.php
<?php

namespace app\zx_apply\controller;

class Tool extends Common
{
	public function _initialize()
	{
		if (input("get.uu") == "y") {
			session("user", [
				"name" => "{Test}"
			]);
		}
		parent::_initialize();
	}
	public function index()
	{
		// 暂未配置登录页面
		return $this->redirect("main");
	}
}<file_sep>/application/common/controller/Common.php
<?php

namespace app\common\controller;

use think\Controller;
use think\Request;
use think\Config;
use think\Db;
use think\Session;
use think\Cache;
use PHPMailer\PHPMailer\PHPMailer;

class Common extends Controller
{

	/**
	 * 判断是否已登录--(初始化函数 _initialize 优先于 $beforeActionList 配置)
	 */
	public function _initialize()
	{
		$request = Request::instance();
		if ($request->controller() == 'common') {
			return $this->error("非法访问！你很6啊。然而我会带你回去。");
		}
		// url : index.php/【MODULE】/【CONTROLLER】/【ACTION】.html
		$permitModule = [
			"esserver"
		];
		$permitController = [ // "Tool"
		];
		$permitActions = [
			"index",
			"main",
			"getvcode"
		];
		// 判断是否从localhost 访问， url 是否允许 未登录访问。否则跳转
		if (substr($request->domain(), -9) == "localhost" || in_array($request->module(), $permitModule) || in_array($request->controller(), $permitController) || in_array($request->action(), $permitActions) || input('session.user/a')) {
			$this->assign("version", config("version"));
		} else {
			// session 保存 to_url
			session("to_url", request()->baseUrl());
			return $this->error('您未登录或登录超时，请先登录！', 'index/index#' . $request->controller() . "/" . $request->action());
		}
	}
	public function index()
	{
		return "当前执行的是：common/index";
	}
	/**
	 * 退出登录
	 */
	public function loginout()
	{
		$this->log("注销登陆", [
			"stauts" => "success",
			"name" => session("user.name")
		]);
		Session::delete("user");
		return $this->success("已注销登录", "index#logout", "", 1);
	}
	/**
	 * php 数组 转成 Grid 组件需要的 json 格式
	 *
	 * @param array $array        	
	 * @param string $header        	
	 * @return string
	 */
	public static function array_to_json($array = [], $header = '')
	{
		$data_arr = [];
		if ($header == '') {
			foreach ($array as $val) {
				$id = null;
				$theData = [];
				foreach ($val as $k => $v) {
					if ($id == null || $k == 'id')
						// 让 id 为 第一个值，或者为id值
					$id = $v;
					$theData[] = $v;
				}
				$data_arr[] = [
					"id" => $theData[0],
					"data" => $theData
				];
			}
		} else {
		}
		return json_encode([
			"rows" => $data_arr
		], JSON_UNESCAPED_UNICODE);
	}
	/**
	 * php 数组根据 header 转成 csv 字符串
	 *
	 * @param array $array        	
	 * @param string $header        	
	 * @return string
	 */
	public static function array_to_csv($array = [], $header = '')
	{
		$csvstr = '';
		if ($header == '') {
			foreach ($array as $val) {
				$i = 1;
				foreach ($val as $v) {
					if ($i < count($val))
						$csvstr .= $v . ',';
					else
						$csvstr .= $v . "\n";
					$i++;
				}
			}
		} else {
			$headers = explode(",", $header);
			foreach ($array as $val) {
				for ($i = 0; $i < count($headers); $i++) {
					if ($i < count($headers))
						$csvstr .= $val[$headers[$i]] . ',';
					else
						$csvstr .= $val[$headers[$i]] . "\n";
				}
			}
		}
		$csvstr = substr($csvstr, 0, strlen($csvstr) - 1);
		return $csvstr;
	}
	function zz()
	{
		return dump($this->csv_to_array([
			"a",
			"b",
			"c"
		], "1,2,3"));
	}

	/**
	 * csv 转 php数组
	 *
	 * @param array $header        	
	 * @param string $csvstr        	
	 * @return array
	 */
	public static function csv_to_array($header = [], $csvstr = '')
	{
		trim($csvstr);
		$data = explode("\n", $csvstr);
		$result = []; // 初始化结果数据
		for ($i = 0; $i < count($data); $i++) { // 将多条原始数据分别分割为数组
			$result[] = array_combine($header, explode(",", trim($data[$i])));
		}
		return $result;
	}
	/**
	 * 根据列名、表名查询非重复数据
	 *
	 * @param string $table        	
	 * @param array $field        	
	 * @param array $where        	
	 * @param string $distinct        	
	 * @param string $order        	
	 * @return string|\think\Collection|\think\db\false|PDOStatement|string
	 */
	public static function get_combo_options($table = '', $field = [], $where = [], $distinct = true, $order = "id")
	{
		$table = input("param.table");
		$field = input("param.field/a");
		$where = input("param.where/a");
		$distinct = input("?param.distinct") ? input("param.distinct/b") : true;
		$order = input("param.order");
		if ($table == '')
			return "传值为空";
		foreach ($field as $k => $f) {
			$field_arr[] = $f . " as " . $k;
		}
		$field_str = implode(",", $field_arr);
		$result = Db::name($table)->distinct($distinct)->where($where)->field($field_str)->order($order)->select();
		return $result; // 自动处理成 json()
			                // return json_encode($result, 256);//Content-Type:text/html
			                // return json ( $result ); // 返回 Content-Type : application/json
	}
	public static function get_combo_options2($column = '', $table = '')
	{
		$table = input("param.table");
		$column = input("param.column");
		if ($table == '' || $column == '')
			return "传值为空,需要_table和_column参数";
		$result = Db::name($table)->distinct(true)->field($column . " as value")->select();
		for ($i = 0; $i < count($result); $i++) {
			$result[$i]['text'] = $result[$i]['value'];
		}
		$result = [
			'options' => $result
		];
		return $result;
	}
	/**
	 * 获取邮件验证码
	 *
	 * @param string $e        	
	 * @param number $ttl        	
	 */
	public function getVcode($e = '', $ttl = '120')
	{
		if (preg_match('/[^A-Za-z0-9.@_]+/', $e)) {
			return $this->error('非法邮箱地址哦');
		} else {
			$e = strtolower($e);
		}
		// 限制系统每2小时最多发送12个验证码。（超2小时code可重复）
		$data = Db::table("phpweb_check")->whereTime('time', '-2 hours')->select();
		if (count($data) > 12) {
			return $this->success("单位时间发送验证码过多。请根据页面下方信息与管理员联系。");
		}
		// 检查$ttl分钟内是否已申请
		// $data = Db::table ( "phpweb_check" )->whereTime ( 'time', '-31 min' )->where ( "loginName", $e )->order ( "time desc" )->find ();
		$codes = [];
		foreach ($data as $v) {
			if ($v['email'] == $e) {
				if (time() - strtotime($v['time']) < $ttl * 60) {
					return $this->success("距离上一次申请间隔小于" . $ttl . "分钟，请勿重复操作。<br><br>如未收到请稍等3分钟并检查是否在垃圾邮件中。");
				}
			}
			$codes[] = $v['code'];
		}
		// 检查是否与生效中的其他用户的相同
		do {
			$vcode = rand(0, 9999);
		} while (in_array($vcode, $codes));
		$address = $e;
		$subject = '[ESWeb]验证码：' . sprintf("%04s", $vcode) . '，可在30分钟内使用。';
		$body = '<p style="color:#088bff;">请确认是您申请了邮箱登录的验证码。若非本人操作，请忽略本邮件。</p><hr /><br /><br /><br /><br />
				<div style="width:500px;padding:30px;background-color:#000;color:#bbb;"><p>Powered by <a style="color:#eee;font-weight:bold;" href="https://github.com/thianda/")">X.Da</a></p>
				<p><a style="color:#eee;font-weight:bold;" href="mailto:<EMAIL>")">Connect me</a>: <EMAIL></p>
				<p>Read <a style="color:#eee;font-weight:bold;" href="https://thianda.github.io/phpweb/notes/zx-apply.html">this</a> to find more</p></div>';
		$sendEmail = $this->sendEmail($address, $subject, $body);
		// $sendEmail = true; // 测试用例
		if (is_bool($sendEmail)) {
			$msg = "验证码已通过邮件发送，请到邮箱内查收主题包含[ESWeb]的邮件。";
			// 新用户，通知管理员
			if (Db::table("phpweb_check")->where("email", $e)->find()) {
			} else {
				$title = "[新用户]" . $e;
				$msg = "来自IP： " . request()->ip() . "，第一次获取了验证码。";
				$this->noticeXianda($title, $msg);
			}
			// 存入数据库
			$insertData = [
				'code' => $vcode,
				'email' => $e,
				'name' => input("param.name"),
				'module' => request()->module()
			];
			Db::table("phpweb_check")->insert($insertData);
			$this->log("获取验证码", [
				"status" => "success",
				"name" => input("param.name"),
				"email" => $e,
				"msg" => $msg
			]);
			return $this->success($msg, null, 2 * $vcode);
		} else {
			$msg = "邮件发送未成功：" . $sendEmail;
			$this->log("获取验证码", [
				"status" => "failed",
				"name" => input("param.name"),
				"email" => $e,
				"msg" => $msg
			]);
			return $this->error($msg);
		}
	}
	/**
	 * 获取参数
	 *
	 * @param string $table        	
	 * @param array $where        	
	 * @return string 参数值
	 */
	public static function getSysInfo($label = '')
	{
		return Db::table("phpweb_sysinfo")->where("label", $label)->value("value");
	}
	public function bugReport()
	{
		if (Request::instance()->isGet()) {
			return $this->display("<h3>??????</h3>");
		} else {
			$data = input("post.");
			// return dump($data);
			return Db::name('bugreport')->insert($data);
			// return $this->success("");
		}
	}
	/**
	 * 通知管理员
	 *
	 * @param unknown $title        	
	 * @param unknown $msg        	
	 */
	protected function noticeManage($title = "", $msg = "", $address = [])
	{
		$number = 20;
		$logs = Db::table("phpweb_log")->field("id,k,v,ip,module,time")->order("time desc")->limit($number)->select();
		$tableStr = '<table class="_xda-resultTable" style="font-size:14px;border-collapse:collapse;border:none;">';
		$tableStr .= '<tr bgcolor="#dddddd" style="font-size:18px;">';
		$tableStr .= '<th>编号</th><th>键</th><th>值</th><th>ip</th><th>模块</th><th>时间</th>';
		$tableStr .= '</tr>';
		foreach ($logs as $key => $value) {
			$tableStr .= '<tr><td style="width:50px;">' . $value["id"] . '</td>';
			$tableStr .= '<td style="width:90px;">' . $value["k"] . '</td>';
			$v = str_replace(",", "\n", $value["v"]);
			$v = str_replace("{", "<pre style='width:500px;overflow:auto;'>", $v);
			$v = str_replace("}", "</pre>", $v);
			$v = str_replace("\":\"", ":\t\t", $v);
			$v = str_replace("\"", "", $v);
			$tableStr .= '<td style="width:500px;">' . $v . '</td>';
			$tableStr .= '<td style="width:130px;">' . long2ip($value['ip']) . '</td>';
			$tableStr .= '<td style="width:65px;">' . $value["module"] . '</td>';
			$tableStr .= '<td style="width:150px;">' . $value["time"] . '</td></tr>';
		}
		$tableStr .= '</table>';
		$tableStr .= '<style>._xda-resultTable th, ._xda-resultTable td{border:solid #000 1px;}</style>';
		// 下面是旧版php的字符串拼接变量的方法。
		$msg = "<p>{$msg}</p><hr><p>以下是最近" . $number . "条系统log日志：</p>{$tableStr}";
		// return $msg;
		$result = $this->sendEmail($address, $title, $msg);
		return $result;
	}
	protected function noticeXianda($title = "", $msg = "")
	{
		$this->noticeManage($title, $msg, "<EMAIL>");
	}
	/**
	 * 记录系统log
	 *
	 * @param unknown $k        	
	 * @param unknown $v        	
	 * @param unknown $time        	
	 */
	protected function log($k = "", $v = [])
	{
		Db::table("phpweb_log")->insert([
			"k" => $k,
			"v" => json_encode($v, JSON_UNESCAPED_UNICODE),
			"module" => Request::instance()->module(),
			"ip" => ip2long(request()->ip())
		]);
	}
	protected function sendEmail($address = [], $subject = '', $body = '', $attachments = null)
	{
		try {
			$mail = new PHPMailer();
			$mail->isSMTP(); // Set mailer to use SMTP
			$mail->CharSet = "utf-8";
			$mail->SetLanguage('zh_cn');
			// $mail->SMTPDebug = 1;
			$account = config("email");
			$account['Username'] = Cache::remember("email_username", function () {
				Config::parse("static/email_config", "ini");
				return Config::get("email_account.Username");
			});
			$account['Password'] = Cache::remember("email_password", function () {
				Config::parse("static/email_config", "ini");
				return Config::get("email_account.Password");
			});
			$mail->Host = $account['SMTP']; // Specify main and backup SMTP servers
			$mail->SMTPAuth = true; // Enable SMTP authentication
			$mail->Username = $account['Username']; // SMTP username
			$mail->Password = $account['<PASSWORD>']; // SMTP password
			$mail->SMTPSecure = 'ssl'; // Enable TLS encryption, `ssl` also accepted
			$mail->Port = $account['Port']; // TCP port to connect to
			$mail->setFrom($account['Username'], '专线开通辅助');
			if (is_string($address)) {
				$mail->addAddress($address);
			} else {
				foreach ($address as $key => $addr) {
					if (substr($key, 0, 2) === 'CC') $mail->addCC($addr);
					else $mail->addAddress($addr);
				}
			}
			// Name is optional
			// $mail->addReplyTo ( '<EMAIL>', 'Information' );
			// $mail->addCC ( '<EMAIL>' );
			// $mail->addBCC ( '<EMAIL>' );
			if (!is_null($attachments)) {
				if (is_array($attachments)) {
					foreach ($attachments as $attachment) {
						$mail->addAttachment($attachment);
					}
				} else {
					$mail->addAttachment($attachments);
				}
			}
			// $mail->addAttachment ( '/var/tmp/file.tar.gz' ); // Add attachments
			// $mail->addAttachment ( '/aa.jpg', '附件new.jpg' ); // Optional name
			// 绝对路径从磁盘根目录算起，相对路径从public/index.php算起。
			$mail->isHTML(true); // Set email format to HTML
			$mail->Subject = $subject;
			//$body .= '<br><br><br><p style="float:right;color:red;font-size:10px;">'+config('version')+'</p>';  
			$mail->Body = $body;
			$mail->AltBody = '您的邮件客户端不支持查看HTML格式的邮件正文。';

			if ($mail->send()) {
				return true;
			} else {
				return $mail->ErrorInfo;
			}
		} catch (Exception $e) {
			return $mail->ErrorInfo;
		}
	}
	public function _empty()
	{
		$request = Request::instance();
		$dir = APP_PATH . $request->module() . DS . "view" . DS . $request->controller() . DS . $request->action() . "." . config('template.view_suffix');
		if (file_exists($dir))
			return $this->fetch($request->action());
		else {
			return $this->error("请求未找到，将返回上一页...(common/controller/Common.php->_empty())");
		}
	}
}
<file_sep>/application/zx_apply/controller/Generator.php
<?php

namespace app\zx_apply\controller;

use app\zx_apply\model\Infotables;
use Overtrue\Pinyin\Pinyin;
use app\zx_apply\model\Iptables;


class Generator extends Common
{
/* 各系统的【单位属性】默认固定为“企业” */
/* 应该根据字段 unitProperty 来填写 */
/* 应删除字段 unitAttribute，或在 apply.html 中隐藏该字段，已精简页面显示 */
	public static function generateIDCinfoFiles($ids = null, $type = 2)
	{
		if (!is_array($ids)) {
			$ids = explode(",", $ids);
		}
		$row = 3;
		$cellValues = [];
		$sheets = [
			2 => ["表4", "表5", "表8", "表10", "表11"]
		];
		$colNames = "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z";
		$theCompany = base64_decode("5Lit5Zu956e75Yqo6YCa5L+h6ZuG5Zui6L695a6B5pyJ6ZmQ5YWs5Y+4");
		$engineRoom = base64_decode("6L695a6B56e75Yqo6ZOB5bKt5biCSURD5LiT57q/5py65oi/");
		$structure = [
			"表4" => [
				"title" => "IP地址段信息",
				"header" => "起始IP地址,终止IP地址,IP地址使用方式,使用人,使用人的证件类型,对应证件号码,来源单位,分配单位,分配使用时间,机房名称",
				"default" => [
					"C" => "专线",
					"G" => $theCompany,
					"H" => $theCompany,
					"J" => $engineRoom,
				],
			],
			"表5" => [
				"title" => "用户信息",
				"header" => "用户属性,单位名称,单位属性,证件类型,证件号码,单位地址,邮政编码,服务开通时间,注册时间,网络安全责任人(名称),网络安全责任人(证件号码)",
				"default" => [
					"A" => "其他用户",
					//"G" => 112000,
				]
			],
			"表8" => [
				"title" => "占用机房信息(其他用户)",
				"header" => "机房名称,资源分配时间,网络带宽(Mbps),用户信息(单位名称)",
				"default" => [
					"A" => $engineRoom,
				]
			],
			"表10" => [
				"title" => "其他用户IP地址段信息",
				"header" => "机房名称,资源分配时间,网络带宽,用户信息(单位名称),起始IP地址,终止IP地址",
				"default" => [
					"A" => $engineRoom,
				]
			],
			"表11" => [
				"title" => "网络安全责任人",
				"header" => "姓名,证件类型,证件号码,固定电话,移动电话,email地址",
				"default" => [
					"B" => "身份证",
					"D" => "无",
				]
			],
		];
		/* start creating cellValues */
		foreach ($sheets[$type] as $sheet) {
			/* build headers */
			$header = [];
			$keys = explode(" ", $colNames);
			$values = explode(",", $structure[$sheet]["header"]);
			foreach ($values as $k => $v) {
				$header[$keys[$k] . 2] = $v;
			}
			$cellValues[$sheet] = array_merge(["A1" => $structure[$sheet]["title"]], $header);
			/* add default values */
			foreach ($structure[$sheet]["default"] as $k => $v) {
				$cellValues[$sheet][$k . $row] = $v;
			}
		}
		function getArrayValue($arr = [], $k = "0")
		{
			return array_key_exists($k, $arr) ? $arr[$k] : null;
		}
		foreach ($ids as $id) {
			/* add dynamic values */
			$data = Infotables::get($id)->toArray();
			$extra = getArrayValue($data, "extra");
			$nullVal = null;
			$credential = $extra ? getArrayValue($extra, "credential") ? config("credential")[getArrayValue($extra, "credential")] : $nullVal : $nullVal;
			$unitProperty = $extra ? getArrayValue($extra, "unitProperty") ? config("unitProperty")[getArrayValue($extra, "unitProperty")] : $nullVal : $nullVal;
			if (in_array("表4", $sheets[$type])) {
				$cellValues["表4"]["A" . $row] = $data["ip"]; // ip
				$cellValues["表4"]["B" . $row] = $data["ip"]; // ip
				$cellValues["表4"]["D" . $row] = $data["cName"]; // 客户名
				$cellValues["表4"]["E" . $row] = $credential; // 证件类型
				$cellValues["表4"]["F" . $row] = getArrayValue($extra, "credentialnum"); // 证件号码
				$cellValues["表4"]["I" . $row] = $data["create_time"]; // 分配时间
			}
			if (in_array("表5", $sheets[$type])) {
				$cellValues["表5"]["B" . $row] = $data["cName"]; // 客户名
				$cellValues["表5"]["C" . $row] = $unitProperty; // 单位属性
				$cellValues["表5"]["D" . $row] = $credential; // 证件类型
				$cellValues["表5"]["E" . $row] = getArrayValue($extra, "credentialnum"); // 证件号码
				$cellValues["表5"]["F" . $row] = $data["cAddress"]; // 客户地址
				$cellValues["表5"]["G" . $row] = $data["extra"]["zipCode"]; // 邮政编码
				$cellValues["表5"]["H" . $row] = $data["create_time"]; // 服务开通时间
				$cellValues["表5"]["I" . $row] = $data["create_time"]; // 注册时间
				$cellValues["表5"]["J" . $row] = getArrayValue($extra, "securityPerson"); // 网络安全责任人
				$cellValues["表5"]["K" . $row] = getArrayValue($extra, "securityPersonID"); // 责任人身份证号
			}
			if (in_array("表8", $sheets[$type])) {
				$cellValues["表8"]["B" . $row] = $data["create_time"]; // 分配时间
				$cellValues["表8"]["C" . $row] = $data["bandWidth"] + 0; // 带宽
				$cellValues["表8"]["D" . $row] = $data["cName"]; // 客户名
			}
			if (in_array("表10", $sheets[$type])) {
				$cellValues["表10"]["B" . $row] = $data["create_time"]; // 分配时间
				$cellValues["表10"]["C" . $row] = $data["bandWidth"] + 0; // 带宽
				$cellValues["表10"]["D" . $row] = $data["cName"]; // 客户名
				$cellValues["表10"]["E" . $row] = $data["ip"]; // ip
				$cellValues["表10"]["F" . $row] = $data["ip"]; // ip
			}
			if (in_array("表11", $sheets[$type])) {
				$cellValues["表11"]["A" . $row] = getArrayValue($extra, "securityPerson");
				$cellValues["表11"]["C" . $row] = getArrayValue($extra, "securityPersonID"); // 责任人身份证号
				$cellValues["表11"]["E" . $row] = $data["cPhone"] + 0; // 客户电话
				$cellValues["表11"]["F" . $row] = $data["cEmail"]; // 客户邮箱
				$row++;
			}
		}
		//return $cellValues;
		$spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
		$index = 1;
        // 编辑worksheet
		foreach ($cellValues as $ws => $_sheet) {
			$worksheet = new \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet($spreadsheet, $ws);
			$spreadsheet->addSheet($worksheet, $index++);
			foreach ($_sheet as $k => $v) {
				$worksheet->setCellValue($k, $v);
				// $worksheet->getCell ( $k )->setValue ( $v );
			}
		}
		$spreadsheet->removeSheetByIndex(0);
		$spreadsheet->getProperties()->setCreator("X.Da");
		$spreadsheet->getProperties()->setLastModifiedBy('X.Da');
		$writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, "Xlsx");
		// header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); // 告诉浏览器数据excel07文件
		// header('Content-Disposition: attachment;filename="' . $fileName . '"'); // 告诉浏览器将输出文件的名称
		// header('Cache-Control: max-age=0'); // 禁止缓存
		// $writer->save("php://output");
		$dir = "../runtime/temp/";
		$fileName = '铁岭IDC.ISP-' . $data["ip"] . "-" . $data["cName"] . '.xlsx';
		// $tmp = time(). '.xlsx';
		// $writer->save($dir . $tmp);
		$writer->save($dir . $fileName);
		$spreadsheet->disconnectWorksheets();
		unset($spreadsheet);
		unset($writer);
		// if (file_exists($dir . $fileName) || !file_exists($dir . $tmp)) {
		// 	return -1;	// idc.isp附件生成失败
		// } else {
		// 	return rename($dir . $fileName, $dir . $tmp) ? $dir . $fileName : 0;
		// }
		return $dir . $fileName;
	}
	public static function generateZgWorkflow($ids = null)
	{
		if (is_string($ids)) {
			$ids = explode(",", $ids);
		}
		$row = 5;
		$cellValues = [];
		$principal = base64_decode("5Y2c546J");
		$tel = base64_decode("MTg4NDEwNTA4MTU=") + 0;
		$dept = base64_decode("5a6i5oi35ZON5bqU5Lit5b+D");
		$email = base64_decode("YnV5dS50bEBsbi5jaGluYW1vYmlsZS5jb20");
		$engineRoom = base64_decode("<KEY>");
		$default = [
			"B" => "铁岭",
			"D" => "占用",
			"E" => "192.168.127.12/20",
			"F" => "集客专线",
			"G" => "互联网专线",
			"H" => "LNTIL-MA-CMNET-BAS02-YZME60X",
			"L" => $principal,
			"M" => $tel,
			"N" => "Auto import! --X.Da",
			"O" => "铁岭",
			"P" => $principal,
			"R" => "其他",
			//"S" => "企业",
			"T" => "辽宁",
			"W" => $dept,
			"X" => $email,
			"AD" => "静态",
			"AH" => "已启用",
			"AI" => $engineRoom,
		];
		foreach ($ids as $id) {
			foreach ($default as $k => $v) {
				$cellValues[$k . $row] = $v;
			}
			$data = Infotables::get($id)->toArray();
			$cellValues["C" . $row] = $data["ip"] . "/32"; // ip
			$cellValues["K" . $row] = $data["create_time"]; // 分配时间
			$cellValues["Q" . $row] = $data["cName"]; // 客户名
			$cellValues["S" . $row] = $data["extra"]["unitAttribute"]; // 企业属性
            /* 以下为选填 */
			$cellValues["I" . $row] = $data["cPerson"]; // 客户联系人
			$cellValues["J" . $row] = $data["cPhone"] + 0; // 客户电话
			$cellValues["U" . $row] = $data["cAddress"]; // 客户地址
			$cellValues["V" . $row] = $data["cEmail"]; // 客户邮箱
			$cellValues["AG" . $row] = $data["instanceId"] + 0; // 政企专线计费代号
			$row++;
		}
		$pFilename = './static/sampleData/zg_import.xls';
		return self::exportExcelFile($pFilename, 0, $cellValues, 'Xls', '资管流程-' . $data["cName"] . '.xls');
	}

	public static function generateJtIp($ids = null)
	{
		$row = 4;
		$cellValues = [];
		$dept = base64_decode("6ZOB5bKt56e75Yqo5a6i5oi35ZON5bqU5Lit5b+D");
		$principal = base64_decode("5Y2c546J");
		$tel = base64_decode("MTg4NDEwNTA4MTU=") + 0;
		$email = base64_decode("YnV5dS50bEBsbi5jaGluYW1vYmlsZS5jb20");
		$default = [
			"D" => "其他",
			//"F" => "企业",
			"G" => "辽宁",
			"H" => "铁岭",
			"Q" => "静态",
			"T" => "互联网专线",
			"U" => "占用",
			"V" => "已启用",
			"AA" => $dept,
			"AB" => $principal,
			"AC" => $tel,
			"AD" => $email
		];
		foreach ($ids as $id) {
			foreach ($default as $k => $v) {
				$cellValues[$k . $row] = $v;
			}
			$data = Infotables::get($id)->toArray();
			$cellValues["B" . $row] = $data["ip"] . "/32"; // ip
			$cellValues["C" . $row] = $data["cName"]; // 客户名
			$cellValues["F" . $row] = $data["extra"]["unitAttribute"]; // 企业属性
			$cellValues["L" . $row] = $data["cAddress"]; // 客户地址
			$cellValues["M" . $row] = $data["cPerson"]; // 客户联系人
			$cellValues["N" . $row] = $data["cPhone"] + 0; // 客户电话
			$cellValues["O" . $row] = $data["cEmail"]; // 客户邮箱
			$cellValues["R" . $row] = $data["create_time"]; // 分配时间
			$row++;
		}
		$pFilename = './static/sampleData/ip_jt.xlsx';
		return self::exportExcelFile($pFilename, 0, $cellValues, 'Xlsx', '集团IP备案-' . $data["cName"] . '.xlsx');
	}

	public static function generateGxbIp($ids = null)
	{
		$row = 2;
		$cellValues = [];
		$default = [
			"D" => "其他",
			//"F" => "企业",
			"Q" => "静态"
		];
		foreach ($ids as $id) {
			foreach ($default as $k => $v) {
				$cellValues[$k . $row] = $v;
			}
			$data = Infotables::get($id)->toArray();
			$cellValues["A" . $row] = $data["ip"]; // ip
			$cellValues["B" . $row] = $data["ip"]; // ip
			$cellValues["C" . $row] = $data["cName"]; // 客户名
			$cellValues["F" . $row] = $data["extra"]["unitAttribute"]; // 企业属性
			$cellValues["L" . $row] = $data["cAddress"]; // 客户地址
			$cellValues["M" . $row] = $data["cPerson"]; // 客户联系人
			$cellValues["N" . $row] = $data["cPhone"] + 0; // 客户电话
			$cellValues["O" . $row] = $data["cEmail"]; // 客户邮箱
			$cellValues["R" . $row] = $data["create_time"]; // 分配时间
			$cellValues["F" . $row] = "企业";
			$cellValues["G" . $row] = isset($data["extra"]["province"]) ? $data["extra"]["province"] : "";
			$cellValues["H" . $row] = isset($data["extra"]["city"]) ? $data["extra"]["city"] : "";
			$row++;
		}
		$pFilename = './static/sampleData/ip_gxb.xls';
		return self::exportExcelFile($pFilename, 4, $cellValues, 'Xls', '工信部IP备案-' . $data["cName"] . '.xls');
	}

	/**
	 * 导出到excel
	 *
	 * @param unknown $pFilename 模板文件地址
	 * @param unknown $workSheetIndex 工作表索引
	 * @param unknown $cellValues  数据
	 * @param unknown $writerType 输出类型 Xls or Xlsx
	 * @param unknown $fileName 输出文件名
	 * @param String $writerMethod 写入$cellValues使用的phpsSpreadsheet 方法名
	 * @param boolval $unusePHP 是否用 PHP 导出 excel 文件
	 */
	private static function exportExcelFile($pFilename, $workSheetIndex, $cellValues, $writerType, $fileName, $writerMethod = "setCellValue")
	{
		if (!input("post.php/b")) {
			return [
				"fileName" => $fileName,
				"cellValues" => $cellValues
			];
		}
		$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($pFilename);
		$worksheet = $spreadsheet->getSheet($workSheetIndex);
        // 编辑worksheet
		foreach ($cellValues as $d => $v) {
			$worksheet->$writerMethod($d, $v);
            // $worksheet->getCell ( $d )->setValue ( $v );
		}
        // 定义spreadsheet参数并输出到浏览器
		$spreadsheet->getProperties()->setCreator("X.Da");
		$spreadsheet->getProperties()->setLastModifiedBy('X.Da');
		$writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, $writerType);
		if ($writerType == "Xls") {
			header('Content-Type: application/vnd.ms-excel'); // 告诉浏览器将要输出excel03文件
		} else {
			header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); // 告诉浏览器数据excel07文件
		}
		header('Content-Disposition: attachment;filename="' . $fileName . '"'); // 告诉浏览器将输出文件的名称
		header('Cache-Control: max-age=0'); // 禁止缓存
		$writer->save("php://output");
		$spreadsheet->disconnectWorksheets();
		unset($spreadsheet);
		unset($writer);
	}
	public static function generateScript($id = null)
	{
		$data = Infotables::get($id);
		if ($data["zxType"] == "互联网") {
			if ($data['neFactory'] == 'ONU') {
				return self::error("ONU业务暂不支持数据制作脚本");
			}
			return self::generateScriptNet($data);
		}
		if ($data["zxType"] == "卫生网") {
			return self::generateScriptWsw($data);
		}
	}

	/**
	 * 数据制作脚本生成-互联网
	 *
	 * @param unknown $data            
	 * @return string|NULL[]|string[]
	 */
	private static function generateScriptNet($data)
	{
		$data["domain"] = 'tlyd-rb'; // 1. domain
		$aStation = config("aStation");
		$data["sw93"] = $aStation[$data["aStation"]]; // 2. 9312名
		$pinyin = new Pinyin();
		$desc = substr($data["sw93"], 0, stripos($data["sw93"], "-") + 1);
		$desc = str_replace("CHJ", "TL", $desc);
		$_desc = $pinyin->convert(preg_replace("/[^\x{4e00}-\x{9fa5}A-Za-z0-9-]/u", "", $data["cName"]));
		foreach ($_desc as $v) {
			$desc .= ucfirst($v);
		}
		$data["desc"] = $desc . "_NET"; // 3. 描述
		$device9312 = json_decode(base64_decode(config("device9312")), true)[$data["sw93"]];
		function bas($bas, $device9312, $data)
		{
			$trunk = $device9312['bas' . $bas . '_down_port'];
			$rbp = $device9312['rbp_name'];
			$_bas_name = [
				"01" => "01-CHJ",
				"02" => "02-YZL"
			];
			if (strlen($data["domain"]) > 4) { // tlyd-rb
				$rbp = "\n remote-backup-profile " . $rbp;
			} else {
				$rbp = null;
			}
			$script = "interface " . $trunk . "." . $data["vlan"];
			$script .= "\ndis th\n ";
			$script .= "\n description [LNTIL-MA-CMNET-BAS" . $_bas_name[$bas] . "ME60X]" . $trunk . "." . $data["vlan"] . "-[" . $data["desc"] . "]";
			$script .= "\n user-vlan " . $data["vlan"] . $rbp;
			$script .= "\n bas\n #\n access-type layer2-subscriber default-domain authentication " . $data["domain"];
			$script .= "\n authentication-method bind\n";
			$script .= "static-user " . $data["ip"] . " " . $data["ip"] . " gateway " . substr($data["ip"], 0, strripos($data["ip"], ".") + 1) . "1 " . "interface " . $trunk . "." . $data["vlan"] . " vlan " . $data["vlan"] . " domain-name " . $data["domain"] . " detect\r\n";
			return $script;
		}
		function the93($device9312, $data)
		{
			if ($data["neFactory"]) {
				$data["neFactory"] === '华为' && $down = "port_hw";
				$data["neFactory"] === '中兴' && $down = "port_zte";
			} else {
				return "网元厂家未定义";
			}
			$script = "vlan " . $data["vlan"] . "\ndis th\n \n";
			$script .= "description to-[" . $data["desc"] . "]\nq";
			$script .= "\ninterface " . $device9312["up_port_yz"]; // 上行银州 bas02
			$script .= "\nport trunk allow-pass vlan " . $data["vlan"];
			if (strlen($data["domain"]) > 4) { // 上行柴河 bas01
				$script .= "\ninterface " . $device9312["up_port_ch"] . "\nport trunk allow-pass vlan " . $data["vlan"];
			}
			$script .= "\ninterface " . $device9312[$down]; // 下行
			$script .= "\nport trunk allow-pass vlan " . $data["vlan"];
			$script .= "\nq\r\n";
			return $script;
		}
		$result = [
			"bas01" => bas("01", $device9312, $data),
			"bas02" => bas("02", $device9312, $data),
			"the93" => [
				$data["sw93"],
				the93($device9312, $data)
			]
		];
		return $result;
	}

	/**
	 * 数据制作脚本生成-卫生网
	 *
	 * @param unknown $data            
	 * @return string|NULL[]|string[]
	 */
	private static function generateScriptWsw($data)
	{
		$aStation = config("aStation");
		$data["sw93"] = $aStation[$data["aStation"]]; // 2. 9312名
		$pinyin = new Pinyin();
		$desc = substr($data["sw93"], 0, stripos($data["sw93"], "-") + 1); // 3. 描述
		$desc = str_replace("CHJ", "TL", $desc);
		$_desc = $pinyin->convert(preg_replace("/[^\x{4e00}-\x{9fa5}A-Za-z0-9-]/u", "", $data["cName"]));
		foreach ($_desc as $v) {
			$desc .= ucfirst($v);
		}
		$device9312 = json_decode(config("device9312"), true)[$data["sw93"]];
		$trunk = $device9312['bas02_down_port'];
		$ip = Iptables::ip_parse($data["ip"]);
		$ipB = Iptables::ip_parse($data["ipB"]);
		$script = "interface " . $trunk . "." . $data["vlan"] . "\ndis th\n";
		$script .= "\n vlan-type dot1q " . $data["vlan"];
		$script .= "\n description [LNTIL-MA-CMNET-BAS02-YZLME60X]" . $trunk . "." . $data["vlan"] . "-[" . $desc . "]";
		$script .= "\n ip binding vpn-instance tlwsw";
		$script .= "\n ip address " . long2ip($ipB[2] + 1) . " " . long2ip($ipB[1]);
		$script .= "\n traffic-policy remarkdscp inbound";
		$script .= "\n statistic enable";
		$script .= "\nip route-static vpn-instance tlwsw " . long2ip($ip[2]) . " " . long2ip($ip[1]) . " " . long2ip($ipB[2] + 2) . "\r\n";
        /* the9312 */
		if ($data["neFactory"]) {
			$data["neFactory"] === '华为' && $down = "port_hw";
			$data["neFactory"] === '中兴' && $down = "port_zte";
		} else {
			return [
				"the93" => [
					"网元厂家未定义",
					""
				]
			];
		}
		$the9312 = "vlan " . $data["vlan"] . "\ndis th\n\n";
		$the9312 .= "description to-[" . $desc . "]\nq";
		$the9312 .= "\ninterface " . $device9312["up_port_yz"] . "\nport trunk allow-pass vlan " . $data["vlan"];
		$the9312 .= "\ninterface " . $device9312[$down] . "\nport trunk allow-pass vlan " . $data["vlan"] . "\nq\r\n";
		return [
			"bas02" => $script,
			"the93" => [
				$data["sw93"],
				$the9312
			]
		];
	}
	/**
	 * 修复历史数据新增 6 个 extra 字段为空的问题
	 *
	 * @return void
	 */
	public function fix6newExtraFields()
	{
		$data = Infotables::all();
		foreach ($data as $k => $v) {
			$unitProperty = config("unitProperty");
			$extra = $v->extra;
			if (is_null($extra)) {
				continue;
			}
			if (!isset($extra['unitAttribute'])) $extra['unitAttribute'] = is_null($extra['unitProperty']) ? "" : $unitProperty[$extra['unitProperty']];
			if (!isset($extra['securityPerson'])) $extra['securityPerson'] = is_null($v->cPerson) ? "" : $v->cPerson;
			if (!isset($extra['securityPersonID'])) $extra['securityPersonID'] = '0';
			if (!isset($extra['securityPhone'])) $extra['securityPhone'] = is_null($v->cPhone) ? "" : $v->cPhone;
			if (!isset($extra['securityEmail'])) $extra['securityEmail'] = is_null($v->cEmail) ? "" : $v->cEmail;
			if (!isset($extra['zipCode'])) $extra['zipCode'] = "112000";
			$data[$k]->extra = $extra;
			$data[$k]->save();
		}
		return dump("ok");
	}

	private function cacheSettings()
	{
		$client = new \Redis();
		$client->connect('127.0.0.1', 6379);
		$pool = new \Cache\Adapter\Redis\RedisCachePool($client);
		$simpleCache = new \Cache\Bridge\SimpleCache\SimpleCacheBridge($pool);
		\PhpOffice\PhpSpreadsheet\Settings::setCache($simpleCache);
	}
}<file_sep>/application/trouble/model/Forms.php
<?php

namespace app\trouble\model;

use think\Model;

class Forms extends Model {
	protected $type = [ 
			'id' => 'integer',
			'createTime' => 'datetime',
			'applicationApprovalTime' => 'datetime',
			'approvalTime' => 'datetime',
			'dispatchTime' => 'datetime',
			'acceptanceTime' => 'datetime',
			'marks' => 'integer',
			'status' => 'integer' 
	];
	public static function beautify($data, $single = false) {
		$status = [ 
				0 => '等待部门审批',
				1 => '部门不同意',
				2 => '等待处理',
				3 => '客响不同意',
				4 => '等待处理',
				5 => '已撤销',
				6 => '已派单，待受理',
				7 => '处理中',
				8 => '待申请人确认',
				9 => '待派单人归档',
				- 1 => '已归档' 
		];
		if (empty ( $data )) {
			return null;
		} else {
			if ($single) {
				$data ['statusText'] = $status [$data ['status']];
				$data ['logs'] = strtr ( $data ['logs'], [ 
						"；" => "；<br>" 
				] );
			} else {
				foreach ( $data as $k => $v ) {
					$data [$k] ['statusText'] = $status [$v ['status']];
				}
			}
			return $data;
		}
	}
}