qzyReal 2 лет назад
Сommit
f656d1c640
100 измененных файлов с 11372 добавлено и 0 удалено
  1. 12 0
      .idea/face-ui.iml
  2. 6 0
      .idea/misc.xml
  3. 8 0
      .idea/modules.xml
  4. 182 0
      .idea/workspace.xml
  5. 70 0
      index.html
  6. 158 0
      login.html
  7. 21 0
      main.html
  8. BIN
      sa-frame/admin-logo.png
  9. 252 0
      sa-frame/com/sa-info.vue
  10. 455 0
      sa-frame/com/sa-item.vue
  11. 261 0
      sa-frame/com/sa-td.vue
  12. BIN
      sa-frame/index/admin-loading.gif
  13. 106 0
      sa-frame/index/admin-util.js
  14. 65 0
      sa-frame/index/index.css
  15. 496 0
      sa-frame/index/index.js
  16. 225 0
      sa-frame/index/theme.css
  17. 127 0
      sa-frame/login/app.js
  18. BIN
      sa-frame/login/bg.jpg
  19. BIN
      sa-frame/login/name.png
  20. 8 0
      sa-frame/login/particles.min.js
  21. BIN
      sa-frame/login/password.png
  22. 35 0
      sa-frame/login/reset.css
  23. 152 0
      sa-frame/login/style.css
  24. 342 0
      sa-frame/menu-list-sp.js
  25. 19 0
      sa-frame/menu-list.js
  26. 61 0
      sa-frame/nav/com-add-tab.vue
  27. 160 0
      sa-frame/nav/com-right-menu.vue
  28. 37 0
      sa-frame/nav/nav-logo.vue
  29. 133 0
      sa-frame/nav/nav-menu-bar.vue
  30. 158 0
      sa-frame/nav/nav-tab-bar.vue
  31. 332 0
      sa-frame/nav/nav-tool-bar.vue
  32. 57 0
      sa-frame/nav/nav-view-vessel.vue
  33. 148 0
      sa-frame/sa-code.js
  34. 108 0
      sa-view-sp/console/com-chart-1.vue
  35. 114 0
      sa-view-sp/console/com-chart-2.vue
  36. 113 0
      sa-view-sp/console/com-chart-3.vue
  37. 49 0
      sa-view-sp/console/com-intro.vue
  38. 47 0
      sa-view-sp/console/com-origin.vue
  39. 129 0
      sa-view-sp/console/com-sta-data.vue
  40. 66 0
      sa-view-sp/console/com-stack.vue
  41. 289 0
      sa-view-sp/console/com-update-log.vue
  42. 119 0
      sa-view-sp/console/console-main.html
  43. 117 0
      sa-view-sp/per-tips/alert-tips.html
  44. 133 0
      sa-view-sp/sp-admin-login/sp-admin-login-list.html
  45. 127 0
      sa-view-sp/sp-admin/admin-add.html
  46. 83 0
      sa-view-sp/sp-admin/admin-info.html
  47. 288 0
      sa-view-sp/sp-admin/admin-list.html
  48. 82 0
      sa-view-sp/sp-admin/update-password.html
  49. 88 0
      sa-view-sp/sp-apilog/api-log-list-delete.html
  50. 333 0
      sa-view-sp/sp-apilog/api-log-list.html
  51. 139 0
      sa-view-sp/sp-cfg/app-cfg.html
  52. 147 0
      sa-view-sp/sp-cfg/server-cfg.html
  53. 344 0
      sa-view-sp/sp-console/redis-console.html
  54. 106 0
      sa-view-sp/sp-console/redis-key-add.html
  55. 20 0
      sa-view-sp/sp-console/sql-console.html
  56. 63 0
      sa-view-sp/sp-role/menu-list.html
  57. 166 0
      sa-view-sp/sp-role/menu-setup.html
  58. 104 0
      sa-view-sp/sp-role/role-add.html
  59. 142 0
      sa-view-sp/sp-role/role-list.html
  60. 113 0
      sa-view/sys-dict/sys-dict-add.html
  61. 64 0
      sa-view/sys-dict/sys-dict-info.html
  62. 134 0
      sa-view/sys-dict/sys-dict-list.html
  63. 148 0
      sa-view/tb-car-black/tb-car-black-add.html
  64. 73 0
      sa-view/tb-car-black/tb-car-black-info.html
  65. 142 0
      sa-view/tb-car-black/tb-car-black-list.html
  66. 162 0
      sa-view/tb-car-filing/tb-car-filing-add.html
  67. 77 0
      sa-view/tb-car-filing/tb-car-filing-info.html
  68. 153 0
      sa-view/tb-car-filing/tb-car-filing-list.html
  69. 134 0
      sa-view/tb-channel/tb-channel-add.html
  70. 70 0
      sa-view/tb-channel/tb-channel-info.html
  71. 172 0
      sa-view/tb-channel/tb-channel-list.html
  72. 124 0
      sa-view/tb-dept/tb-dept-add.html
  73. 70 0
      sa-view/tb-dept/tb-dept-info.html
  74. 146 0
      sa-view/tb-dept/tb-dept-list.html
  75. 151 0
      sa-view/tb-person-black/tb-person-black-add.html
  76. 73 0
      sa-view/tb-person-black/tb-person-black-info.html
  77. 135 0
      sa-view/tb-person-black/tb-person-black-list.html
  78. 155 0
      sa-view/tb-person-filing/tb-person-filing-add.html
  79. 77 0
      sa-view/tb-person-filing/tb-person-filing-info.html
  80. 159 0
      sa-view/tb-person-filing/tb-person-filing-list.html
  81. 149 0
      sa-view/tb-terminal/tb-terminal-add.html
  82. 74 0
      sa-view/tb-terminal/tb-terminal-info.html
  83. 192 0
      sa-view/tb-terminal/tb-terminal-list.html
  84. 122 0
      sa-view/tb-venues/tb-venues-add.html
  85. 66 0
      sa-view/tb-venues/tb-venues-info.html
  86. 156 0
      sa-view/tb-venues/tb-venues-list.html
  87. BIN
      static/icon/icon-article.png
  88. BIN
      static/icon/icon-comment.png
  89. BIN
      static/icon/icon-goods.png
  90. BIN
      static/icon/icon-money.png
  91. BIN
      static/icon/icon-order.png
  92. BIN
      static/icon/icon-user.png
  93. BIN
      static/img/kulian.png
  94. BIN
      static/img/up-icon.png
  95. 1 0
      static/kj/Sortable.min.js
  96. 0 0
      static/kj/element-ui/index.js
  97. BIN
      static/kj/element-ui/theme-chalk/fonts/element-icons.ttf
  98. BIN
      static/kj/element-ui/theme-chalk/fonts/element-icons.woff
  99. 0 0
      static/kj/element-ui/theme-chalk/index.css
  100. 478 0
      static/kj/httpVueLoader.js

+ 12 - 0
.idea/face-ui.iml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$">
+      <excludeFolder url="file://$MODULE_DIR$/.tmp" />
+      <excludeFolder url="file://$MODULE_DIR$/temp" />
+      <excludeFolder url="file://$MODULE_DIR$/tmp" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 6 - 0
.idea/misc.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="JavaScriptSettings">
+    <option name="languageLevel" value="ES6" />
+  </component>
+</project>

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/face-ui.iml" filepath="$PROJECT_DIR$/.idea/face-ui.iml" />
+    </modules>
+  </component>
+</project>

+ 182 - 0
.idea/workspace.xml

@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ChangeListManager">
+    <list default="true" id="5a92affb-0c7f-4924-9551-c65b96d91036" name="Default Changelist" comment="" />
+    <ignored path="$PROJECT_DIR$/.tmp/" />
+    <ignored path="$PROJECT_DIR$/temp/" />
+    <ignored path="$PROJECT_DIR$/tmp/" />
+    <option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
+    <option name="SHOW_DIALOG" value="false" />
+    <option name="HIGHLIGHT_CONFLICTS" value="true" />
+    <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
+    <option name="LAST_RESOLUTION" value="IGNORE" />
+  </component>
+  <component name="FileEditorManager">
+    <leaf SIDE_TABS_SIZE_LIMIT_KEY="300">
+      <file pinned="false" current-in-tab="false">
+        <entry file="file://$PROJECT_DIR$/login.html">
+          <provider selected="true" editor-type-id="text-editor">
+            <state relative-caret-position="2604">
+              <caret line="84" column="4" selection-start-line="84" selection-start-column="4" selection-end-line="84" selection-end-column="4" />
+            </state>
+          </provider>
+        </entry>
+      </file>
+      <file pinned="false" current-in-tab="true">
+        <entry file="file://$PROJECT_DIR$/sa-frame/menu-list-sp.js">
+          <provider selected="true" editor-type-id="text-editor">
+            <state relative-caret-position="217">
+              <caret line="42" selection-start-line="42" selection-end-line="42" />
+              <folding>
+                <element signature="n#!!doc" expanded="true" />
+              </folding>
+            </state>
+          </provider>
+        </entry>
+      </file>
+      <file pinned="false" current-in-tab="false">
+        <entry file="file://$PROJECT_DIR$/sa-frame/menu-list.js">
+          <provider selected="true" editor-type-id="text-editor">
+            <state relative-caret-position="1054">
+              <caret line="48" selection-start-line="48" selection-end-line="48" />
+              <folding>
+                <element signature="n#!!doc" expanded="true" />
+              </folding>
+            </state>
+          </provider>
+        </entry>
+      </file>
+    </leaf>
+  </component>
+  <component name="IdeDocumentHistory">
+    <option name="CHANGED_PATHS">
+      <list>
+        <option value="$PROJECT_DIR$/login.html" />
+        <option value="$PROJECT_DIR$/sa-frame/menu-list.js" />
+        <option value="$PROJECT_DIR$/sa-frame/menu-list-sp.js" />
+      </list>
+    </option>
+  </component>
+  <component name="ProjectFrameBounds" extendedState="6">
+    <option name="x" value="153" />
+    <option name="y" value="135" />
+    <option name="width" value="3150" />
+    <option name="height" value="1980" />
+  </component>
+  <component name="ProjectView">
+    <navigator proportions="" version="1">
+      <foldersAlwaysOnTop value="true" />
+    </navigator>
+    <panes>
+      <pane id="ProjectPane">
+        <subPane>
+          <expand>
+            <path>
+              <item name="face-ui" type="b2602c69:ProjectViewProjectNode" />
+              <item name="face-ui" type="462c0819:PsiDirectoryNode" />
+            </path>
+            <path>
+              <item name="face-ui" type="b2602c69:ProjectViewProjectNode" />
+              <item name="face-ui" type="462c0819:PsiDirectoryNode" />
+              <item name="sa-frame" type="462c0819:PsiDirectoryNode" />
+            </path>
+          </expand>
+          <select />
+        </subPane>
+      </pane>
+      <pane id="Scope" />
+    </panes>
+  </component>
+  <component name="PropertiesComponent">
+    <property name="WebServerToolWindowFactoryState" value="false" />
+    <property name="last_opened_file_path" value="$PROJECT_DIR$/../../douyin/website" />
+    <property name="nodejs_interpreter_path.stuck_in_default_project" value="undefined stuck path" />
+    <property name="nodejs_npm_path_reset_for_default_project" value="true" />
+    <property name="settings.editor.selected.configurable" value="liveEdit" />
+  </component>
+  <component name="RunDashboard">
+    <option name="ruleStates">
+      <list>
+        <RuleState>
+          <option name="name" value="ConfigurationTypeDashboardGroupingRule" />
+        </RuleState>
+        <RuleState>
+          <option name="name" value="StatusDashboardGroupingRule" />
+        </RuleState>
+      </list>
+    </option>
+  </component>
+  <component name="SvnConfiguration">
+    <configuration />
+  </component>
+  <component name="TaskManager">
+    <task active="true" id="Default" summary="Default task">
+      <changelist id="5a92affb-0c7f-4924-9551-c65b96d91036" name="Default Changelist" comment="" />
+      <created>1656645596059</created>
+      <option name="number" value="Default" />
+      <option name="presentableId" value="Default" />
+      <updated>1656645596059</updated>
+      <workItem from="1656645597166" duration="2399000" />
+      <workItem from="1656896967927" duration="1121000" />
+      <workItem from="1656936653114" duration="10000" />
+    </task>
+    <servers />
+  </component>
+  <component name="TimeTrackingManager">
+    <option name="totallyTimeSpent" value="3530000" />
+  </component>
+  <component name="ToolWindowManager">
+    <frame x="-6" y="34" width="1548" height="932" extended-state="6" />
+    <layout>
+      <window_info active="true" content_ui="combo" id="Project" order="0" visible="true" weight="0.19418758" />
+      <window_info id="Structure" order="1" side_tool="true" weight="0.25" />
+      <window_info id="Favorites" order="2" side_tool="true" />
+      <window_info anchor="bottom" id="Message" order="0" />
+      <window_info anchor="bottom" id="Find" order="1" />
+      <window_info anchor="bottom" id="Run" order="2" />
+      <window_info anchor="bottom" id="Debug" order="3" weight="0.4" />
+      <window_info anchor="bottom" id="Cvs" order="4" weight="0.25" />
+      <window_info anchor="bottom" id="Inspection" order="5" weight="0.4" />
+      <window_info anchor="bottom" id="TODO" order="6" />
+      <window_info anchor="bottom" id="Docker" order="7" show_stripe_button="false" />
+      <window_info anchor="bottom" id="Version Control" order="8" />
+      <window_info anchor="bottom" id="Terminal" order="9" />
+      <window_info anchor="bottom" id="Event Log" order="10" side_tool="true" />
+      <window_info anchor="right" id="Commander" internal_type="SLIDING" order="0" type="SLIDING" weight="0.4" />
+      <window_info anchor="right" id="Ant Build" order="1" weight="0.25" />
+      <window_info anchor="right" content_ui="combo" id="Hierarchy" order="2" weight="0.25" />
+    </layout>
+  </component>
+  <component name="TypeScriptGeneratedFilesManager">
+    <option name="version" value="1" />
+  </component>
+  <component name="editorHistoryManager">
+    <entry file="file://$PROJECT_DIR$/login.html">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="2604">
+          <caret line="84" column="4" selection-start-line="84" selection-start-column="4" selection-end-line="84" selection-end-column="4" />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/sa-frame/menu-list.js">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="1054">
+          <caret line="48" selection-start-line="48" selection-end-line="48" />
+          <folding>
+            <element signature="n#!!doc" expanded="true" />
+          </folding>
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/sa-frame/menu-list-sp.js">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="217">
+          <caret line="42" selection-start-line="42" selection-end-line="42" />
+          <folding>
+            <element signature="n#!!doc" expanded="true" />
+          </folding>
+        </state>
+      </provider>
+    </entry>
+  </component>
+</project>

+ 70 - 0
index.html

@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title></title>
+		<meta charset="utf-8">
+		<link rel="shortcut icon" type="image/x-icon" href="sa-frame/admin-logo.png" class="admin-icon">
+		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<link rel="stylesheet" href="https://unpkg.com/element-ui@2.13.0/lib/theme-chalk/index.css">
+		<link rel="stylesheet" href="sa-frame/index/index.css">
+		<link rel="stylesheet" href="sa-frame/index/theme.css">
+		<link rel="stylesheet" href="static/sa.css">
+	</head>
+	<body>
+		<!-- App -->
+		<div class="app" style="display: none;" :style="'display: block;'" 
+			:class="['theme-0', 'theme-' + themeV, (isOpen ? '' : 'app-fold'), (isOpenRight ? '' : 'app-fold-right')]" >
+			<!-- 左 -->
+			<div class="nav-left">
+				<!-- logo部分 -->
+				<div class="nav-left-top">
+					<nav-logo></nav-logo>
+				</div>
+				<!-- 左下:菜单 -->
+				<div class="nav-left-bottom">
+					<nav-menu-bar ref="nav-menu-bar"></nav-menu-bar>
+				</div>
+			</div>
+			<!-- 右 -->
+			<div class="nav-right">
+				<!-- 工具栏 -->
+				<div class="nav-right-1" des="">
+					<nav-tool-bar ref="nav-tool-bar"></nav-tool-bar>
+				</div>
+				<!-- Tab栏 -->
+				<div class="nav-right-2">
+					<nav-tab-bar ref="nav-tab-bar"></nav-tab-bar>
+				</div>
+				<!-- 视图容器 -->
+				<div class="nav-right-3">
+					<nav-view-vessel></nav-view-vessel>
+				</div>
+			</div>
+			<!-- 右键菜单 -->
+			<com-right-menu ref="com-right-menu"></com-right-menu>
+			<!-- 双击添加新tab -->
+			<com-add-tab ref="com-add-tab"></com-add-tab>
+		</div>
+		
+		<!-- js依赖库 -->
+		<script src="https://unpkg.com/vue@2.6.10/dist/vue.js"></script>
+		<script type="text/javascript">Vue.config.productionTip = false;</script>
+		<script src="https://unpkg.com/http-vue-loader@1.4.2/src/httpVueLoader.js"></script>
+		<script src="https://unpkg.com/element-ui@2.13.0/lib/index.js"></script>
+		<script src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"></script>
+		<script src="https://www.layuicdn.com/layer-v3.1.1/layer.js"></script>
+		<script src="https://unpkg.com/sortablejs@1.14.0/Sortable.min.js"></script>
+		<script src="https://unpkg.com/vuedraggable@2.24.3/dist/vuedraggable.umd.min.js"></script>
+		<!-- <script src="https://www.itxst.com/package/sortable/sortable.min.js"></script> -->
+		<!-- <script src="https://www.itxst.com/package/vuedraggable/vuedraggable.umd.min.js"></script> -->
+		
+		<!-- js本地库 -->
+		<script src="sa-frame/index/admin-util.js"></script>
+		<script src="sa-frame/index/index.js"></script>
+		<script src="sa-frame/menu-list.js"></script>
+		<script src="sa-frame/menu-list-sp.js"></script>
+		<script src="static/sa.js"></script>
+		<script src="sa-frame/sa-code.js"></script>
+
+	</body>
+</html>

+ 158 - 0
login.html

@@ -0,0 +1,158 @@
+<!DOCTYPE html>
+<html lang="zh">
+	<head>
+		<meta charset="utf-8">
+		<title>登录</title>
+		<meta name="description" content="particles.js is a lightweight JavaScript library for creating particles.">
+		<meta name="author" content="Vincent Garreau" />
+		<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
+		<link rel="stylesheet" media="screen" href="sa-frame/login/style.css">
+		<link rel="stylesheet" href="static/sa.css">
+		<style type="text/css">
+			/* 背景图片 */
+			#particles-js{
+				background-image: url(sa-frame/login/bg.jpg);
+			}
+
+			/* 样式调整 */
+			.login-box{display: flex; justify-content: center; align-items: center; position: fixed; width: inherit; height: 100%; pointer-events: none;}
+			.login{height: auto; padding: 50px 50px; position: static; margin: 0 auto !important; pointer-events: all; border-radius: 0px;}
+			.login-top{margin-top: 0px; margin-bottom: 30px;}
+			.logo-img{width: 50px; height: 50px; vertical-align: middle; position: relative; top: -3px; border-radius: 50%; margin-left: -10px; margin-right: 10px;}
+			.logo-img{display: none;}
+			.login-button{width: 300px; border-radius: 0px; transition: all 0.2s; margin-top: 30px;}
+			.login-button:hover{background-color: #0E80eF;}
+			/* .page-title{line-height: 50px;} */
+			.sk-rotating-plane{}
+			/* 动画相关 */
+			/* .login{background-color: rgba(0,0,0,0); } */
+			.login{opacity: 0; width: 380px;}
+			.login-center-input{width: 260px;}
+
+			.login-box .remember-box{color: #888; margin-bottom: 10px;}
+			.remember-box label{cursor: pointer; }
+			.remember-box [type=checkbox]{position: relative; top: 2px; margin-right: 2px;}
+			.remember-box .remark-text{float: right; position: relative; top: 1px; margin-right: 5px;}
+		</style>
+	</head>
+	<body>
+
+		<div id="particles-js">
+			<div class="login-box">
+				<div class="login">
+					<div class="login-top">
+						<img src="" class="logo-img" alt="">
+						<span class="page-title">登录</span>
+					</div>
+					<div class="login-center clearfix">
+						<div class="login-center-img"><img src="sa-frame/login/name.png" /></div>
+						<div class="login-center-input">
+							<input type="text" name="key" value="" placeholder="请输入账号" />
+							<div class="login-center-input-text">账号</div>
+						</div>
+					</div>
+					<div class="login-center clearfix">
+						<div class="login-center-img"><img src="sa-frame/login/password.png" /></div>
+						<div class="login-center-input">
+							<input type="password" name="password" value="" placeholder="请输入密码" />
+							<div class="login-center-input-text">密码</div>
+						</div>
+					</div>
+					<div class="login-center clearfix remember-box">
+						<label><input type="checkbox" id="remember"> 记住我</label>
+						<span class="remark-text">测试:sa/123456</span>
+					</div>
+					<div class="login-button">登录</div>
+				</div>
+			</div>
+			<div class="sk-rotating-plane"></div>
+		</div>
+
+		<!-- scripts -->
+		<script src="sa-frame/login/particles.min.js"></script>
+		<script src="sa-frame/login/app.js"></script>
+		<script src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"></script>
+		<script src="https://www.layuicdn.com/layer-v3.1.1/layer.js"></script>
+		<script src="static/sa.js"></script>
+		<script type="text/javascript">
+
+			// 你所有要改的代码全在这里   ↓↓↓↓↓
+
+			// 所有参考属性
+			var page_title = '智慧防疫 后台 - 登录';		// 页面标题
+			var key = 'sa';							// 默认的账号
+			var password = '123456';				// 默认的password
+			var logo = 'sa-frame/admin-logo.png';	// logo地址,为空字符串则不显示
+
+			// 点击登录按钮
+			document.querySelector(".login-button").onclick = function() {
+				// 1、取值
+				var p = {
+					key: $('[name=key]').val(),
+					password: $('[name=password]').val(),
+					remember: $('#remember').is(':checked')
+				}
+				// 2、判断
+				if(p.key == '' || p.password == ''){
+					return layer.msg('请输入账号密码');
+				}
+				// 3、请求后台
+				sa.ajax('/AccAdmin/doLogin', p, function(res){
+					// 写入token
+					if(res.data.tokenInfo) {
+						if(p.remember) {
+							sessionStorage.removeItem('satoken');
+							localStorage.setItem('satoken', res.data.tokenInfo.tokenValue);
+						} else {
+							localStorage.removeItem('satoken');
+							sessionStorage.setItem('satoken', res.data.tokenInfo.tokenValue);
+						}
+					}
+					// 写入账号
+					sa.$sys.setCurrUser(res.data.admin);
+					// 写入权限码
+					sa.setAuth(res.data.perList);
+					// 写入全局配置
+					sa.$sys.setAppCfg(res.data.appCfg);
+
+					// 打个招呼,进入 index.html
+					sa.msg('登录成功,欢迎你:' + p.key);
+					setTimeout(function () {
+						if(parent == window){
+							location.href = "index.html";
+						}else{
+							sa.closeCurrIframe();
+							parent.location.reload();
+						}
+					}, 500);
+				})
+			}
+			// 你所有要改的代码全在这里   ↑↑↑↑↑
+
+		</script>
+		<script type="text/javascript">
+			// 替换属性
+			$('.page-title').html(page_title);
+			$('title').html(page_title);
+			$('[name=key]').val(key);
+			$('[name=password]').val(password);
+			if(logo != null && logo != '') {
+				$('.logo-img').attr('src', logo);
+				$('.logo-img').show();
+			}
+			// 绑定回车事件
+			$('[name=password]').bind('keypress', function(event){
+				if(event.keyCode == "13") {
+					$('.login-button').click();
+				}
+			});
+			// 去掉透明
+			setTimeout(function() {
+				// document.querySelector('.login').style.backgroundColor = 'rgba(256,256,256,1)';
+				document.querySelector('.login').style.opacity = '1';
+			}, 0)
+
+			console.log('本页面参考于jq22,原作者:http://www.jq22.com/jquery-info20074');
+		</script>
+	</body>
+</html>

+ 21 - 0
main.html

@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<meta charset="UTF-8">
+		<title></title>
+		<link rel="stylesheet" href="static/sa.css" />
+		<style type="text/css">
+			html{background-color: #EEE;}
+		</style>
+		<script type="text/javascript">
+			location.href="sa-view-sp/console/console-main.html";
+		</script>
+	</head>
+	<body>
+		<div style="width: 100vw; height: 100vh; display: flex; justify-content: center; align-items: center; color: #000;">
+			<div style="text-align: center;">
+				<h1>欢迎使用 SA-后台管理 </h1>
+			</div>
+		</div>
+	</body>
+</html>

BIN
sa-frame/admin-logo.png


+ 252 - 0
sa-frame/com/sa-info.vue

@@ -0,0 +1,252 @@
+<template>
+	<!-- 自定义slot -->
+	<div class="c-item" :class="{br: br}" v-if="$slots.default">
+		<label class="c-label" v-if="name && name.length > 0">{{name}}:</label> 
+		<span v-else-if="name === undefined"></span> 
+		<label class="c-label" v-else></label> 
+		<span v-else></span> 
+		<slot></slot>
+	</div>
+	<!-- 普通信息 -->
+	<div class="c-item" :class="{br: br}" v-else-if="type == 'text'">
+		<label class="c-label">{{name}}:</label> 
+		<span>{{value}}</span>
+		<span v-if="sa.isNull(value)">无</span>
+	</div>
+	<!-- num -->
+	<div class="c-item" :class="{br: br}" v-else-if="type == 'num'">
+		<label class="c-label">{{name}}:</label> 
+		<span class="tc-num">{{value}}</span>
+		<span v-if="sa.isNull(value)">无</span>
+	</div>
+	<!-- textarea -->
+	<div class="c-item" :class="{br: br}" v-else-if="type == 'textarea'">
+		<label class="c-label">{{name}}:</label> 
+		<span class="c-item-mline" v-if="sa.isNull(value) == false">{{value}}</span>
+		<span v-if="sa.isNull(value)">无</span>
+	</div>
+	<!-- text-list -->
+	<div class="c-item" :class="{br: br}" v-else-if="type == 'text-list'">
+		<label class="c-label">{{name}}:</label> 
+		<div class="c-item-mline" v-if="value_arr.length > 0">
+			<div v-for="item in value_arr">
+				<p class="s-text-list-p">{{item}}</p>
+			</div>
+		</div>
+		<span v-else>无</span>
+	</div>
+	<!-- img -->
+	<div class="c-item" :class="{br: br}" v-else-if="type == 'img'">
+		<label class="c-label">{{name}}:</label> 
+		<img :src="value" class="info-img" @click="sa.showImage(value, '400px', '400px')" v-if="value">
+		<span v-else>无</span>
+	</div>
+	<!-- audio、video、file -->
+	<div class="c-item" :class="{br: br}" v-else-if="type == 'audio' || type == 'video' || type == 'file'">
+		<label class="c-label">{{name}}:</label> 
+		<el-link type="info" :href="value" target="_blank" v-if="!sa.isNull(value)">{{value}}</el-link>
+		<span v-else>无</span>
+	</div>
+	<!-- img-list -形如:url1,url2,url3 -->
+	<div class="c-item" :class="{br: br}" v-else-if="type == 'img-list'">
+		<label class="c-label">{{name}}:</label> 
+		<div class="c-item-mline image-box image-box-info" v-if="value_arr.length > 0">
+			<div class="image-box-2" v-for="image in value_arr">
+				<img :src="image" @click="sa.showImage(image, '500px', '400px')" />
+			</div>
+		</div>
+		<span v-else>无</span>
+	</div>
+	<!-- audio-list、video-list、file-list、img-video-list -->
+	<div class="c-item" :class="{br: br}" v-else-if="type == 'audio-list' || type == 'video-list' || type == 'file-list' || type == 'img-video-list'">
+		<label class="c-label">{{name}}:</label> 
+		<div class="c-item-mline" v-if="value_arr.length > 0">
+			<div v-for="item in value_arr">
+				<el-link type="info" :href="item" target="_blank">{{item}}</el-link>
+			</div>
+		</div>
+		<span v-else>无</span>
+	</div>
+	<!-- 钱 money (单位 元) -->
+	<div class="c-item" :class="{br: br}" v-else-if="type == 'money'">
+		<label class="c-label">{{name}}:</label> 
+		<b class="c-price">¥{{value}}</b>
+	</div>
+	<!-- 钱 price-f (单位 分) -->
+	<div class="c-item" :class="{br: br}" v-else-if="type == 'money-f'">
+		<label class="c-label">{{name}}:</label> 
+		<b class="c-price">¥{{value / 100}}</b>
+	</div>
+	<!-- 富文本 richtext f -->
+	<div class="c-item" :class="{br: br}" v-else-if="type == 'richtext' || type == 'f'">
+		<label class="c-label">{{name}}:</label> 
+		<div class="editor-box content-box-info c-item-mline">
+			<div v-html="value"></div>
+		</div>
+	</div>
+	<!-- 显示枚举 j、num -->
+	<div class="c-item" :class="{br: br}" v-else-if="type == 'enum' || type == 'j' || type == 'switch'">
+		<label class="c-label">{{name}}:</label> 
+		<span v-for="j in jvList" :key="j.key">
+			<b :style="{color: j.color || '#303236'}" v-if="value == j.key">{{j.value}}</b>
+		</span>
+	</div>
+	<!-- link -->
+	<div class="c-item" :class="{br: br}" v-else-if="type == 'link'">
+		<label class="c-label">{{name}}:</label> 
+		<!-- <span class="c-item-mline">{{value}}</span> -->
+		<el-link type="primary" :href="value" target="_blank" v-if="!sa.isNull(value)">{{value}}</el-link>
+		<span v-else>无</span>
+	</div>
+	
+	<!-- 日期 -->
+	<div class="c-item" :class="{br: br}" v-else-if="type == 'date'">
+		<label class="c-label">{{name}}:</label> 
+		<span class="tc-date">{{sa.forDate(value, 1)}}</span>
+		<span v-if="sa.isNull(value)">无</span>
+	</div>
+	<!-- 日期时间 -->
+	<div class="c-item" :class="{br: br}" v-else-if="type == 'datetime'">
+		<label class="c-label">{{name}}:</label> 
+		<span class="tc-date">{{sa.forDate(value, 2)}}</span>
+		<span v-if="sa.isNull(value)">无</span>
+	</div>
+	<!-- 时间 -->
+	<div class="c-item" :class="{br: br}" v-else-if="type == 'time'">
+		<label class="c-label">{{name}}:</label> 
+		<span class="tc-date">{{value}}</span>
+		<span v-if="sa.isNull(value)">无</span>
+	</div>
+	
+	<!-- 评分组件 -->
+	<div class="c-item" :class="{br: br}" v-else-if="type == 'rate'">
+		<label class="c-label">{{name}}:</label> 
+		<div style="display: inline-block;">
+			<el-rate :value="value <= 5 ? value : 5" show-text disabled></el-rate>
+			<span v-if="sa.isNull(value)">无</span>
+		</div>
+	</div>
+	
+	
+</template>
+
+<script>
+	module.exports = {
+		// props: ['name', 'value'],
+		props: {
+			// text、num、
+			type: {
+				default: 'text'
+			},
+			// label提示文字
+			name: {
+				type: String
+			},
+			// 绑定的值 
+			value: {},
+			// 提示文字
+			placeholder: {},
+			// 是否禁用
+			disabled: {},
+			// 是否换行 
+			br: {
+				type: Boolean,
+				default: false
+			},
+			// type=menu时,值列表    -- 形如:{1: '正常[green]', 2: '禁用[red]'}  
+			jv: {default: ''},
+			// type=menu时,具体的枚举类型 -- 1=单选框,2=单选文字,3=单选按钮,4=单选下拉框
+			jtype: {default: 1},
+			// 级联选择的数据列表
+			options: {},
+			// 快捷按钮显示列表,形如:add,get,delete,export,reset 
+			show: {},	
+			// 分页信息 
+			curr: {}, size: {}, total: {}, sizes: {}, 
+			// 空值时显示的文字
+			not: {default: '无'}
+			
+		},
+		data() {
+			return {
+				// 日期范围时的值 
+				dateRangeValue: [],
+				// 快捷按钮显示按钮列表 
+				showBtns: [],
+				// type=menu时,解析的值列表    -- 形如:[{key: 1, value: '正常', color: 'green'}]
+				jvList: [],
+				// type = img-list 时,解析的元素List
+				value_arr: []
+			}
+		},
+		watch: {
+			// 监听一些类型的 value 变动 
+			value: function(oldValue, newValue) {
+				// img-list、audio-list、video-list、file-list、img-video-list
+				if(this.type == 'img-list' || this.type == 'audio-list' || this.type == 'video-list' || this.type == 'file-list'
+					|| this.type == 'img-video-list' || this.type == 'text-list') {
+					this.value_to_arr(this.value); 
+				}
+			},
+		},
+		methods: {
+			// 解析枚举 
+			parseJv: function() {
+				for(let key in this.jv) {
+					let value = this.jv[key];
+					let color = '';
+					// 
+					if(value.indexOf('[') != -1 && value.endsWith(']')) {
+						let index = value.indexOf('[');
+						color = value.substring(index + 1, value.length - 1);
+						value = value.substring(0, index);
+						// console.log(color + ' --- ' + value);
+					}
+					// 
+					if(isNaN(key) == false) {
+						key = parseInt(key);
+					}
+					// 
+					this.jvList.push({
+						key: key,
+						value: value,
+						color: color
+					})
+				}
+			},
+			// 解析 value 为 value_arr
+			value_to_arr: function(value) {
+				this.value_arr = sa.isNull(value) ? [] : value.split(',');		
+				for (var i = 0; i < this.value_arr.length; i++) {
+					if(this.value_arr[i] == '' || this.value_arr[i].trim() == '') {
+						sa.arrayDelete(this.value_arr, this.value_arr[i]);
+						i--;
+					}
+				}
+			},
+			
+		},
+		created() {
+			// console.log(this.br);
+			if(this.type == 'fast-btn') {
+				this.showBtns = this.show.split(',');
+				for (var i = 0; i < this.showBtns.length; i++) {
+					this.showBtns[i] = this.showBtns[i].trim();
+				}
+			}
+			// 如果是枚举
+			if(this.type == 'enum' || this.type == 'j' || this.type == 'switch') {	
+				this.parseJv();
+				// console.log(this.jvList);
+			}
+			// 如果是 img-list 等 
+			if(this.type == 'img-list' || this.type == 'audio-list' || this.type == 'video-list' || this.type == 'file-list'
+				|| this.type == 'img-video-list' || this.type == 'text-list') {
+				this.value_to_arr(this.value);
+			}
+		}
+	}
+</script>
+
+<style scoped>
+</style>

+ 455 - 0
sa-frame/com/sa-item.vue

@@ -0,0 +1,455 @@
+<template>
+	<!-- 自定义slot -->
+	<div class="c-item" :class="{br: br}" v-if="$slots.default && type != 'fast-btn'">
+		<label class="c-label" v-if="name && name.length > 0">{{name}}:</label> 
+		<span v-else-if="name === undefined"></span> 
+		<label class="c-label" v-else></label> 
+		<span v-else></span> 
+		<slot></slot>
+	</div>
+	<!-- 普通input -->
+	<div class="c-item" :class="{br: br}" v-else-if="type == 'text' || type == 'link'">
+		<label class="c-label"><span v-if="need" style="color: red;">*</span>{{name}}:</label> 
+		<el-input type="text" :value="value" @input="onInput" :placeholder="placeholder?placeholder:name" :disabled="disabled"></el-input>
+	</div>
+	<!-- 数字input -->
+	<div class="c-item" :class="{br: br}" v-else-if="type == 'num'">
+		<label class="c-label"><span v-if="need" style="color: red;">*</span>{{name}}:</label> 
+		<el-input type="number" :value="value" @input="onInput" :placeholder="placeholder?placeholder:name" :disabled="disabled"></el-input>
+	</div>
+	<!-- 密码input -->
+	<div class="c-item" :class="{br: br}" v-else-if="type == 'password'">
+		<label class="c-label"><span v-if="need" style="color: red;">*</span>{{name}}:</label> 
+		<el-input type="password" :value="value" @input="onInput" :placeholder="placeholder" :disabled="disabled"></el-input>
+	</div>
+	<!-- 多行文本域 -->
+	<div class="c-item" :class="{br: br}" v-else-if="type == 'textarea'">
+		<label class="c-label">{{name}}:</label> 
+		<div style="display: inline-block;">
+			<el-input type="textarea" :autosize="{ minRows: 3, maxRows: 10}" :value="value" @input="onInput" :placeholder="placeholder" :disabled="disabled"></el-input>
+		</div>
+	</div>
+	<!-- 普通input - 列表 -->
+	<div class="c-item" :class="{br: br}" v-else-if="type == 'text-list'">
+		<label class="c-label">{{name}}:</label> 
+		<div class="c-item-mline">
+			<div v-for="item in value_arr">
+				<el-input v-model="item.value" @input="value_arr_change"></el-input>
+				<el-link type="danger" class="del-rr" @click="value_arr_delete(item)">
+					<i class="el-icon-close"></i>
+					<small style="vertical-align: top;">删除</small>
+				</el-link>
+			</div>
+			<el-link type="primary" @click="value_arr_push({value: ''})">[ + 添加 ]</el-link>
+			<span class="c-remark" style="vertical-align: -5%;" v-if="remark">{{remark}}</span>
+		</div>
+	</div>
+	<!-- 钱 money (单位 元) -->
+	<div class="c-item" :class="{br: br}" v-else-if="type == 'money'">
+		<label class="c-label">{{name}}:</label> 
+		<el-input type="text" :value="value" @input="onInput" :placeholder="placeholder" :disabled="disabled"></el-input>
+		<span>元</span>
+	</div>
+	<!-- 钱 price-f (单位 分) -->
+	<div class="c-item" :class="{br: br}" v-else-if="type == 'money-f'">
+		<label class="c-label">{{name}}:</label> 
+		<el-input type="text" v-model="valueReal" @input="$emit('input', $event * 100)" :placeholder="placeholder" :disabled="disabled"></el-input>
+		<span>元</span>
+	</div>
+	<!-- img -->
+	<div class="c-item" :class="{br: br}" v-else-if="type == 'img'">
+		<label class="c-label">{{name}}:</label> 
+		<img :src="value" class="info-img" @click="sa.showImage(value, '400px', '400px')" v-if="!sa.isNull(value)">
+		<el-link type="primary" @click="sa.uploadImage(src => {$emit('input', src); sa.ok2('上传成功');})">上传</el-link>
+	</div>
+	<!-- audio -->
+	<div class="c-item" :class="{br: br}" v-else-if="type == 'audio'">
+		<label class="c-label">{{name}}:</label> 
+		<el-link type="info" :href="value" target="_blank" v-if="!sa.isNull(value)">{{value}}</el-link>
+		<el-link type="primary" @click="sa.uploadAudio(src => {$emit('input', src); sa.ok2('上传成功');})">上传</el-link>
+	</div>
+	<!-- video -->
+	<div class="c-item" :class="{br: br}" v-else-if="type == 'video'">
+		<label class="c-label">{{name}}:</label> 
+		<el-link type="info" :href="value" target="_blank" v-if="!sa.isNull(value)">{{value}}</el-link>
+		<el-link type="primary" @click="sa.uploadVideo(src => {$emit('input', src); sa.ok2('上传成功');})">上传</el-link>
+	</div>
+	<!-- file -->
+	<div class="c-item" :class="{br: br}" v-else-if="type == 'file'">
+		<label class="c-label">{{name}}:</label> 
+		<el-link type="info" :href="value" target="_blank" v-if="!sa.isNull(value)">{{value}}</el-link>
+		<el-link type="primary" @click="sa.uploadFile(src => {$emit('input', src); sa.ok2('上传成功');})">上传</el-link>
+	</div>
+	<!-- img-list -->
+	<div class="c-item" :class="{br: br}" v-else-if="type == 'img-list'">
+		<label class="c-label">{{name}}:</label> 
+		<div class="c-item-mline image-box">
+			<div class="image-box-2" v-for="item in value_arr">
+				<img :src="item.value" @click="sa.showImage(item.value, '500px', '400px')" />
+				<p>
+					<el-link @click="value_arr_delete(item)" style="color: #999;">
+						<i class="el-icon-close" style="position: relative; top: 2px;"></i>
+						移除 
+					</el-link>
+				</p>
+			</div>
+			<!-- 上传图集 -->
+			<div class="image-box-2 up_img" @click="sa.uploadImageList(src => value_arr_push({value: src}))">
+				<img src="../../static/img/up-icon.png">
+			</div>
+		</div>
+	</div>
+	<!-- audio-list、video-list、file-list、img-video-list -->
+	<div class="c-item" :class="{br: br}" v-else-if="type == 'audio-list' || type == 'video-list' || type == 'file-list' || type == 'img-video-list'">
+		<label class="c-label">{{name}}:</label> 
+		<div class="c-item-mline">
+			<div v-for="item in value_arr">
+				<el-link type="info" :href="item.value" target="_blank">{{item.value}}</el-link>
+				<el-link type="danger" class="del-rr" @click="value_arr_delete(item)">
+					<i class="el-icon-close"></i>
+					<small style="vertical-align: top;">删除</small>
+				</el-link>
+			</div>
+			<el-link type="primary" @click="sa.uploadAudioList(src => value_arr_push({value: src}))" v-if="type == 'audio-list'">上传</el-link>
+			<el-link type="primary" @click="sa.uploadVideoList(src => value_arr_push({value: src}))" v-if="type == 'video-list'">上传</el-link>
+			<el-link type="primary" @click="sa.uploadFileList(src => value_arr_push({value: src}))" v-if="type == 'file-list'">上传</el-link>
+			<el-link type="primary" @click="sa.uploadImageList(src => value_arr_push({value: src}))" v-if="type == 'img-video-list'">上传图片</el-link>
+			<el-link type="primary" @click="sa.uploadVideoList(src => value_arr_push({value: src}))" v-if="type == 'img-video-list'" style="margin-left: 7px;">上传视频</el-link>
+		</div>
+	</div>
+	<!-- 富文本 richtext f -->
+	<div class="c-item" style="margin-top: 10px;" :class="{br: br}" v-else-if="type == 'richtext' || type == 'f'">
+		<label class="c-label">{{name}}:</label> 
+		<div class="editor-box c-item-mline">
+			<div :id="'editor-' + editor_id"></div>
+		</div>
+		<div style="clear: both;"></div>
+	</div>
+	<!-- enum 枚举 -->
+	<div class="c-item" :class="{br: br}" v-else-if="type == 'enum' || type == 'j' || type == 'switch'">
+		<label class="c-label">{{name}}:</label> 
+		<el-radio-group v-if="jtype == 1 || jtype == 2" :class="{'s-radio-text': jtype == 2}" :value="value" @input="onInput">
+			<el-radio label="" v-if="def">{{def}}</el-radio>
+			<el-radio v-for="j in jvList" :key="j.key" :label="j.key">{{j.value}}</el-radio>
+		</el-radio-group>
+		<el-radio-group v-if="jtype == 3" :value="value" @input="onInput">
+			<el-radio-button label="" v-if="def">{{def}}</el-radio-button>
+			<el-radio-button v-for="j in jvList" :key="j.key" :label="j.key">{{j.value}}</el-radio-button>
+		</el-radio-group>
+		<el-select v-if="jtype == 4" :value="value" @input="onInput">
+			<el-option label="" v-if="def" :value="def"></el-option>
+			<el-option v-for="j in jvList" :key="j.key" :label="j.value" :value="j.key"></el-option>
+		</el-select>
+	</div>
+	<!-- 日期选择器 -->
+	<div class="c-item" :class="{br: br}" v-else-if="type == 'date'">
+		<label class="c-label">{{name}}:</label> 
+		<el-date-picker type="date" value-format="yyyy-MM-dd" :value="value" @input="onInput" :placeholder="placeholder" :disabled="disabled"></el-date-picker>
+	</div>
+	<!-- 日期时间选择器 -->
+	<div class="c-item" :class="{br: br}" v-else-if="type == 'datetime'">
+		<label class="c-label"><span style="color: red;" v-if="need">*</span>{{name}}:</label> 
+		<el-date-picker type="datetime" value-format="yyyy-MM-dd HH:mm:ss" :value="value" @input="onInput" :placeholder="placeholder?placeholder:name" :disabled="disabled"></el-date-picker>
+	</div>
+	<!-- 时间选择器 -->
+	<div class="c-item" :class="{br: br}" v-else-if="type == 'time'">
+		<label class="c-label">{{name}}:</label> 
+		<el-time-picker value-format="HH:mm:ss" :value="value" @input="onInput" :placeholder="placeholder" :disabled="disabled"></el-time-picker>
+	</div>
+	<!-- 日期范围选择 -->
+	<div class="c-item" :class="{br: br}" v-else-if="type == 'date-range'">
+		<label class="c-label">{{name}}:</label> 
+		<el-date-picker
+			type="daterange"
+			range-separator="至"
+			start-placeholder="开始日期"
+			end-placeholder="结束日期"
+			value-format="yyyy-MM-dd"
+			:value="dateRangeValue" 
+			@input="dateRangeOnChange"
+			:disabled="disabled"
+			>
+		</el-date-picker>
+	</div>
+	<!-- 滑块 -->
+	<div class="c-item" :class="{br: br}" v-else-if="type == 'slider'">
+		<label class="c-label">{{name}}:</label> 
+		<div style="display: inline-block; height: 0px; vertical-align: top; width: 250px;">
+			<el-slider :value="value" @input="onInput" style="position: relative; top: -5px;" :disabled="disabled"></el-slider>
+		</div>
+	</div>
+	<!-- 级联输入 -->
+	<div class="c-item" :class="{br: br}" v-else-if="type == 'cascader'">
+		<label class="c-label">{{name}}:</label> 
+		<el-cascader :value="value" @input="onInput" :options="options" :props="{expandTrigger: 'hover'}" :placeholder="placeholder" :disabled="disabled"></el-cascader>
+	</div>
+	<!-- 颜色输入 -->
+	<div class="c-item" :class="{br: br}" style="height: 0px;" v-else-if="type == 'color'">
+		<label class="c-label">{{name}}:</label> 
+		<el-color-picker :value="value" @input="onInput" :disabled="disabled"></el-color-picker>
+		<span class="c-remark" style="vertical-align: top;">{{value}}</span>
+	</div>
+	<!-- 评分组件 -->
+	<div class="c-item" :class="{br: br}" v-else-if="type == 'rate'">
+		<label class="c-label">{{name}}:</label> 
+		<div style="display: inline-block;">
+			<el-rate :value="value" @input="onInput" show-text :disabled="disabled"></el-rate>
+		</div>
+	</div>
+	<!-- 快捷增删改查按钮 -->
+	<div class="fast-btn" v-else-if="type == 'fast-btn'">
+		<el-button type="primary" icon="el-icon-plus" @click="$parent.add()" v-if="showBtns.indexOf('add') != -1">新增</el-button>
+		<el-button type="success" icon="el-icon-view" @click="$parent.getBySelect()" v-if="showBtns.indexOf('get') != -1">查看</el-button>
+		<el-button type="danger" icon="el-icon-delete" @click="$parent.deleteByIds()" v-if="showBtns.indexOf('delete') != -1">删除</el-button>
+		<el-button type="warning" icon="el-icon-download" @click="sa.exportExcel()" v-if="showBtns.indexOf('export') != -1">导出</el-button>
+		<el-button type="info"  icon="el-icon-refresh"  @click="sa.f5()" v-if="showBtns.indexOf('reset') != -1">重置</el-button>
+		<slot></slot>
+	</div>
+	<!-- 分页组件 -->
+	<div class="page-box" v-else-if="type == 'page'">
+		<el-pagination background
+			layout="total, prev, pager, next, sizes, jumper" 
+			:current-page.sync="curr" 
+			:page-size.sync="size" 
+			:total="total" 
+			:page-sizes="sizes || [1, 10, 20, 30, 40, 50, 100]" 
+			@current-change="changePage()" 
+			@size-change="changePage()">
+		</el-pagination>
+	</div>
+</template>
+
+<script>
+	module.exports = {
+		// props: ['name', 'value'],
+		props: {
+			// text、num、
+			type: {
+				default: 'text'
+			},
+			need:{
+				type: Boolean,
+				default:false
+			},
+			// label提示文字
+			name: {
+				type: String
+			},
+			// 绑定的值 
+			value: {},
+			// 备注
+			remark: '',
+			// 提示文字
+			placeholder: {},
+			// 是否禁用
+			disabled: {},
+			// 是否换行 
+			br: {
+				type: Boolean,
+				default: false
+			},
+			// 日期范围时的选择字段,调用方需要加 .sync 修饰符 
+			start: {}, end: {},
+			// type=menu时,值列表    -- 形如:{1: '正常[green]', 2: '禁用[red]'}  
+			jv: {default: ''},
+			// type=menu时,具体的枚举类型 -- 1=单选框,2=单选文字,3=单选按钮,4=单选下拉框
+			jtype: {default: 1},
+			// type=menu时,增加的默认项文字 
+			def: {},
+			// 级联选择的数据列表
+			options: {},
+			// 快捷按钮显示列表,形如:add,get,delete,export,reset 
+			show: {},	
+			// 分页信息 
+			curr: {}, size: {}, total: {}, sizes: {}
+			
+		},
+		data() {
+			return {
+				// 日期范围时的值 
+				dateRangeValue: [],
+				// 快捷按钮显示按钮列表 
+				showBtns: [],
+				// type=menu时,解析的值列表    -- 形如:[{key: 1, value: '正常', color: 'green'}]
+				jvList: [],
+				// type = img-list 时,解析的元素List
+				value_arr: [],
+				// 富文本编辑器id
+				editor_id: '',
+				// 富文本编辑器对象 
+				editor: null,
+				// money-f 的底层字段
+				valueReal: ''
+			}
+		},
+		watch: {
+			// 监听一些类型的 value 变动 
+			value: function(oldValue, newValue) {
+				// img-list、audio-list、video-list、file-list、img-video-list
+				if(this.type == 'img-list' || this.type == 'audio-list' || this.type == 'video-list' || this.type == 'file-list'
+					|| this.type == 'img-video-list') {
+					this.value_to_arr(this.value); 
+				}
+				if(this.type == 'text-list' && this.value_arr.length == 0) {
+					this.value_to_arr(this.value); 
+				}
+				// 如果是富文本
+				// if(this.type == 'richtext' || this.type == 'f') {
+				// 	if(this.editor) {
+				// 		// this.editor.txt.html(newValue);
+				// 		$('#editor-' + this.editor_id + " .w-e-text").html(newValue);
+				// 	}
+				// }
+			},
+		},
+		methods: {
+			// input值发生变化时触发
+			onInput: function($event) {
+				this.$emit('input', $event);
+			},
+			// 日期范围选择时触发 
+			dateRangeOnChange: function(value) {
+				console.log(value);
+				this.dateRangeValue = value;
+				this.start = value[0];
+				this.end = value[1];
+				this.$emit('update:start',  value[0]);
+				this.$emit('update:end',  value[1]);
+			},
+			// 刷新分页 
+			changePage: function() {
+				this.$emit('update:curr', this.curr);
+				this.$emit('update:size', this.size);
+				this.$emit('change');
+			},
+			// 解析枚举 
+			parseJv: function() {
+				for(let key in this.jv) {
+					let value = this.jv[key];
+					let color = '';
+					// 
+					if(value.indexOf('[') != -1 && value.endsWith(']')) {
+						let index = value.indexOf('[');
+						color = value.substring(index + 1, value.length - 1);
+						value = value.substring(0, index);
+						// console.log(color + ' --- ' + value);
+					}
+					// 
+					if(isNaN(key) == false) {
+						key = parseInt(key);
+					}
+					// 
+					this.jvList.push({
+						key: key,
+						value: value,
+						color: color
+					})
+				}
+			},
+			// 解析 value 为 value_arr
+			value_to_arr: function(value) {
+				let arr = sa.isNull(value) ? [] : value.split(',');		
+				let value_arr = [];		
+				for (var i = 0; i < arr.length; i++) {
+					if(arr[i] != '' && arr[i].trim() != '') {
+						value_arr.push({value: arr[i]});
+					}
+				}
+				this.value_arr = value_arr;
+			},
+			// value_arr 数组增加值
+			value_arr_push: function(item) {
+				this.value_arr.push(item);
+				// this.value = this.value_arr.join(',');	
+				this.$emit('input', sa.getArrayField(this.value_arr, 'value').join(','));
+			},
+			// value_arr 数组删除值 
+			value_arr_delete: function(item) {
+				sa.arrayDelete(this.value_arr, item);
+				// this.value = this.value_arr.join(',');	
+				this.$emit('input', sa.getArrayField(this.value_arr, 'value').join(','));
+			},
+			// value_arr 更改值时触发 
+			value_arr_change: function() {
+				this.$emit('input', sa.getArrayField(this.value_arr, 'value').join(','));
+			},
+			// 创建富文本编辑器
+			create_editor: function(content) {
+				var E = window.wangEditor;
+				var editor = new E('#editor-' + this.editor_id);
+			
+				editor.config.menus = [
+					'head', 'fontSize', 'fontName', 'italic', 'underline', 'strikeThrough', 'foreColor', 'backColor', 'link', 'list',
+					'justify', 'quote', 'emoticon', 'image', 'table', 'code', 'undo', 'redo' // 重复
+				]
+				editor.config.debug = true; // debug模式
+				// editor.config.uploadFileName = 'file'; // 图片流name
+				editor.config.withCredentials = true; // 跨域携带cookie
+				editor.config.uploadImgMaxSize = 100 * 1024 * 1024;	// 图片大小最大100M
+				// editor.config.uploadImgShowBase64 = true   	// 使用 base64 保存图片
+				// 监听内容变动
+				editor.config.onchange = function (newHtml) {
+					// console.log("change 之后最新的 html", newHtml);
+					this.$emit('input', newHtml);
+				}.bind(this);
+				// 重写上传图片的函数到OSS 
+				editor.config.customUploadImg = function(files, insert) {
+					var file = files[0]; // 文件对象 
+					startUploadImage2(file, function(src) {
+						insert(src);
+						sa.msg('上传成功');
+					});
+				}
+				editor.create(); // 创建
+				editor.txt.html(content);	// 为编辑器赋值
+				this.editor = editor;
+				// setTimeout(function() {
+				// 	$('.editor-box').height($('.editor-box').height());
+				// })
+			},
+			// 为编辑器赋值 
+			editorSet: function(value) {
+				this.editor.txt.html(value);
+			},
+			valueSet(valueReal) {
+				this.valueReal = valueReal;
+			}
+		},
+		created() {
+			// console.log(this.br);
+			if(this.type == 'fast-btn') {
+				this.showBtns = this.show.split(',');
+				for (var i = 0; i < this.showBtns.length; i++) {
+					this.showBtns[i] = this.showBtns[i].trim();
+				}
+			}
+			// 如果是枚举
+			if(this.type == 'enum' || this.type == 'j' || this.type == 'switch') {	
+				this.parseJv();
+			}
+			// 如果是 img-list 等 
+			if(this.type == 'img-list' || this.type == 'audio-list' || this.type == 'video-list' || this.type == 'file-list' 
+				|| this.type == 'img-video-list' || this.type == 'text-list') {
+				this.value_to_arr(this.value);
+			}
+			// 如果是富文本
+			if(this.type == 'richtext' || this.type == 'f') {
+				this.editor_id = sa.randomString(32);
+				this.$nextTick(function() {
+					this.create_editor(this.value);
+				})
+			}
+			// 如果是 money-f 
+			if(this.type == 'money-f') {
+				if(this.value) {
+					this.valueReal = this.value / 100;
+				}
+			}
+			
+			
+		}
+	}
+</script>
+
+<style scoped>
+</style>

+ 261 - 0
sa-frame/com/sa-td.vue

@@ -0,0 +1,261 @@
+<template>
+	<!-- 自定义slot -->
+	<el-table-column v-if="$slots.default || $scopedSlots.default" :label="name" :width="width" :min-width="minWidth">
+		<template slot-scope="s">
+			<slot :row="s.row" :index="s.index"></slot>
+		</template>
+	</el-table-column>
+	<!-- selection框 -->
+	<el-table-column v-else-if="type == 'selection'" type="selection" :width="width || '45px'" :min-width="minWidth"></el-table-column>
+	<!-- index -->
+	<el-table-column v-else-if="type == 'index'" type="index" :label="name" :width="width || '80px'" :min-width="minWidth"></el-table-column>
+	<!-- 普通td -->
+	<el-table-column v-else-if="type == 'text'" :label="name" :width="width" :min-width="minWidth">
+		<template slot-scope="s">
+			<span v-if="s.row[prop]">{{s.row[prop]}}</span>
+			<span v-else>{{not}}</span>
+		</template>
+	</el-table-column>
+	<!-- num 数字 -->
+	<el-table-column v-else-if="type == 'num'" :label="name" :width="width" :min-width="minWidth" class-name="tc-num">
+		<template slot-scope="s">
+			<span v-if="s.row[prop]">{{s.row[prop]}}</span>
+			<span v-else>{{not}}</span>
+		</template>
+	</el-table-column>
+	<!-- icon -->
+	<el-table-column v-else-if="type == 'icon'" :label="name" :width="width" :min-width="minWidth">
+		<template slot-scope="s">
+			<i v-if="s.row[prop]" :class="s.row[prop]" style="font-size: 1.3em;"></i>
+			<span v-else>{{not}}</span>
+		</template>
+	</el-table-column>
+	<!-- text-list -->
+	<el-table-column v-else-if="type == 'text-list'" :label="name" :width="width" :min-width="minWidth || '120px'">
+		<template slot-scope="s">
+			<div v-if="s.row[prop]">
+				<p v-for="item in value_to_arr(s.row[prop])" class="s-text-list-p" style="white-space: nowrap; word-break:keep-all;">
+					{{item}}
+				</p>
+			</div>
+			<div v-else>{{not}}</div>
+		</template>
+	</el-table-column>
+	<!-- img -->
+	<el-table-column v-else-if="type == 'img'" :label="name" :width="width" :min-width="minWidth">
+		<template slot-scope="s">
+			<img v-if="s.row[prop]" :src="s.row[prop]" class="td-img" @click="sa.showImage(s.row[prop], '400px', '400px')" />
+			<span v-else>{{not}}</span>
+		</template>
+	</el-table-column>
+	<!-- audio、video、file -->
+	<el-table-column v-else-if="type == 'audio' || type == 'video' || type == 'file'" :label="name" :width="width" :min-width="minWidth">
+		<template slot-scope="s">
+			<el-link type="info" :href="s.row[prop]" target="_blank" v-if="!sa.isNull(s.row[prop])">预览</el-link>
+			<span v-else>{{not}}</span>
+		</template>
+	</el-table-column>
+	<!-- img-list -->
+	<el-table-column v-else-if="type == 'img-list'" :label="name" :width="width" :min-width="minWidth || '120px'" show-overflow-tooltip>
+		<template slot-scope="s">
+			<div @click="sa.showImageList(value_to_arr(s.row[prop]))" style="cursor: pointer;" v-if="s.row[prop]">
+				<img :src="value_to_arr(s.row[prop])[0]" class="td-img" />
+				<span style="color: #999; padding-left: 0.5em;">点击预览</span>
+			</div>
+			<div v-else>{{not}}</div>
+		</template>
+	</el-table-column>
+	<!-- xxx-list -->
+	<el-table-column v-else-if="type == 'audio-list' || type == 'video-list' || type == 'file-list' || type == 'img-video-list'" :label="name" :width="width" :min-width="minWidth">
+		<template slot-scope="s">
+			<span v-if="s.row[prop]" style="color: #666;">共 {{value_to_arr(s.row[prop]).length}} 个</span>
+			<span v-else>{{not}}</span>
+		</template>
+	</el-table-column>
+	
+	<!-- textarea -->
+	<el-table-column v-else-if="type == 'textarea'" :label="name" :width="width" :min-width="minWidth" show-overflow-tooltip>
+		<template slot-scope="s">
+			<span v-if="s.row[prop]">{{sa.maxLength(s.row[prop], 100)}}</span>
+			<span v-else>{{not}}</span>
+		</template>
+	</el-table-column>
+	<!-- richtext 富文本 -->
+	<el-table-column v-else-if="type == 'richtext' || type == 'f'" :label="name" :width="width" :min-width="minWidth" show-overflow-tooltip>
+		<template slot-scope="s">
+			<span>{{sa.maxLength(sa.text(s.row[prop]), 100)}}</span>
+		</template>
+	</el-table-column>
+	<!-- link -->
+	<el-table-column v-else-if="type == 'link'" :label="name" :width="width" :min-width="minWidth">
+		<template slot-scope="s">
+			<el-link type="primary" :href="s.row[prop]" target="_blank" v-if="!sa.isNull(s.row[prop])">{{s.row[prop]}}</el-link>
+			<div v-else>无</div>
+		</template>
+	</el-table-column>
+	<!-- link-btn -->
+	<el-table-column v-else-if="type == 'link-btn'" :label="name" :width="width" :min-width="minWidth">
+		<template slot-scope="s">
+			<el-link type="primary" @click="$emit('click', s)" v-if="!sa.isNull(s.row[prop])">{{s.row[prop]}}</el-link>
+			<div v-else>无</div>
+		</template>
+	</el-table-column>
+	
+	<!-- 钱 money (单位 元) -->
+	<el-table-column v-else-if="type == 'money'" :label="name" :width="width" :min-width="minWidth">
+		<template slot-scope="s">
+			<b class="c-price" v-if="!sa.isNull(s.row[prop])">¥{{s.row[prop]}}</b>
+			<div v-else>无</div>
+		</template>
+	</el-table-column>
+	<!-- 钱 price-f (单位 分) -->
+	<el-table-column v-else-if="type == 'money-f'" :label="name" :width="width" :min-width="minWidth">
+		<template slot-scope="s">
+			<b class="c-price" v-if="!sa.isNull(s.row[prop])">¥{{s.row[prop] / 100}}</b>
+			<div v-else>无</div>
+		</template>
+	</el-table-column>
+	<!-- 显示枚举 j、num -->
+	<el-table-column v-else-if="type == 'enum' || type == 'j'" :label="name" :width="width" :min-width="minWidth">
+		<template slot-scope="s">
+			<b v-for="j in jvList" :key="j.key" :style="{color: j.color || '#606266'}" v-if="s.row[prop] == j.key">{{j.value}}</b>
+		</template>
+	</el-table-column>
+	<!-- switch 开关 -->
+	<el-table-column v-else-if="type == 'switch'" :label="name" :width="width" :min-width="minWidth">
+		<template slot-scope="s">
+			<el-switch 
+				v-model="s.row[prop]" v-if='jvList.length >= 2' 
+				:active-value="jvList[0].key" :inactive-value="jvList[1].key" 
+				:active-color="jvList[0].color || '#409EFF'" :inactive-color="jvList[1].color || '#ccc'"
+				@change="$emit('change', s)">
+			</el-switch>
+			<span v-for="j in jvList" :key="j.key" :style="{color: '#999'}" v-if="s.row[prop] == j.key">{{j.value}}</span>
+		</template>
+	</el-table-column>
+	<!-- rate 评分 -->
+	<el-table-column v-else-if="type == 'rate'" :label="name" :width="width" :min-width="minWidth">
+		<template slot-scope="s">
+			<el-rate :value="s.row[prop] <= 5 ? s.row[prop] : 5" show-text disabled v-if="!sa.isNull(s.row[prop])"></el-rate>
+			<div v-else>无</div>
+		</template>
+	</el-table-column>
+	<!-- date 日期 -->
+	<el-table-column v-else-if="type == 'date'" :label="name" :width="width" :min-width="minWidth" class-name="tc-date">
+		<template slot-scope="s"><span>{{sa.forDate(s.row[prop]) || not}}</span></template>
+	</el-table-column>
+	<!-- datetime 日期时间 -->
+	<el-table-column v-else-if="type == 'datetime'" :label="name" :width="width" :min-width="minWidth" class-name="tc-date">
+		<template slot-scope="s"><span>{{sa.forDate(s.row[prop], 2) || not}}</span></template>
+	</el-table-column>
+	<!-- time 时间 -->
+	<el-table-column v-else-if="type == 'time'" :label="name" :width="width" :min-width="minWidth" class-name="tc-date">
+		<template slot-scope="s"><span>{{s.row[prop] || not}}</span></template>
+	</el-table-column>
+	<!-- 用户头像 -->
+	<el-table-column v-else-if="type == 'user-avatar'" :label="name" :width="width" :min-width="minWidth">
+		<template slot-scope="s">
+			<!-- 无数据的时候显示的 -->
+			<p v-if="sa.isNull(s.row[prop.split(',')[0]]) && sa.isNull(s.row[prop.split(',')[1]])">暂无</p>
+			<p v-else>
+				<img :src="s.row[prop.split(',')[1]]" class="td-img"
+					style="vertical-align: middle; margin-right: 5px;"
+					@click="sa.showImage(s.row[prop.split(',')[1]], '400px', '400px')" />
+				<b>{{s.row[prop.split(',')[0]]}}</b>
+			</p>
+		</template>
+	</el-table-column>
+</template>
+
+<script>
+	module.exports = {
+		// props: ['name', 'value'],
+		props: {
+			// text、img、
+			type: {
+				default: 'text'
+			},
+			// label提示文字
+			name: {},
+			label: {},
+			// 绑定的属性  
+			prop: {},
+			// 宽度 
+			width: {},
+			// 最小宽度
+			minWidth: {},
+			// type=menu时,值列表    -- 形如:{1: '正常[green]', 2: '禁用[red]'}  
+			jv: {default: ''},
+			// 空值时显示的文字
+			not: {default: '无'}
+		},
+		data() {
+			return {
+				// type=menu时,解析的值列表    -- 形如:[{key: 1, value: '正常', color: 'green'}]
+				jvList: [],
+				
+				// type = img-list 时,解析的元素List
+				value_arr: [],
+				
+			}
+		},
+		methods: {
+			// 解析枚举 
+			parseJv: function() {
+				for(let key in this.jv) {
+					let value = this.jv[key];
+					let color = '';
+					// 
+					if(value.indexOf('[') != -1 && value.endsWith(']')) {
+						let index = value.indexOf('[');
+						color = value.substring(index + 1, value.length - 1);
+						value = value.substring(0, index);
+						// console.log(color + ' --- ' + value);
+					}
+					// 
+					if(isNaN(key) == false) {
+						key = parseInt(key);
+					}
+					// 
+					this.jvList.push({
+						key: key,
+						value: value,
+						color: color
+					})
+				}
+			},
+			// 解析 value 为 value_arr
+			value_to_arr: function(value) {
+				var value_arr = sa.isNull(value) ? [] : value.split(',');		
+				for (var i = 0; i < value_arr.length; i++) {
+					if(value_arr[i] == '' || value_arr[i].trim() == '') {
+						sa.arrayDelete(value_arr, value_arr[i]);
+						i--;
+					}
+				}
+				// this.value_arr = value_arr;
+				// this.$nextTick(function() {
+				// 	this.value_arr = value_arr;
+				// })
+				return value_arr;
+			},
+		},
+		 mounted() {
+			// console.log(this.$slots);
+			// console.log(this.$scopedSlots.default);
+			// console.log(this.type);
+			this.name = this.name || this.label;
+			// 如果是枚举 
+			if(this.type == 'enum' || this.type == 'j' || this.type == 'switch') {
+				this.parseJv();
+			}
+			// 如果是 img-list 等 
+			// if(this.type == 'img-list' || this.type == 'audio-list' || this.type == 'video-list' || this.type == 'file-list' || this.type == 'img-video-list') {
+			// 	this.value_to_arr(this.value);
+			// }
+		}
+	}
+</script>
+
+<style scoped>
+</style>

BIN
sa-frame/index/admin-loading.gif


+ 106 - 0
sa-frame/index/admin-util.js

@@ -0,0 +1,106 @@
+// ======================== 一些工具方法 ======================== 
+
+var sa_admin_code_util = {
+	// 删除数组某个元素
+	arrayDelete: function(arr, item){
+		var index = arr.indexOf(item);
+		if (index > -1) {
+			arr.splice(index, 1);
+		}
+	},
+	
+	//执行一个函数, 解决layer拉伸或者最大化的时候,iframe高度不能自适应的问题
+	solveLayerBug: function(index) {
+		var selected = '#layui-layer' + index;
+		var height = $(selected).height();
+		var title_height = $(selected).find('.layui-layer-title').height();
+		$(selected).find('iframe').css('height', (height - title_height) + 'px');
+		// var selected = '#layui-layer' + index;
+		// var height = $(selected).height();
+		// var title_height = $(selected).find('.layui-layer-title').height();
+		// $(selected).find('iframe').css('height', (height - title_height) + 'px');
+	},
+	
+	// ======================== 菜单集合相关 ======================== 
+	
+	// 将一维平面数组转换为 Tree 菜单 (根据其指定的parent_id添加到其父菜单的childList)
+	arrayToTree: function(menu_list) {
+		for (var i = 0; i < menu_list.length; i++) {
+			var menu = menu_list[i];
+			// 添加到其指定的父菜单的childList
+			if(menu.parent_id) {
+				var parent_menu = this.findMenuById(menu_list, menu.parent_id);
+				if(parent_menu) {
+					parent_menu.childList = parent_menu.childList || [];
+					parent_menu.childList.push(menu);
+					menu_list.splice(i, 1);	// 从一维中删除 
+					i--;
+				}
+			}
+		}
+		return menu_list;
+	},
+	
+	
+	// 将 menu_list 处理一下 
+	refMenuList: function(menu_list) {
+		for (var i = 0; i < menu_list.length; i++) {
+			var menu = menu_list[i];
+			// 有子项的递归处理 
+			if(menu.childList){
+				menu.children = menu.childList;
+				this.refMenuList(menu.childList);
+			}
+		}
+		return menu_list;
+	},
+	
+	
+	
+	// 返回指定 index 的menu   
+	getMenuById: function(menuList, id) {
+		for (var i = 0; i < menuList.length; i++) {
+			var menu = menuList[i];
+			if(menu.id + '' == id + '') {
+				return menu;
+			}
+			// 如果是二级或多级 
+			if(menu.childList) {
+				var menu2 = this.getMenuById(menu.childList, id);
+				if(menu2 != null) {
+					return menu2;
+				}
+			}
+		}
+		return null;
+	},
+	
+	
+	
+	// 将 Tree 菜单 转换为 一维平面数组 
+	treeToArray: function(menu_list) {
+		var arr = [];
+		function _dg(menu_list) {
+			menu_list = menu_list || [];
+			for (var i = 0; i < menu_list.length; i++) {
+				var menu = menu_list[i];
+				arr.push(menu);
+				// 如果有子菜单 
+				if(menu.childList) {
+					_dg(menu.childList);
+				}
+			}
+		}
+		_dg(menu_list);
+		return arr;
+	},
+	
+	
+}
+
+
+
+
+
+
+

+ 65 - 0
sa-frame/index/index.css

@@ -0,0 +1,65 @@
+*{margin: 0; padding: 0; }
+html,body{height: 100%; background-color: #EEE;} 
+body{height: 100vh;background-color: #EEE;background-image: url(admin-loading.gif); background-repeat: no-repeat;background-position: 50% 50%;}
+.app{height: 100%; font-size: 16px; font-family: "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif;}
+.app{background-color: #EEE;}
+
+/* 变量 */
+body{
+	--nav-left-width: 200px;
+	--nav-left-width-fold: 64px;
+	--nav-right-1-height: 50px;
+	--nav-right-2-height: 35px;
+}
+
+.nav-left, .nav-right {position: fixed; top: 0; height: 100%;}
+
+/* 左边 */
+.nav-left{width: var(--nav-left-width); left: 0px; z-index: 200; overflow: hidden;}
+.nav-left-top{width: 100%; box-sizing: border-box; height: 85px; line-height: 85px;/* z-index: 100; */ overflow: hidden;}
+.nav-left-bottom{width: 100%; box-sizing: border-box; height: calc(100% - 85px); overflow: hidden;}
+
+/* 右边 */
+.nav-right{width: calc(100% - var(--nav-left-width)); right: 0px; z-index: 100; }
+.nav-right-1{height: var(--nav-right-1-height); line-height: var(--nav-right-1-height); z-index: 200; position: relative; border-bottom: 1px #F1F1F1 solid; box-sizing: border-box; overflow: hidden;}
+.nav-right-2{height: var(--nav-right-2-height); line-height: var(--nav-right-2-height); z-index: 200; position: relative; box-shadow: 0 2px 2px rgba(0,0,0,0.1);}
+.nav-right-3{width: 100%; height: calc(100vh - var(--nav-right-1-height) - var(--nav-right-2-height)); position: relative; overflow: hidden;}
+
+/* .fas{transition: all 0s;} */
+
+/* 所有带动画的元素 */
+.admin-logo,.nav-left,.nav-left-top,.nav-left-bottom, .nav-right/* , .nav-right-2 * */{transition: all 0.2s; }
+
+
+/* 菜单折叠 */
+.app-fold{
+	--nav-left-width: 64px;
+}
+
+/* 菜单折叠时 部分元素隐藏 */
+.app-fold .admin-title, .app-fold .menu-name, .app-fold-right .el-submenu__icon-arrow{display: none;}
+.app-fold .admin-logo{margin-left: 12px !important;}
+
+/* .nav-right-3 包裹了太多 View,不能让它参与动画,因为实在太TM卡了 */
+.nav-right-3{width: calc(100% - var(--nav-left-width)); position: fixed; transition: none;} 
+.app-fold-right .nav-right-3{width: calc(100% - 64px); left: 64px;}
+
+
+/* -------------- 其它 --------------- */
+
+/* 折叠时悬浮菜单样式,防止透明 */
+.el-menu--vertical .el-menu--popup{background-color: #FFF !important; color: red !important;}
+
+/* 最高层级 */
+.z-index-max{z-index: 2147483647;}
+
+
+/* 遮罩样式 */
+.shade-fox{position: absolute; z-index: 1000000; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5); color: #FFF; top: 0px;}
+.shade-fox{display: flex; justify-content: center; align-items: center}
+.shade-text{}
+
+/* 去除掉便签的大边框 */
+.layer-note-class .layui-layer-input{outline: 0; box-shadow: none !important; padding: 0.8em !important; font-family: 'Times New Roman', Times, serif;}
+.layer-note-class .layui-layer-input{border: 0px #ddd solid; border-bottom: 1px #ddd solid;}
+

+ 496 - 0
sa-frame/index/index.js

@@ -0,0 +1,496 @@
+// 首页 
+var homeTab = {
+	id: 'home',	// 唯一标识 
+	name: '首页',
+	url: 'main.html',	// 页面地址 
+	isNeedLoad: false,		// 标注:是否需要此刻加载
+	hideClose: true	// 隐藏关闭键 
+}
+
+// sa_admin对象 
+var sa_admin = new Vue({
+	components: {
+		"nav-logo": httpVueLoader('sa-frame/nav/nav-logo.vue'),				// logo 
+		"nav-menu-bar": httpVueLoader('sa-frame/nav/nav-menu-bar.vue'),		// 菜单栏 
+		"nav-tool-bar": httpVueLoader('sa-frame/nav/nav-tool-bar.vue'),		// 工具栏
+		"nav-tab-bar": httpVueLoader('sa-frame/nav/nav-tab-bar.vue'),		// tab栏
+		"nav-view-vessel": httpVueLoader('sa-frame/nav/nav-view-vessel.vue'),	// 视图容器 
+		"com-right-menu": httpVueLoader('sa-frame/nav/com-right-menu.vue'),		// 右键菜单 
+		"com-add-tab": httpVueLoader('sa-frame/nav/com-add-tab.vue'),			// 双击添加 tab 的弹窗 
+	},
+	el: '.app',
+	data: {
+		// ------------------------------- 配置 -------------------------------
+		title: '',		// 页面标题  -- Sa-Admin
+		logo: '',		// logo地址  -- sa-frame/admin-logo.png
+		icon: '',		// icon地址  -- sa-frame/admin-logo.png
+		version: 'v1.40.0',					// 当前版本号
+		updateTime: '2021-9-26',			// 更新日期 
+		githubUrl: 'https://github.com/click33/sa-admin',	// github地址 
+		isRemeOpen: true,		// 是否记住上一次最后打开的窗口 
+		printInfo: true,		// 是否在控制台打印信息 
+		homeTab: homeTab,	// 主页首屏 Tab 
+		menuList: [],		// 全部菜单集合 
+		showList: [],		// 显示的菜单集合(id集合) 
+		
+		plusVersion: 'v1.27.0',				//sa-plus版本
+		plusUpdateTime: '2022-02-26',		//sa-plus更新日期 
+		plusGithubUrl: 'https://github.com/click33/sa-plus',	// github地址 
+		
+		// ------------------------------- 状态 -------------------------------
+		themeV: localStorage.getItem('themeV') || '1',	// 当前 / 默认的主题 
+		isOpen: true,			// 当前是否展开菜单 (整体框架)
+		isOpenRight: true,		// 当前是否展开  (右边) (将右边盒子折叠与菜单折叠分开,这样可以减少动画的卡顿现象) 
+		activeMenuId: '0',		// 正在高亮的菜单id 
+		isDrag: false,			// 当前是否正在拖拽 tab 
+		dragTab: null,			// 当前正在拖拽的 tab 
+		tabList: [homeTab],		// 当前 Tab 集合 
+		viewList: [homeTab],		// 当前 View 集合 
+		nativeTab: homeTab,		// 当前正显示的Tab 
+		user: null	,// user信息
+		dropList: [],			// 头像处下拉列表菜单 
+	},
+	watch: {
+		// 监听title改变时, 页面title也跟着切换 
+		title: function(newValue, oldValue) {
+			document.querySelector('title').innerHTML = newValue;
+		},
+		// 监听 icon_url 网页图标 
+		icon: function(newValue, oldValue) {
+			var icon = newValue;
+			var iconTarget = document.querySelector('.admin-icon');
+			if(iconTarget) {
+				iconTarget.setAttribute('href', icon);
+			}
+		}
+	},
+	methods: {
+		
+		// ------------------- 初始化相关 -------------------- 
+		// 初始化模板, 此方法必须且只能调用一次 
+		init: function(option) {
+			
+			// 打开上次最后的一个窗口  
+			this.showTabByHash();	
+			if(this.nativeTab.id == this.homeTab.id) {
+				this.showHome();
+			}
+			
+			// 打印版本等信息
+			if(this.printInfo) {
+				this.printVesion();
+			}
+			
+			// 手动触发一下窗口变动监听
+			window.onresize();		
+			
+		},
+		// 初始化菜单:
+		// 	showList = 显示菜单id数组  —— (注意是id的数组),你填哪些id哪些菜单才会显示 ,为空时代表显示所有	
+		initMenu: function(showList) {
+			this.setMenuList(window.menuList, showList);
+		},
+		// 写入菜单: 
+		// 	menuList = 全部菜单  —— 可以是已经渲染好的 tree 数组,也可以是一个尚未渲染的一维数组(你只要指定好 parent_id,Sa-Admin内部会自动渲染)
+		// 	showList = 显示菜单id数组  —— (注意是id的数组),你填哪些id哪些菜单才会显示 ,为空时代表显示所有	
+		setMenuList: function(menuList, showList) {
+			// 设置 全部菜单 
+			this.menuList = this.arrayToTree(menuList);
+			// 设置 显示的菜单id 
+			showList = showList || this.getAllId(this.menuList);
+			for (var i = 0; i < showList.length; i++) {
+				showList[i] = showList[i] + '';
+			} 
+			this.showList = showList;
+		},
+		
+		// ------------------- Menu 相关操作 --------------------
+		// 根据 id 查找 Menu 
+		getMenuById: function(id) {
+			return this.findMenuById(this.menuList, id);
+		},
+		// 显示某个菜单,根据id 
+		showMenuById: function(id) {
+			var menu = this.getMenuById(id);
+			if(menu) {
+				this.showTab(menu); 
+			}
+		},
+		// 显示homeTab
+		showHome: function() {
+			this.showTab(this.homeTab); 
+		},
+		// 返回当前所有菜单的 一维数组 形式 (将树形菜单转化为一维数组并返回) 方便遍历 
+		getYwList: function() {
+			var arr = [];
+			function _dg(menuList) {
+				menuList = menuList || [];
+				for (var i = 0; i < menuList.length; i++) {
+					var menu = menuList[i];
+					arr.push(menu);
+					// 如果有子菜单 
+					if(menu.childList) {
+						_dg(menu.childList);
+					}
+				}
+			}
+			_dg(this.menuList);
+			return arr;
+		},
+		// 获取菜单所有id 
+		getAllId: function() {
+			var arr = [];
+			this.getYwList().forEach(function(item) {
+				arr.push(item.id);
+			});
+			return arr;
+		},
+		
+		// ------------------- Tab 相关操作 --------------------
+		// 刷新Tab
+		f5Tab: function(tab) {
+			var cs = '#iframe-' + tab.id;
+			var iframe = document.querySelector(cs);
+			if(iframe) {
+				iframe.setAttribute('src', this.getTabUrl(tab));
+			} else {
+				tab.isNeedLoad = false;
+				this.$nextTick(function() {
+					tab.isNeedLoad = true;
+				})
+			}
+		},
+		// 获取 Tab,根据 id
+		getTabById: function(id) {
+			for (var i = 0; i < this.tabList.length; i++) {
+				if(this.tabList[i].id + '' == id + '') {
+					return this.tabList[i];
+				}
+			}
+			return null;
+		},
+		// 添加一个Tab  {id,name,url}
+		addTab: function(tab) {
+			// 如果没有提供id,则随机一个
+			if(!tab.id) {
+				tab.id = new Date().getTime() + '' + this.randomNum();
+			}
+			// 如果没有指定类型
+			if(tab.view === undefined) {
+				if(this.getUrlExt(tab.url).toLowerCase() == 'vue') {
+					tab.view = httpVueLoader(tab.url);
+				}
+			}
+			if(tab.isNeedLoad === undefined) {
+				// tab.isNeedLoad = true;
+				Vue.set(tab, 'isNeedLoad', true);
+			}
+			// console.log('添加之前:' + JSON.stringify(tab));
+			this.tabList.push(tab);
+			this.viewList.push(tab);
+			// tab 超过 20 个,提示过多,如果用户无视继续添加则超过 30 个后不再提示 
+			if(this.tabList.length > 20 && this.tabList.length < 30) {
+				sa_admin.$message({message: '选项卡过多会造成窗口卡顿,建议您关闭不使用的窗口', type: 'warning'});
+			}
+		},
+		// 显示某个页面  (如果不存在, 则先添加)
+		showTab: function(tab) {
+			// 标注:需要此刻加载 
+			// tab.isNeedLoad = false;	
+			Vue.set(tab, 'isNeedLoad', true);
+			// 如果是外部链接
+			if(tab.is_blank) {
+				return open(tab.url); 
+			}
+			// 如果是当前正在显示的tab , 则直接返回,无需继续操作 
+			if(tab == this.nativeTab) {
+				return;
+			}
+			// 如果是click函数 
+			if(tab.click) {
+				if(tab.click() !== true) {
+					return;
+				}
+			}
+			// 如果这个 tab 还没有添加到 tabList 上 
+			if(this.getTabById(tab.id) == null){
+				this.addTab(tab);
+			}
+			// 然后开始显示这个 tab 
+			this.nativeTab = tab;
+			// this.nativeTab.is_load = true;	// 标注:已经加载过了 
+			this.activeMenuId = tab.id + '';	// 左边自动关联, 如果左边没有,则无效果 
+			
+			// 刷新一下url中的锚链 
+			this.$nextTick(function() {
+				this.f5HashByNativeTab();
+			})
+			
+			// 调整一下滚动条 
+			this.$nextTick(function() {
+				try{
+					this.$refs['nav-tab-bar'].scrollToAuto(); 
+				}catch(e){}
+			})
+		},
+		// 显示一个选项卡, 根据 id , 不存在则不显示 
+		showTabById: function(id) {
+			var tab = this.getTabById(id);
+			if(tab) {
+				this.showTab(tab);
+			}
+		},
+		// 关闭 tab (带动画)
+		closeTab: function(tab, callFn) {
+			
+			// homeTab不能关闭 
+			if(tab == this.homeTab || tab.hideClose){
+				return;
+			}
+			
+			// 执行关闭动画
+			var div = document.querySelector('#tab-' + tab.id);
+			div.style.width = div.offsetWidth + 'px';
+			setTimeout(function() {
+				div.style.width = '0px';
+			}, 0);
+			
+			// 等待动画结束
+			setTimeout(function() {
+				
+				// 如果 tab 为当前正在显示的 tab, 则切换为前一个 tab  
+				if(tab == this.nativeTab) {
+					var index = this.tabList.indexOf(tab); 
+					var preTab = this.tabList[index - 1]; 
+					if(preTab) {
+						this.showTab(preTab); 
+					} else {
+						var nextTab = this.tabList[index + 1]; 
+						this.showTab(nextTab); 
+					}
+				}
+				// 从 tabList 中移除这个 tab 
+				sa_admin_code_util.arrayDelete(this.tabList, tab);
+				sa_admin_code_util.arrayDelete(this.viewList, tab);
+				// 如果有回调 
+				if(callFn) {
+					this.$nextTick(function() {
+						callFn();
+					})
+				}
+			}.bind(this), 150);
+		},
+		// 关闭 tab, 根据 id 
+		closeTabById: function(id, callFn) {
+			var tab = this.getTabById(id);
+			if(tab) {
+				this.closeTab(tab, callFn);
+			}
+		},
+		// 悬浮打开 tab 
+		xfTab: function(tab) {
+			console.log('悬浮');
+			// layer打开
+			var index = layer.open({
+				type: 2,
+				title: tab.name,
+				moveOut: true, // 是否可拖动到外面
+				maxmin: true, // 显示最大化按钮
+				shadeClose: false,
+				shade: 0,
+				area: ['80%', '80%'],
+				zIndex: layer.zIndex,
+				content: this.getTabUrl(tab),
+				// 解决拉伸或者最大化的时候,iframe高度不能自适应的问题
+			    resizing: function (layero) {
+			        sa_admin_code_util.solveLayerBug(index);
+			    },
+				// 操作这个layer的时候置顶它 
+				success: function(layero){
+					layer.setTop(layero); 
+				}
+			});
+			// 解决拉伸或者最大化的时候,iframe高度不能自适应的问题 
+			document.querySelector('#layui-layer' + index + ' .layui-layer-max').onclick = function() {
+				setTimeout(function() {
+					sa_admin_code_util.solveLayerBug(index);
+				}, 200)
+			}
+		},
+		// 新窗口打开 tab 
+		newWinTab: function(tab) {
+			open(this.getTabUrl(tab)); 
+			// this.closeTab(tab);
+		},
+		// 获取指定 tab 所代表 iframe 的 url 地址 (同域下可获取最新地址, 跨域时只能获取初始化时的地址)
+		getTabUrl: function(tab) {
+			var cs = '#iframe-' + tab.id;
+			var iframe = document.querySelector(cs);
+			if(!iframe) {
+				return tab.url;
+			}
+			try{
+				return iframe.contentWindow.location.href;
+			}catch(e){
+				return iframe.getAttribute('src');
+			}
+		},
+		
+		// ------------------- 框架整体相关操作 --------------------
+		// 展开菜单 
+		startOpen: function() {
+			this.isOpen = true;
+			setTimeout(function() {
+				this.isOpenRight = true;
+			}.bind(this), 200);
+		},
+		// 折叠菜单 
+		endOpen: function() {
+			this.isOpen = false;
+			this.isOpenRight = false;
+		},
+		
+		// ------------------- 锚链接路由相关 --------------------
+		// 根据锚链接, 打开窗口
+		showTabByHash: function() {
+			// 如果非记住模式
+			if(this.isRemeOpen == false) {
+				return;
+			}
+			// 获取锚链接中的id
+			var hash = location.hash;
+			var id = hash.replace('#', '');
+			if(id == '') {
+				return;
+			}
+			// 如果已经存在与tabbar中 
+			var tab = this.getTabById(id);
+			if(tab) {
+				return this.showTab(tab);
+			}
+			// 否则从菜单中打开 
+			this.showMenuById(id);
+			// 此时, 仍有一种tab打不开, 那就是自定义tab然后还已经关闭的,
+			// 预设 解决方案: 在localStor里存储所有打开过的tab,
+			// 以后如果有强需求这个功能时, 再实现 
+		},
+		// 根据当前tab刷新一下锚链接 
+		f5HashByNativeTab: function() {
+			// 如果非记住模式
+			if(this.isRemeOpen == false) {
+				return;
+			}
+			location.hash = this.nativeTab.id;
+		},
+		
+		// ------------------- 工具方法 -------------------- 
+		// 弹窗提示 
+		msg: function(msg) {
+			layer.msg(msg)
+		},
+		// 返回随机数 
+		randomNum: function(min, max) {
+			min = min || 1;
+			max = max || 1000000000;
+			return parseInt(Math.random() * (max - min + 1) + min, 10);
+		},
+		// 从 menuList 里查找指定 id 的 menu,支持多级递归 
+		findMenuById: function(menuList, id) {
+			for (var i = 0; i < menuList.length; i++) {
+				var menu = menuList[i];
+				if(menu.id + '' == id + '') {
+					return menu;
+				}
+				// 如果是二级或多级
+				if(menu.childList) {
+					var menu2 = this.findMenuById(menu.childList, id);
+					if(menu2 != null) {
+						return menu2;
+					}
+				}
+			}
+			return null;
+		},
+		// 获取文件后缀
+		getUrlExt: function(url) {
+			if(!url) {
+				return "";
+			}
+			if(url.indexOf('?') > -1) {
+				url = url.split('?')[0];
+			}
+			if(url.indexOf('#') > -1) {
+				url = url.split('#')[0];
+			}
+			var index= url.lastIndexOf(".");
+			if(index == -1) {
+				return "";
+			}
+			var ext = url.substr(index + 1);
+			return ext;
+		},
+		// 将一维平面数组转换为 Tree 菜单 (根据其指定的 parent_id 添加到其父菜单的childList)
+		arrayToTree: function(menuList) {
+			for (var i = 0; i < menuList.length; i++) {
+				var menu = menuList[i];
+				// 如果这个 Menu 指定了 parent_id 属性,则将其转移到其指定的父 Menu 的 childList 属性上 
+				if(menu.parent_id) {
+					var parent_menu = this.findMenuById(menuList, menu.parent_id);
+					if(parent_menu) {
+						menu.parent_menu = parent_menu;
+						parent_menu.childList = parent_menu.childList || [];
+						parent_menu.childList.push(menu);
+						menuList.splice(i, 1);	// 从一维中删除 
+						i--;
+					}
+				}
+			}
+			return menuList;
+		},
+		
+		// ------------------- 其它 -------------------- 
+		// 获取指定 tab 栏的 window 对象, 用于多窗口通信 
+		getTabWindow: function(tabId) {
+			var iframe = document.querySelector('#iframe-' + tabId);
+			if(iframe != null)  {
+				return iframe.contentWindow;
+			}
+			return null;
+		},
+		// 打印版本
+		// printVesion: function() {
+		// 	console.log('欢迎使用Sa-Admin,当前版本:' + this.version + ",更新于:" + this.updateTime + ",GitHub地址:" + this.githubUrl);
+		// 	console.log('如在使用中发现任何bug或者疑问,请加入QQ群交流:782974737,点击加入:' + 'https://jq.qq.com/?_wv=1027&k=5DHN5Ib');
+		// },
+		printVesion: function() {
+			var str = ('Sa-Plus ' + this.plusVersion + " (" + this.plusUpdateTime + ")  GitHub:" + this.plusGithubUrl);
+			// console.log('%c%s', 'color: green; font-size: 12px; font-weight: 400; margin-top: 4px; margin-bottom: 4px;', str);
+			var str2 = ('如在使用中发现任何bug或者疑问,请加入QQ群交流:782974737,点击加入:' + 'https://jq.qq.com/?_wv=1027&k=5DHN5Ib');
+			var s = str;// + ' \n' + str2;
+			console.log('%c%s', 'color: green; font-size: 12px; margin-top: 2px; margin-bottom: 2px;', s);
+		},
+		
+	},
+	created:function(){
+		
+	}
+});
+var saAdmin = sa_admin;		
+Vue.prototype.sa_admin = sa_admin;
+Vue.prototype.saAdmin = saAdmin;
+
+// 监听窗口大小变动
+window.onresize = function() {
+	if(document.body.clientWidth < 800) {
+		sa_admin.endOpen();
+	} else {
+		sa_admin.startOpen();
+	}
+}
+
+// 监听锚链接变动
+window.onhashchange = function() {
+	sa_admin.showTabByHash();
+}
+
+

+ 225 - 0
sa-frame/index/theme.css

@@ -0,0 +1,225 @@
+/* 样式调整为继承父级 */
+.nav-left .el-submenu__title i,
+.nav-left .el-menu-item i,
+.nav-right-1 .el-dropdown,
+.tab-title:hover .el-icon-caret-right,
+.tab-title.tab-native .el-icon-caret-right {
+	color: inherit;
+}
+
+.el-menu,
+.el-submenu,
+.nav-left .el-submenu__title,
+.nav-left .el-submenu .el-submenu .el-submenu__title,
+.nav-left .el-menu-item {
+	color: inherit;
+	background-color: inherit;
+}
+
+.theme-0 .menu-name,.theme-0 .tab-title-2>span{transition: none !important;}
+
+
+/* 声明变量 */
+body{
+	--menu-bg-color: #222;		/* 菜单 - 背景色 */
+	--menu-color: #FFF;			/* 菜单 - 文字色 */
+	--menu-bg-color-2: #000;	/* 二级菜单 - 背景色 */
+	--menu-hover-bg-color: #4E5465;			/* 菜单悬浮 - 背景色 */
+	--menu-active-bg-color: #2D8CF0;		/* 菜单选中 - 背景色 */
+	--menu-active-color: #FFF;				/* 菜单选中 - 文字色 */
+	--tool-bg-color: #FFF;			/* 工具栏 - 背景色 */
+	--tool-color: #333;				/* 工具栏 - 文字色 */
+	--tool-hover-bg-color: #EEE;			/* 工具栏悬浮 - 背景色 */
+	
+	/* --tab-hover-bg-color: var(--menu-active-bg-color); */		/* Tab栏悬浮和选中 - 文字色 */
+	/* --tab-hover-color: var(--menu-active-color); */			/* Tab栏悬浮和选中 - 文字色 */
+	
+	--nav-left-top-border-color: 1px #222 solid;	/* 左上 - 右边框颜色 */
+	--nav-left-bottom-border-color: 1px #222 solid;	/* 左下 - 右边框颜色 */
+}
+
+/* ========================== 主题 - 0 默认样式 蓝色 ==========================  */
+.theme-0 {}
+
+/* 左上 - 右边框颜色 */
+.theme-0 .nav-left-top{
+	border-right: var(--nav-left-top-border-color);
+}
+/* 左下 - 右边框颜色 */
+.theme-0 .nav-left-bottom{
+	border-right: var(--nav-left-bottom-border-color);
+}
+
+/* 左边栏背景色,前景色 */
+.theme-0 .nav-left {
+	background-color: var(--menu-bg-color);
+	color: var(--menu-color);
+}
+
+/* 二级菜单背景色 */
+.theme-0 .el-submenu .el-menu-item,
+.theme-0 .nav-left .el-submenu .el-submenu .el-submenu__title{
+	background: var(--menu-bg-color-2);
+}
+
+/* 所有菜单悬浮样式*/
+.theme-0 .nav-left .el-submenu__title:hover,
+.theme-0 .nav-left .el-submenu .el-submenu .el-submenu__title:hover,
+.theme-0 .nav-left .el-menu-item:hover{
+	background-color: var(--menu-hover-bg-color);
+}
+/* 所有菜单选中时 */
+.theme-0 .nav-left .el-menu-item.is-active {
+	/* background-color: var(--menu-active-bg-color); */
+	background: var(--menu-active-bg-color);
+	color: var(--menu-active-color);
+}
+
+/* 工具栏背景色颜色, 前景色 */
+.theme-0 .nav-right-1 {
+	color: var(--tool-color);
+	background-color: var(--tool-bg-color);
+}
+
+/* 工具栏悬浮颜色 */
+.theme-0 .tool-fox:hover {
+	background-color: var(--tool-hover-bg-color);
+}
+
+/* tab卡片栏 - 悬浮颜色 */
+.theme-0 .tab-title:hover{
+	color: var(--menu-active-bg-color);
+	border: 1px var(--menu-active-bg-color) solid;
+}
+/* tab卡片栏 - 选中颜色 */
+.theme-0 .tab-native.tab-title {
+	background-color: var(--menu-active-bg-color);
+	color: var(--menu-active-color);
+	border: 1px var(--menu-active-bg-color) solid;
+}
+
+/* 以下的主题 logo栏变小 */
+.theme-3 .nav-left-top,
+.theme-4 .nav-left-top,
+.theme-10 .nav-left-top{height: 50px; line-height: 50px; text-indent: 0.3em;}
+
+.theme-3 .nav-left-top .admin-logo,
+.theme-4 .nav-left-top .admin-logo,
+.theme-10 .nav-left-top .admin-logo{width: 28px; height: 28px; position: relative; top: -2px;}
+
+.theme-3 .nav-left-bottom,
+.theme-4 .nav-left-bottom,
+.theme-10 .nav-left-bottom{height: calc(100% - 85px + 36px);}
+
+
+
+
+/* ========================== 主题-1 什么也不覆盖 即:全部取默认样式 ==========================  */
+.theme-1 {}
+
+/* ========================== 主题-2 绿色 ==========================  */
+.theme-2 {
+	--menu-active-bg-color: #009688;	/* 菜单选中 - 背景色 */
+}
+
+/* ========================== 主题-3 白色 清爽 ==========================  */
+.theme-3 {
+	--menu-bg-color: #FFF;		/* 菜单 - 背景色 */
+	--menu-color: #333;			/* 菜单 - 文字色 */
+	--menu-bg-color-2: #fafafa;	/* 二级菜单 - 背景色 */
+	--menu-hover-bg-color: #ECF5FF;			/* 菜单悬浮 - 背景色 */
+	--menu-active-bg-color: #ECF5FF;		/* 菜单选中 - 背景色 */
+	--menu-active-color: #409EFF;				/* 菜单选中 - 文字色 */
+	
+	--nav-left-top-border-color: 1px #ddd solid;	/* 左上 - 右边框颜色 */
+	--nav-left-bottom-border-color: 1px #ddd solid;	/* 左下 - 右边框颜色 */
+}
+/* ----- 附加样式 ----- */
+/* logo下面的边框 */
+.theme-3 .nav-left-top{border-bottom: 1px #eee solid;}
+/* tab卡片栏 - 悬浮颜色 */
+.theme-3 .tab-title:hover{
+	color: var(--menu-active-color);
+	border: 1px var(--menu-active-color) solid;
+}
+/* tab卡片栏 - 选中颜色 */
+.theme-3 .tab-native.tab-title {
+	background-color: var(--menu-active-bg-color);
+	color: var(--menu-active-color);
+	border: 1px var(--menu-active-color) solid;
+}
+
+
+/* ========================== 主题-4 灰绿色 ==========================  */
+.theme-4 {
+	--menu-bg-color: #EEE;		/* 菜单 - 背景色 */
+	--menu-color: #333;			/* 菜单 - 文字色 */
+	--menu-bg-color-2: #DDD;	/* 二级菜单 - 背景色 */
+	--menu-hover-bg-color: #ECF5FF;			/* 菜单悬浮 - 背景色 */
+	--menu-active-bg-color: #009688;		/* 菜单选中 - 背景色 */
+	--menu-active-color: #FFF;				/* 菜单选中 - 文字色 */
+	--tool-bg-color: #222;			/* 工具栏 - 背景色 */
+	--tool-color: #EEE;				/* 工具栏 - 文字色 */
+	--tool-hover-bg-color: #444;			/* 工具栏悬浮 - 背景色 */
+	
+	--nav-left-bottom-border-color: 1px #ddd solid;	/* 左下 - 右边框颜色 */
+}
+
+.theme-4 .nav-left-top{height: 49px; line-height: 49px; text-indent: 0.3em; background-color: #222; color: #FFF;}
+
+/* ========================== 主题-5 红色 ==========================  */
+.theme-5 {
+	--menu-active-bg-color: #dd4949;	/* 菜单选中 - 背景色 */
+}
+
+/* ========================== 主题-6 钛合金  ==========================  */
+.theme-6 {
+	--menu-active-bg-color: #805322;		/* 菜单选中 - 背景色 */
+	--tool-bg-color: #222;				/* 工具栏 - 背景色 */
+	--tool-color: #EEE;					/* 工具栏 - 文字色 */
+	--tool-hover-bg-color: #444;			/* 工具栏悬浮 - 背景色 */
+}
+
+/* ========================== 主题-7 沉淀式黑蓝 ==========================  */
+.theme-7 {
+	--tool-bg-color: #222;				/* 工具栏 - 背景色 */
+	--tool-color: #EEE;					/* 工具栏 - 文字色 */
+	--tool-hover-bg-color: #444;			/* 工具栏悬浮 - 背景色 */
+}
+
+/* ========================== 主题-8 简约式灰蓝 ==========================  */
+.theme-8 {
+	--menu-active-bg-color: #4E5465;		/* 菜单选中 - 背景色 */
+}
+
+/* ========================== 主题-9 紫色 ==========================  */
+.theme-9 {
+	--menu-active-bg-color: #A906B3;		/* 菜单选中 - 背景色 */
+}
+
+/* ========================== 主题-10 简约草绿 ==========================  */
+.theme-10 {
+	--menu-bg-color: #FFF;		/* 菜单 - 背景色 */
+	--menu-color: #333;			/* 菜单 - 文字色 */
+	--menu-bg-color-2: #fff;	/* 二级菜单 - 背景色 */
+	--menu-hover-bg-color: #ECF5FF;			/* 菜单悬浮 - 背景色 */
+	--menu-active-bg-color: #73D13D;		/* 菜单选中 - 背景色 */
+	
+	--nav-left-top-border-color: 1px #fff solid;	/* 左下 - 右边框颜色 */
+	--nav-left-bottom-border-color: 1px #ddd solid;	/* 左下 - 右边框颜色 */
+}
+
+/* logo下面的边框 */
+.theme-10 .nav-left-top{border-bottom: 1px #eee solid;}
+
+/* tab卡片栏 - 悬浮颜色 */
+.theme-10 .tab-title:hover{
+	color: var(--menu-active-bg-color);
+	border: 1px var(--menu-active-bg-color) solid;
+}
+/* tab卡片栏 - 选中颜色 */
+.theme-10 .tab-native.tab-title {
+	background-color: var(--menu-active-bg-color);
+	color: var(--menu-active-color);
+	border: 1px var(--menu-active-bg-color) solid;
+}

+ 127 - 0
sa-frame/login/app.js

@@ -0,0 +1,127 @@
+/* -----------------------------------------------
+/* How to use? : Check the GitHub README
+/* ----------------------------------------------- */
+
+/* To load a config file (particles.json) you need to host this demo (MAMP/WAMP/local)... */
+/*
+particlesJS.load('particles-js', 'particles.json', function() {
+  console.log('particles.js loaded - callback');
+});
+*/
+
+/* Otherwise just put the config content (json): */
+
+particlesJS('particles-js',
+
+	{
+		"particles": {
+			"number": {
+				"value": 40,
+				"density": {
+					"enable": true,
+					"value_area": 800
+				}
+			},
+			"color": {
+				"value": "#ffffff"
+			},
+			"shape": {
+				"type": "circle",
+				"stroke": {
+					"width": 0,
+					"color": "#000000"
+				},
+				"polygon": {
+					"nb_sides": 5
+				},
+				"image": {
+					"src": "img/github.svg",
+					"width": 100,
+					"height": 100
+				}
+			},
+			"opacity": {
+				"value": 0.7,
+				"random": false,
+				"anim": {
+					"enable": false,
+					"speed": 1,
+					"opacity_min": 0.1,
+					"sync": false
+				}
+			},
+			"size": {
+				"value": 3,
+				"random": true,
+				"anim": {
+					"enable": false,
+					"speed": 40,
+					"size_min": 0.1,
+					"sync": false
+				}
+			},
+			"line_linked": {
+				"enable": true,
+				"distance": 150,
+				"color": "#ffffff",
+				"opacity": 0.6,
+				"width": 1
+			},
+			"move": {
+				"enable": true,
+				"speed": 6,
+				"direction": "none",
+				"random": false,
+				"straight": false,
+				"out_mode": "out",
+				"bounce": false,
+				"attract": {
+					"enable": false,
+					"rotateX": 600,
+					"rotateY": 1200
+				}
+			}
+		},
+		"interactivity": {
+			"detect_on": "canvas",
+			"events": {
+				"onhover": {
+					"enable": true,
+					"mode": "grab"
+				},
+				"onclick": {
+					"enable": true,
+					"mode": "push"
+				},
+				"resize": true
+			},
+			"modes": {
+				"grab": {
+					"distance": 200,
+					"line_linked": {
+						"opacity": 1
+					}
+				},
+				"bubble": {
+					"distance": 400,
+					"size": 40,
+					"duration": 2,
+					"opacity": 8,
+					"speed": 3
+				},
+				"repulse": {
+					"distance": 200,
+					"duration": 0.4
+				},
+				"push": {
+					"particles_nb": 4
+				},
+				"remove": {
+					"particles_nb": 2
+				}
+			}
+		},
+		"retina_detect": false
+	}
+
+);

BIN
sa-frame/login/bg.jpg


BIN
sa-frame/login/name.png


Разница между файлами не показана из-за своего большого размера
+ 8 - 0
sa-frame/login/particles.min.js


BIN
sa-frame/login/password.png


+ 35 - 0
sa-frame/login/reset.css

@@ -0,0 +1,35 @@
+@charset "utf-8";
+/* CSS Document */
+/*Reset*/
+*{box-sizing:content-box;}
+a:hover, a:focus{text-decoration:none;}
+body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,textarea,p,blockquote,th,td{margin:0;padding:0;}
+table{border-collapse:collapse;border-spacing:0;}
+body{-webkit-text-size-adjust:none;}
+fieldset,img{border:0;}
+img{ vertical-align: top; max-width: 100%; }
+address,caption,cite,code,dfn,em,th,var{font-style:normal;font-weight:normal;}
+ol,ul{list-style:none;}
+caption,th{text-align:left;}
+h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}
+q:before,q:after{content:'';}
+abbr,acronym {border:0;}
+.clearfix:after{visibility:hidden;display: block;font-size:0;content:" ";clear:both;height:0;}
+* html .clearfix{ zoom: 1; } /* IE6 */
+*:first-child+html .clearfix { zoom: 1; } /* IE7 */
+.cli{ clear:both; font-size:0; height:0; overflow:hidden;display:block;}
+.lclear{clear:left;font-size:0;height:0;overflow:hidden;}	
+.fl{float:left;}
+.fr{float:right;}
+
+/* ֹ
+iframe{nifm2:expression(this.src='about:blank',this.outerHTML='');}
+script{no2js:expression((this.src.toLowerCase().indexOf('http')==0)?document.close():'');}
+*/
+/* ıԼ˶
+div{word-wrap: break-word;word-break: normal;}  
+p{text-align:justify; text-justify:inter-ideograph;}
+*/
+/*general*/
+body{font-size:12px;font-family:'微软雅黑',"宋体","Arial Narrow",Helvetica,sans-serif;color:#000;line-height:1.2;text-align:left;}
+a{color:#333;text-decoration:none;}

+ 152 - 0
sa-frame/login/style.css

@@ -0,0 +1,152 @@
+@charset "utf-8";
+/* CSS Document */
+/*Reset*/
+*{box-sizing:content-box;}
+a:hover, a:focus{text-decoration:none;}
+body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,textarea,p,blockquote,th,td{margin:0;padding:0;}
+table{border-collapse:collapse;border-spacing:0;}
+body{-webkit-text-size-adjust:none;}
+fieldset,img{border:0;}
+img{ vertical-align: top; max-width: 100%; }
+address,caption,cite,code,dfn,em,th,var{font-style:normal;font-weight:normal;}
+ol,ul{list-style:none;}
+caption,th{text-align:left;}
+h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}
+q:before,q:after{content:'';}
+abbr,acronym {border:0;}
+.clearfix:after{visibility:hidden;display: block;font-size:0;content:" ";clear:both;height:0;}
+* html .clearfix{ zoom: 1; } /* IE6 */
+*:first-child+html .clearfix { zoom: 1; } /* IE7 */
+.cli{ clear:both; font-size:0; height:0; overflow:hidden;display:block;}
+.lclear{clear:left;font-size:0;height:0;overflow:hidden;}	
+.fl{float:left;}
+.fr{float:right;}
+
+/* ֹ
+iframe{nifm2:expression(this.src='about:blank',this.outerHTML='');}
+script{no2js:expression((this.src.toLowerCase().indexOf('http')==0)?document.close():'');}
+*/
+/* ıԼ˶
+div{word-wrap: break-word;word-break: normal;}  
+p{text-align:justify; text-justify:inter-ideograph;}
+*/
+/*general*/
+body{font-size:12px;font-family:'微软雅黑',"宋体","Arial Narrow",Helvetica,sans-serif;color:#000;line-height:1.2;text-align:left;}
+a{color:#333;text-decoration:none;}
+
+
+
+/* 以下为手写代码  */
+html,body{ 
+	width:100%;
+	height:100%;
+}
+
+canvas{
+  display:block;
+  vertical-align:bottom;
+}
+
+.count-particles{
+  background: #000022;
+  position: absolute;
+  top: 48px;
+  left: 0;
+  width: 80px;
+  color: #13E8E9;
+  font-size: .8em;
+  text-align: left;
+  text-indent: 4px;
+  line-height: 14px;
+  padding-bottom: 2px;
+  font-family: Helvetica, Arial, sans-serif;
+  font-weight: bold;
+}
+
+.js-count-particles{
+  font-size: 1.1em;
+}
+
+#stats,
+.count-particles{
+  -webkit-user-select: none;
+  margin-top: 5px;
+  margin-left: 5px;
+}
+
+#stats{
+  border-radius: 3px 3px 0 0;
+  overflow: hidden;
+}
+
+.count-particles{
+  border-radius: 0 0 3px 3px;
+}
+
+
+#particles-js{
+	width: 100%;
+	height: 100%;
+	position: relative;
+	/* background-image: url(sa-frame/login/bg.jpg); */
+	background-position: 50% 50%;
+	background-size: cover;
+	background-repeat: no-repeat;
+	margin-left: auto;
+	margin-right: auto;
+}
+
+.sk-rotating-plane {
+	display: none;
+    width: 80px;
+    height: 80px;
+    margin: auto;
+    background-color: white;
+    -webkit-animation: sk-rotating-plane 1.2s infinite ease-in-out;
+    animation: sk-rotating-plane 1.2s infinite ease-in-out;
+    z-index: 1;
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    margin-left: -40px;
+    margin-top: -80px;
+}
+.sk-rotating-plane.active{display: block;}
+
+@keyframes sk-rotating-plane{
+	0% {
+	    -webkit-transform: perspective(120px) rotateX(0deg) rotateY(0deg);
+	    transform: perspective(120px) rotateX(0deg) rotateY(0deg);
+	}
+	50% {
+	    -webkit-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg);
+	    transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg);
+	}
+	100% {
+	    -webkit-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);
+	    transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);
+	}
+}
+
+@keyframes login-small{
+	0%{
+		transform: scale(1);-moz-transform: scale(1);	/* Firefox 4 */-webkit-transform: scale(1);	/* Safari 和 Chrome */-o-transform: scale(1);	/* Opera */-ms-transform:scale(1); 	/* IE 9 */
+	}
+	100%{
+		transform: scale(0.2);-moz-transform: scale(0.1);	/* Firefox 4 */-webkit-transform: scale(0.2);	/* Safari 和 Chrome */-o-transform: scale(0.1);	/* Opera */-ms-transform:scale(0.1); 	/* IE 9 */
+	}
+}
+
+.login{z-index: 2;position:absolute;width: 350px;border-radius: 2px;height: 500px;background: white;box-shadow: 0px 0px 5px #333333;top: 50%;left: 50%;margin-top: -250px;margin-left: -175px;transition: all 1s;-moz-transition: all 1s;	/* Firefox 4 */-webkit-transition: all 1s;	/* Safari 和 Chrome */-o-transition: all 1s;	/* Opera */}
+.login-top{font-size: 24px;margin-top: 100px;padding-left: 40px;box-sizing: border-box;color: #333333;margin-bottom: 50px;}
+.login-center{width: 100%;box-sizing: border-box;padding: 0 40px;margin-bottom: 30px;}
+.login-center-img{width: 20px;height: 20px;float: left;margin-top: 5px;}
+.login-center-img>img{width: 100%;}
+.login-center-input{float: left;width: 230px;margin-left: 15px;height: 30px;position: relative;}
+.login-center-input input{z-index: 2;transition: all 0.5s;padding-left: 10px;color: #333333;width: 100%;height: 30px;border: 0;border-bottom: 1px solid #cccccc;border-top: 1px solid #ffffff;border-left: 1px solid #ffffff;border-right: 1px solid #ffffff;box-sizing: border-box;outline: none;position: relative;}
+.login-center-input input:focus{border: 1px solid dodgerblue;}
+.login-center-input-text{background: white;padding: 0 5px;position: absolute;z-index: 0;opacity: 0;height: 20px;top: 50%;margin-top: -10px;font-size: 14px;left: 5px;color: dodgerblue;line-height: 20px;transition: all 0.5s;-moz-transition: all 0.5s;	/* Firefox 4 */-webkit-transition: all 0.5s;	/* Safari 和 Chrome */-o-transition: all 0.5s;	/* Opera */}
+.login-center-input input:focus~.login-center-input-text{top: 0;z-index: 3;opacity: 1;margin-top: -15px;}
+.login.active{-webkit-animation: login-small 0.8s ; animation: login-small 0.8s ;animation-fill-mode:forwards;-webkit-animation-fill-mode:forwards}
+.login-button{cursor: pointer;width: 250px;text-align: center;height: 40px;line-height: 40px;background-color: dodgerblue;border-radius: 5px;margin: 0 auto;margin-top: 50px;color: white;}
+

+ 342 - 0
sa-frame/menu-list-sp.js

@@ -0,0 +1,342 @@
+// 此处定义所有有关 sa-plus 的路由菜单
+// 如需添加自定义菜单,请不要更改此文件,请在 menu-list.js 里添加 (没有这个文件就新建)
+window.menuList = window.menuList || [];
+window.menuList.unshift({
+	id: 'bas',
+	name: '身份相关',
+	isShow: false, // 隐藏显示
+	childList: [{
+			id: 'dev',
+			name: '开发者权限',
+			isShow: false
+		},
+		{
+			id: 'in-system',
+			name: '后台管理权限',
+			isShow: false
+		},
+	]
+}, {
+	id: 'auth',
+	name: '基础数据管理',
+	icon: 'el-icon-unlock',
+	parent: true,
+
+	childList: [{
+			id: 'tb-dept-list',
+			name: '组织管理',
+			url: 'sa-view/tb-dept/tb-dept-list.html',
+			childList: [{
+					id: 'tb-dept-add',
+					name: '添加',
+					isShow: false,
+				},
+				{
+					id: 'tb-dept-del',
+					name: '删除',
+					isShow: false,
+
+				},
+				{
+					id: 'tb-dept-edit',
+					name: '编辑',
+					isShow: false,
+
+				},
+			]
+		},
+		{
+			id: 'admin-list',
+			name: '用户管理',
+			url: 'sa-view-sp/sp-admin/admin-list.html',
+			info: '所有管理员账号',
+			childList: [{
+					id: 'sp-admin-add',
+					name: '添加',
+					isShow: false,
+				},
+				{
+					id: 'sp-admin-del',
+					name: '删除',
+					isShow: false,
+
+				},
+				{
+					id: 'sp-admin-edit',
+					name: '编辑',
+					isShow: false,
+
+				},
+			]
+		},
+		{
+			id: 'role-list',
+			name: '角色管理',
+			url: 'sa-view-sp/sp-role/role-list.html',
+			info: '管理系统各种角色',
+			childList: [{
+					id: 'sp-role-add',
+					name: '添加',
+					isShow: false,
+				},
+				{
+					id: 'sp-role-del',
+					name: '删除',
+					isShow: false,
+
+				},
+				{
+					id: 'sp-role-edit',
+					name: '编辑',
+					isShow: false,
+
+				},
+				{
+					id: 'sp-role-permission',
+					name: '分配权限',
+					isShow: false,
+
+				},
+			]
+		},
+		{
+			id: 'menu-list',
+			name: '菜单列表',
+			url: 'sa-view-sp/sp-role/menu-list.html',
+			info: '所有菜单项预览'
+		},
+		{
+			id: 'apilog-list',
+			name: '日志管理',
+			url: 'sa-view-sp/sp-apilog/api-log-list.html',
+			info: '记录本系统所有的api请求'
+		},
+		{
+			id: 'sys-dict-list',
+			name: '字典管理',
+			url: 'sa-view/sys-dict/sys-dict-list.html',
+			childList: [{
+					id: 'sys-dict-add',
+					name: '添加',
+					isShow: false,
+				},
+				{
+					id: 'sys-dict-del',
+					name: '删除',
+					isShow: false,
+
+				},
+				{
+					id: 'sys-dict-edit',
+					name: '编辑',
+					isShow: false,
+
+				},
+
+			]
+		},
+	]
+}, {
+	id: 'console',
+	name: '监控中心',
+	parent: true,
+	icon: 'el-icon-view',
+	info: '对本系统的各种监控',
+	childList: [{
+			id: 'redis-console',
+			name: 'Redis控制台',
+			url: 'sa-view-sp/sp-console/redis-console.html',
+			info: 'redis常用工具'
+		},
+
+		{
+			id: 'sql-console',
+			name: 'SQL 监控台',
+			url: 'sa-view-sp/sp-console/sql-console.html',
+			info: 'sql控制台'
+		},
+		{
+			id: 'form-generator',
+			name: '在线表单构建',
+			url: 'https://mrhj.gitee.io/form-generator/#/'
+		},
+	]
+}, {
+	id: 'sp-cfg',
+	name: '系统配置',
+	parent: true,
+	icon: 'el-icon-setting',
+	info: '有关系统的一些配置',
+	childList: [{
+			id: 'sp-cfg-app',
+			name: '系统对公配置',
+			url: 'sa-view-sp/sp-cfg/app-cfg.html'
+		},
+		{
+			id: 'sp-cfg-server',
+			name: '服务器私有配置',
+			url: 'sa-view-sp/sp-cfg/server-cfg.html'
+		},
+	]
+}, {
+	id: 'filing-system',
+	name: '备案管理子系统',
+	parent: true,
+	icon: 'el-icon-setting',
+	childList: [{
+			id: 'tb-filing',
+			name: '备案管理',
+			parent: true,
+			url: 'sa-view/tb-car-black/tb-car-black-list.html',
+			childList: [{
+					id: 'tb-car-filing-list',
+					name: '车辆备案',
+					url: 'sa-view/tb-car-filing/tb-car-filing-list.html',
+					childList: [{
+							id: 'tb-car-filing-add',
+							name: '添加',
+							isShow: false,
+						},
+						{
+							id: 'tb-car-filing-del',
+							name: '删除',
+							isShow: false,
+					
+						},
+						{
+							id: 'tb-car-filing-edit',
+							name: '编辑',
+							isShow: false,
+					
+						},
+					]
+				},
+				{
+					id: 'tb-person-filing-list',
+					name: '人员备案',
+					url: 'sa-view/tb-person-filing/tb-person-filing-list.html',
+					childList: [{
+							id: 'tb-person-filing-add',
+							name: '添加',
+							isShow: false,
+						},
+						{
+							id: 'tb-person-filing-del',
+							name: '删除',
+							isShow: false,
+					
+						},
+						{
+							id: 'tb-person-filing-edit',
+							name: '编辑',
+							isShow: false,
+					
+						},
+					]
+				}
+			]
+		},
+		{
+			id: 'tb-black',
+			name: '黑名单管理',
+			parent: true,
+			url: 'sa-view/tb-car-black/tb-car-black-list.html',
+			childList: [{
+					id: 'tb-car-black-list',
+					name: '车辆黑名单',
+					url: 'sa-view/tb-car-black/tb-car-black-list.html',
+					childList: [{
+							id: 'tb-car-black-add',
+							name: '添加',
+							isShow: false,
+						},
+						{
+							id: 'tb-car-black-del',
+							name: '删除',
+							isShow: false,
+
+						},
+						{
+							id: 'tb-car-black-edit',
+							name: '编辑',
+							isShow: false,
+
+						},
+					]
+				},
+				{
+					id: 'tb-person-black-list',
+					name: '人员黑名单',
+					url: 'sa-view/tb-person-black/tb-person-black-list.html',
+					childList: [{
+							id: 'tb-person-black-add',
+							name: '添加',
+							isShow: false,
+						},
+						{
+							id: 'tb-person-black-del',
+							name: '删除',
+							isShow: false,
+
+						},
+						{
+							id: 'tb-person-black-edit',
+							name: '编辑',
+							isShow: false,
+
+						},
+					]
+				}
+			]
+		}
+
+	]
+}, {
+	id: 'tb-channel',
+	name: '通道管理',
+	icon: 'el-icon-folder-opened',
+	info: '通道管理表数据的维护',
+	childList: [{
+			id: 'tb-channel-list',
+			name: '通道管理-列表',
+			url: 'sa-view/tb-channel/tb-channel-list.html'
+		},
+		{
+			id: 'tb-channel-add',
+			name: '通道管理-添加',
+			url: 'sa-view/tb-channel/tb-channel-add.html'
+		},
+	]
+}, {
+	id: 'tb-terminal',
+	name: '设备管理',
+	icon: 'el-icon-folder-opened',
+	info: '设备管理表数据的维护',
+	childList: [{
+			id: 'tb-terminal-list',
+			name: '设备管理-列表',
+			url: 'sa-view/tb-terminal/tb-terminal-list.html'
+		},
+		{
+			id: 'tb-terminal-add',
+			name: '设备管理-添加',
+			url: 'sa-view/tb-terminal/tb-terminal-add.html'
+		},
+	]
+}, {
+	id: 'tb-venues',
+	name: '场所管理',
+	icon: 'el-icon-folder-opened',
+	info: '场所管理表数据的维护',
+	childList: [{
+			id: 'tb-venues-list',
+			name: '场所管理-列表',
+			url: 'sa-view/tb-venues/tb-venues-list.html'
+		},
+		{
+			id: 'tb-venues-add',
+			name: '场所管理-添加',
+			url: 'sa-view/tb-venues/tb-venues-add.html'
+		},
+	]
+}, );

+ 19 - 0
sa-frame/menu-list.js

@@ -0,0 +1,19 @@
+// 一个菜单可以包括的所有属性 
+// {
+// 	id: '12345',		// 菜单id, 必须唯一
+// 	name: '用户中心',		// 菜单名称, 同时也是tab选项卡上显示的名称
+// 	icon: 'el-icon-user',	// 菜单图标, 参考地址:  https://element.eleme.cn/#/zh-CN/component/icon
+//	info: '管理所有用户',	// 菜单介绍, 在菜单预览和分配权限时会有显示 
+// 	url: 'sa-html/user/user-list.html',	// 菜单指向地址
+// 	parentId: 1,			// 所属父菜单id, 如果指定了一个值, sa-admin在初始化时会将此菜单转移到指定菜单上 
+// 	isShow: true,			// 是否显示, 默认true
+// 	isBlank: false,		// 是否属于外部链接, 如果为true, 则点击菜单时从新窗口打开 
+// 	childList: [			// 指定这个菜单所有的子菜单, 子菜单可以继续指定子菜单, 至多支持三级菜单
+// 		// .... 
+// 	],
+//	click: function(){}		// 点击菜单执行一个函数 
+// }
+
+// 定义菜单列表 
+var menuList =	[
+]

+ 61 - 0
sa-frame/nav/com-add-tab.vue

@@ -0,0 +1,61 @@
+<template>
+	<!-- 双击弹出的窗口 -->
+	<div class="at-form-fox" style="width: 0px; height: 0px; overflow: hidden; ">
+		<div class="at-form-dom" style="width: 300px; padding: 20px 0 10px 0; background-color: #FFF;">
+			<el-form label-width="80px" size="mini">
+				<!-- <h5 style="padding: 0 0 10px 26px;">创建新页面</h5> -->
+				<el-form-item label="标题:">
+					<el-input style="width: 200px;" v-model="atTitle" placeholder="页面标题"></el-input>
+				</el-form-item>
+				<el-form-item label="地址:" style="margin-top: -10px;">
+					<el-input style="width: 200px;" v-model="atUrl" placeholder="https://www.baidu.com/" @keyup.native.enter="atOk()"></el-input>
+				</el-form-item>
+				<el-form-item label="操作:" style="margin-top: -10px;">
+					<el-button type="primary" icon="el-icon-plus" size="mini" @click="atOk()">确定</el-button>
+				</el-form-item>
+			</el-form>
+		</div>
+	</div>
+</template>
+
+<script>
+	module.exports = {
+		data() {
+			return {
+				atTitle: '',		// 添加窗口时: 标题
+				atUrl: '',			// 添加窗口时: 地址 
+			}
+		},
+		methods: {
+			// 双击tab栏空白处, 打开弹窗添加窗口 
+			atOpen: function() {
+				window.r_layer_12345678910 = layer.open({
+					type: 1,
+					// shade: false,
+					shade: 0.5,
+					title: "添加新窗口", //不显示标题
+					content: $('.at-form-dom'), //捕获的元素
+					cancel: function(){
+						
+					}
+				});
+			},
+			// 根据表单添加新窗口 
+			atOk: function() {
+				if(this.atTitle == '' || this.atUrl == '') {
+					return;
+				}
+				this.$root.showTab({id: new Date().getTime(), name: this.atTitle, url: this.atUrl});
+				layer.close(window.r_layer_12345678910);
+				this.atTitle = '';
+				this.atUrl = '';
+			},
+		},
+		created() {
+			
+		}
+	}
+</script>
+
+<style scoped>
+</style>

+ 160 - 0
sa-frame/nav/com-right-menu.vue

@@ -0,0 +1,160 @@
+<template>
+	<!-- 鼠标右键弹出的盒子 -->
+	<!-- 【向下展开动画,坐标平移动画】二者只可得其一 -->
+	<div class="right-box" :style="rightStyle" v-show="rightShow" tabindex="-1" @blur="right_closeMenu2()">
+		<div class="right-box-2">
+			<div @click="right_closeMenu(); right_f5()"><i class="el-icon-caret-right"></i>刷新</div>
+			<div @click="right_closeMenu(); right_copy()"><i class="el-icon-caret-right"></i>复制</div>
+			<div @click="right_closeMenu(); right_close()"><i class="el-icon-caret-right"></i>关闭</div>
+			<div @click="right_closeMenu(); right_close_other()"><i class="el-icon-caret-right"></i>关闭其它</div>
+			<div @click="right_closeMenu(); right_close_all()"><i class="el-icon-caret-right"></i>关闭所有</div>
+			<div @click="right_closeMenu(); right_xf()"><i class="el-icon-caret-right"></i>悬浮打开</div>
+			<div @click="right_closeMenu(); right_window_open()"><i class="el-icon-caret-right"></i>新窗口打开</div>
+			<div @click="right_closeMenu2();"><i class="el-icon-caret-right"></i>取消</div>
+		</div>
+	</div>
+</template>
+
+<script>
+	module.exports = {
+		data() {
+			return {
+				rightShow: false,	// 右键菜单是否正在显示 
+				rightTab: null,		// 右键菜单正在操作的 tab 
+				rightStyle: {		// 右键菜单的 style 样式 
+					left: '0px',		// 坐标x 
+					top: '0px',			// 坐标y 
+					maxHeight: '0px'	// 右键菜单的最高高度 (控制是否展开) 
+				},
+			}
+		},
+		methods: {
+			// 展开右键菜单
+			right_showMenu: function(tab, event) {
+				this.rightTab = tab;	// 绑定操作tab  
+				var e = event || window.event;
+				this.rightStyle.left = (e.clientX + 1) + 'px';	// 设置给坐标x
+				this.rightStyle.top = e.clientY + 'px';		// 设置给坐标y
+				this.rightShow = true;	// 显示右键菜单 
+				this.$nextTick(function() {
+					var foxHeight = document.querySelector('.right-box-2').offsetHeight;	// 应该展开多高 
+					this.rightStyle.maxHeight = foxHeight + 'px';	// 展开 
+					document.querySelector('.right-box').focus();		// 获得焦点,以被捕获失去焦点事件
+				});
+			},
+			// 关闭右键菜单 - 立即关闭
+			right_closeMenu: function() {
+				this.rightStyle.maxHeight = '0px';	
+				this.rightShow = false;
+			},
+			// 关闭右键菜单 - 带动画折叠关闭 (失去焦点和点击取消时调用, 为什么不全部调用这个? 因为其它时候调用这个都太卡了) 
+			right_closeMenu2: function() {
+				this.rightStyle.maxHeight = '0px';	
+				// this.rightShow = false;
+			},
+			// 右键 - 刷新
+			right_f5: function() {
+				this.$root.showTab(this.rightTab);	// 先转到 
+				this.$root.f5Tab(this.rightTab);
+			},
+			// 右键 - 复制
+			right_copy: function() {
+				this.$root.showTab({name: this.rightTab.name, url: this.$root.getTabUrl(this.rightTab)});
+			},
+			// 右键 - 悬浮 
+			right_xf: function() {
+				this.$root.closeTab(this.rightTab);   
+				this.$root.xfTab(this.rightTab);
+			},
+			// 右键 - 新窗口打开
+			right_window_open: function() {
+				// this.$root.closeTab(this.rightTab); 
+				this.$root.newWinTab(this.rightTab); 
+			},
+			// 右键 - 关闭 
+			right_close: function() {
+				if(this.rightTab == this.$root.homeTab){
+					return this.$message({
+						dangerouslyUseHTMLString: true,
+						message: '<b>这个不能关闭哦</b>',
+						type: 'warning',
+						showClose: true,
+					});
+				}
+				this.$root.closeTab(this.rightTab);
+			},
+			// 右键 - 关闭其它 
+			right_close_other: function() {
+				var root = this.$root;
+				// 先滑到最左边 
+				root.$refs['nav-tab-bar'].scrollX = 0;	
+				// 递归删除 
+				var i = 0;
+				var deleteFn = function() {
+					// 如果已经遍历全部 
+					if(i >= root.tabList.length) {
+						return;
+					}
+					// 如果在白名单,i++继续遍历, 如果不是,递归删除 
+					var tab = root.tabList[i];
+					if(tab == root.homeTab || tab == this.rightTab){	
+						i++;
+						deleteFn();
+					} else {
+						root.closeTab(tab, function() {
+							deleteFn();
+						});
+					}
+				}.bind(this);
+				deleteFn();
+			},
+			// 右键 - 关闭所有 
+			right_close_all: function() {
+				var root = this.$root;
+				// 先滑到最左边 
+				root.$refs['nav-tab-bar'].scrollX = 0;	
+				// 递归删除 
+				var i = 0;
+				var deleteFn = function() {
+					// 如果已经遍历全部 
+					if(i >= root.tabList.length) {
+						return;
+					}
+					// 如果在白名单,i++继续遍历, 如果不是,递归删除 
+					var tab = root.tabList[i];
+					if(tab == root.homeTab){	
+						i++;
+						deleteFn();
+					} else {
+						root.closeTab(tab, function() {
+							deleteFn();
+						});
+					}
+				}.bind(this);
+				deleteFn();
+			},
+			
+		},
+		created() {
+			
+		}
+	}
+</script>
+
+<style scoped>
+	
+	/* 右键菜单 样式 */
+	.right-box {
+		position: fixed;
+		z-index: 2147483647;
+		transition: max-height 0.2s;
+		outline:none;
+		max-height: 0px;
+		overflow: hidden;
+		box-shadow: 1px 1px 2px #000;
+	}
+	.right-box-2{font-size: 0.8em; padding: 0.5em 0; border: 1px #aaa solid; border-radius: 1px; background-color: #FFF;}
+	.right-box-2>div {line-height: 2.2em; padding-left: 0.7em; padding-right: 1.8em; cursor: pointer; white-space: nowrap;}
+	.right-box-2>div:hover {background-color: #ddd;color: #2D8CF0;}
+	.right-box-2>div i{ margin-right: 8px;}
+</style>

+ 37 - 0
sa-frame/nav/nav-logo.vue

@@ -0,0 +1,37 @@
+<!-- 左上:logo部分 -->
+<template>
+	<div class="com-logo-box" :title="$root.title" @click="$root.showHome()">
+		<img :src="$root.logo" class="admin-logo" v-if="$root.logo">
+		<span class="admin-title">{{$root.title}}</span>
+	</div>
+</template>
+
+<script>
+	module.exports = {
+		data() {
+			return {
+			}
+		},
+		methods: {
+		},
+		created() {
+		}
+	}
+</script>
+
+<style scoped>
+	.com-logo-box {
+		cursor: pointer;
+	}
+
+	.admin-logo {
+		width: 40px;
+		height: 40px;
+		border-radius: 50%;
+		vertical-align: middle;
+		margin-left: 22px;
+	}
+	
+	.admin-title{padding-right: 0.5em; margin-left: 0.5em; font-size: 1.05em;}
+	
+</style>

+ 133 - 0
sa-frame/nav/nav-menu-bar.vue

@@ -0,0 +1,133 @@
+<template>
+	<!-- 左下:菜单栏 -->
+	<div class="menu-box-1">
+		<div class="menu-box-2">
+			<!-- 
+				菜单:
+					unique-opened = 是否只有菜单打开 
+					default-active = 正在高亮的菜单id   
+					collapse = 是否折叠
+					参考文档:https://element.eleme.cn/#/zh-CN/component/menu
+			-->
+			<el-menu 
+				class="el-menu-style-1" 
+				:default-active="$root.activeMenuId" 
+				:collapse="!$root.isOpen"
+				@select="selectMenu" 
+				>
+				<div v-for="(menu, index) in $root.menuList" v-if="!menu.show" :key="index">
+					<!-- 1 如果是子菜单 -->
+					<el-menu-item v-if="!menu.parent && menu.isShow !== false && $root.showList.indexOf(menu.id) > -1" :index="menu.id + '' ">
+						<span class="menu-i"><i :class="menu.icon" :title="menu.name"></i></span>
+						<span class="menu-name">{{menu.name}}</span>
+					</el-menu-item>
+					<!-- 1 如果是父菜单 -->
+					<el-submenu v-if="menu.parent && menu.isShow !== false && $root.showList.indexOf(menu.id) > -1" :index="menu.id + '' ">
+						<template slot="title">
+							<span class="menu-i"><i :class="menu.icon" :title="menu.name"></i></span>
+							<span class="menu-name">{{menu.name}}</span>
+						</template>
+						<!-- 遍历其子项 -->
+						<div v-for="(menu2, index) in menu.childList" :key="index">
+							<!-- 2 如果是子菜单 -->
+							<el-menu-item v-if="!menu2.parent && menu2.isShow !== false && $root.showList.indexOf(menu2.id) > -1" :index="menu2.id + '' ">
+								<span class="menu-i"><i :class="menu2.icon" :title="menu2.name"></i></span>
+								<span class="menu-name">{{menu2.name}}</span>
+							</el-menu-item>
+							<!-- 2 如果是父菜单 -->
+							<el-submenu v-if="menu2.parent && menu2.isShow !== false && $root.showList.indexOf(menu2.id) > -1" :index="menu2.id + '' ">
+								<template slot="title">
+									<span class="menu-i"><i :class="menu2.icon" :title="menu2.name"></i></span>
+									<span class="menu-name">{{menu2.name}}</span>
+								</template>
+								<!-- 遍历其子项 -->
+								<div v-for="(menu3, index) in menu2.childList" :key="index">
+									<!-- 3 如果是子菜单 -->
+									<el-menu-item v-if="!menu3.parent && menu3.isShow !== false && $root.showList.indexOf(menu3.id) > -1" :index="menu3.id + '' ">
+										<span class="menu-i"><i :class="menu3.icon" :title="menu3.name"></i></span>
+										<span class="menu-name">{{menu3.name}}</span>
+									</el-menu-item>
+									<!-- 3 如果是父菜单 -->
+									<el-submenu v-if="menu3.parent && menu3.isShow !== false && $root.showList.indexOf(menu3.id) > -1" :index="menu3.id + '' ">
+										<template slot="title">
+											<span class="menu-i"><i :class="menu3.icon" :title="menu3.name"></i></span>
+											<span class="menu-name">{{menu3.name}}</span>
+										</template>
+										<!-- 4 -->
+										<div v-for="(menu4, index) in menu3.childList" :key="index">
+											<el-menu-item v-if="menu4.isShow !== false && $root.showList.indexOf(menu4.id) > -1" :index="menu4.id + '' ">
+												<span class="menu-i"><i :class="menu4.icon" :title="menu4.name"></i></span>
+												<span class="menu-name">{{menu4.name}}</span>
+											</el-menu-item>
+										</div>
+									</el-submenu>
+								</div>
+							</el-submenu>
+						</div>
+					</el-submenu>
+				</div>
+			</el-menu>
+			<!-- tab被拖拽时的遮罩(左拖拽:关闭) -->
+			<div class="shade-fox" v-if="$root.isDrag" 
+				@dragover="$event.preventDefault();" 
+				@drop="$event.preventDefault(); $event.stopPropagation(); $root.$refs['com-right-menu'].rightTab = $root.dragTab; $root.$refs['com-right-menu'].right_close();">
+				<span style="font-size: 16px;">关闭</span>
+			</div>
+		</div>
+	</div>
+</template>
+
+<script>
+	module.exports = {
+		data() {
+			return {
+				
+			}
+		},
+		methods: {
+			// 点击子菜单时触发的回调  
+			// 参数:index=点击菜单index标识(不是下标,是菜单id), 
+			// 		indexArray=所有已经打开的菜单id数组,形如:['1', '1-1', '1-1-1'] 
+			selectMenu: function(index, indexArray) {
+				this.$root.showMenuById(index);
+			},
+		},
+		created() {
+		}
+	}
+</script>
+
+<style scoped>
+	/* 1 2 配合,把滚动条隐藏 */
+	.menu-box-1{width: calc(var(--nav-left-width) + 20px); height: 100%; overflow-y: auto;}
+	.menu-box-2{width: calc(var(--nav-left-width) + 1px); padding-bottom: 200px;}
+	
+	.menu-box-1 i[class^=el-icon-]{font-size: 16px;}
+	.menu-box-2 .menu-i{display: inline-block; vertical-align: top; width: 29px;}
+	
+	/* 动画速度加快 */
+	.menu-box-1,.menu-box-2 *{transition: all 0.2s;}
+	
+	/* 隐藏右边框 */
+	.el-menu{border: 0px;}
+	
+	/* 一级菜单,高度45px */
+	.el-menu-item,
+	.el-submenu__title{height: 45px !important; line-height: 45px !important;}
+	
+	/* 二级以下菜单,高度40px */
+	.el-submenu .el-menu-item,
+	.el-submenu .el-submenu .el-submenu__title{height: 40px !important; line-height: 40px !important;}
+	
+	/* 二级菜单 左边距 */
+	.el-submenu .el-menu-item,
+	.el-submenu .el-submenu .el-submenu__title{padding-left: 2.5em !important;}
+	
+	/* 三级菜单 左边距 */
+	.el-submenu .el-submenu .el-menu-item,
+	.el-submenu .el-submenu .el-submenu .el-submenu__title{padding-left: 3.6em !important;}
+	
+	/* 四级菜单 左边距 */
+	.el-submenu .el-submenu .el-submenu .el-menu-item{padding-left: 4.7em !important;}
+	
+</style>

+ 158 - 0
sa-frame/nav/nav-tab-bar.vue

@@ -0,0 +1,158 @@
+<template>
+	<!-- 右边,第二行:tab栏 -->
+	<div class="towards-box">
+		<div class="towards-left" @click="scrollToLeft()" title="向左滑">
+			<i class="el-icon-arrow-left"></i>
+		</div>
+		<div class="towards-middle" @dblclick="$root.$refs['com-add-tab'].atOpen()" @drop="$event.preventDefault(); $event.stopPropagation();">
+			
+			<div class="tab-title-box" :style="{left: scrollX + 'px'}" @dblclick.stop="">
+				<vuedraggable v-model="$root.tabList" chosen-class="chosen-tab" animation="500" >
+			    	<div 
+			    		v-for="tab in $root.tabList" 
+			    		:key="tab.id"
+			    		:id=" 'tab-' + tab.id " 
+			    		class="tab-title" 
+			    		:class=" (tab == $root.nativeTab ? 'tab-native' : '') " 
+			    		@click="$root.showTab(tab)"
+			    		@contextmenu.prevent="$root.$refs['com-right-menu'].right_showMenu(tab, $event)"
+			    		draggable="true"
+			    		@dragstart="$root.isDrag = true; $root.dragTab = tab"
+			    		@dragend="$root.isDrag = false;"
+			    		>
+			    		<div class="tab-title-2">
+			    			<!-- <i class="el-icon-caret-right"></i> -->
+			    			<span>{{tab.name}}</span>
+			    			<i class="el-icon-close" v-if="!tab.hideClose" @click.stop="$root.closeTab(tab)"></i> 
+			    		</div>
+			    	</div>
+				</vuedraggable>
+			</div>
+			
+			
+		</div>
+		<div class="towards-right" @click="scrollToRight()" title="向右滑">
+			<i class="el-icon-arrow-right"></i>
+		</div>
+	</div>
+</template>
+
+<script>
+	module.exports = {
+		components: {
+			"vuedraggable": window.vuedraggable,	// vuedraggable 
+		},
+		data() {
+			return {
+				scrollX: 0		,// 滚动条位置 
+			}
+		},
+		methods: {
+			// ------------------- tab左右滑动  -------------------- 
+			// 视角向左滑动一段距离 
+			scrollToLeft: function(scroll_width) {
+				var width = document.querySelector('.nav-right-2').clientWidth;	// 视角宽度
+				this.scrollX += scroll_width || width / 8;	// 视角向左滑动一段距离
+				// 越界检查
+				setTimeout(function() {
+					if(this.scrollX > 0){
+						this.scrollX = 0;
+					}
+				}.bind(this), 200);
+			},
+			// 视角向右滑动一段距离 
+			scrollToRight: function(scroll_width) {
+				var width = document.querySelector('.nav-right-2').clientWidth;	// 视角宽度
+				var tabListWidth = document.querySelector('.tab-title-box').clientWidth;	// title总盒子宽度
+				var rightLimit = (0 - tabListWidth + width / 2);	// 右滑的极限
+				this.scrollX -= scroll_width || width / 8;		// 视角向右滑动一段距离
+				// 越界检查
+				setTimeout(function() {
+					if(this.scrollX < rightLimit){
+						this.scrollX = rightLimit;
+					}
+					// 同时防止左边越界 
+					if(this.scrollX > 0){
+						this.scrollX = 0;
+					}
+				}.bind(this), 200);
+			},
+			// 自动归位
+			scrollToAuto: function() {
+				// console.log('自动归位=========');
+				try{
+					// 最后一个不用归位了 
+					// if(this.nativeTab == this.tabList[this.tabList.length - 1]){
+					// 	return;
+					// }
+					var width = document.querySelector('.nav-right-2').clientWidth;	// 视角宽度
+					var left = document.querySelector('.tab-native').lastChild.offsetLeft;	// 当前native-tilte下一个距离左边的距离
+					// console.log(width, left, this.scrollX);
+					// 如果在视图右边越界
+					if(left + this.scrollX > (width - 200)){
+						return this.scrollToRight();
+					}
+					// 如果在视图左边越界 
+					if(left + this.scrollX < 0) {
+						return this.scrollToLeft();
+					}
+				}catch(e){
+					// throw e;
+				}
+			},
+			// 让鼠标滚轮变为横向滚动
+			initScroll: function() {
+				var scroll_width = 60;  // 设置每次滚动的长度,单位 px
+				var scroll_events = "mousewheel DOMMouseScroll MozMousePixelScroll";  // 鼠标滚轮滚动事件名
+				$('.towards-middle').on(scroll_events, function(e) {
+					var delta = e.originalEvent.wheelDelta;  // 鼠标滚轮滚动度数
+					// 滑轮向上滚动,滚动条向左移动,scrollleft-
+					if(delta > 0) {
+						this.scrollToLeft(scroll_width);
+					}
+					// 滑轮向下滚动,滚动条向右移动,scrollleft+
+					else {
+						this.scrollToRight(scroll_width);
+					}
+				}.bind(this));
+			}
+		},
+		created() {
+			this.$nextTick(function() {
+				this.initScroll();
+			})
+		}
+	}
+</script>
+
+<style scoped>
+	
+	.towards-box>div{height: 100%; position: absolute;}
+	
+	.towards-left,.towards-right{width: 24px; text-align: center; background-color: #FFF; cursor: pointer;} 
+	.towards-left{border-right: 1px #fff solid;}
+	.towards-right{border-left: 1px #fff solid; right: 0px;}
+	.towards-left:hover i,.towards-right:hover i{font-weight: 700;/* font-weight: bold; */}
+	
+	.towards-middle{width: 10000px; overflow: auto;/* calc(100% - 50px) */ left: 25px;background-color: #FFF;}
+	.tab-title-box{display: inline-block; position: absolute; left: 0px; transition: all 0.2s;}
+	.tab-title{font-size: 12px; cursor: pointer; float: left; white-space: nowrap; overflow: hidden; text-decoration: none; color: #333;}
+	.tab-title-2{padding: 0px 10px; /* background-color: #FFF; */ }
+	.tab-title-2{transition: padding 0.1s, margin 0.1s;}
+	/* .tab-title .el-icon-caret-right{color: #EEE; font-size: 1.7em; position: relative; top: 4px;} */
+	.tab-title .el-icon-close{display: inline-block; border-radius: 50%; padding: 1px; color: #ccc; margin-left: -8px;}
+	.tab-title .el-icon-close:hover{background-color: red; color: #FFF;}
+	.tab-title span{display: inline-block; margin-left: 10px; margin-right: 10px;}
+	.tab-title:hover span,.tab-native span{/* font-weight: bold; */}
+	
+	
+	/* 卡片样式 */
+	/* .tab-title-box>div{line-height: 35px;} */
+	.tab-title{transition: width 0.2s, background 0s, border 0.2s;}
+	.tab-native{transition: width 0.2s, background 0.2s, border 0.2s;}
+	.tab-title{border-radius: 1.5px; border: 1px #e5e5e5 solid; line-height: 28px; height: 27px; margin: 3px 1.5px; background-color: #fff;}
+	/* .tab-title.tab-native{border: 1px #409EFF solid; background-color: #409EFF; color: #fff; }
+	.tab-title:hover{border: 1px #409EFF solid;} */
+	/* .chosen-tab .tab-title-2{background-color: red;} */
+	
+</style>

+ 332 - 0
sa-frame/nav/nav-tool-bar.vue

@@ -0,0 +1,332 @@
+<!-- 右边第一行,工具栏 -->
+<template>
+	<div class="tools-panel">
+		<div class="tools-left">
+			<span title="折叠菜单" class="tool-fox" v-if="$root.isOpen == true" @click="$root.endOpen()">
+				<i class="el-icon-s-fold"></i>
+			</span>
+			<span title="展开菜单" class="tool-fox" v-if="$root.isOpen == false" @click="$root.startOpen()">
+				<i class="el-icon-s-unfold"></i>
+			</span>
+			<span title="搜索-input" class="tool-fox search-fox" :class=" isSearch ? 'search-fox-show' : '' ">
+				<el-select v-model="searchText" size="mini" filterable placeholder="请输入菜单关键字" ref="search" 
+					@change="findMenuBySearch" @blur="closeSearch" @keyup.esc.native="closeSearch">
+					<el-option v-for="item in searchList" :key="item.id" :label="item.text" :value="item.id"></el-option>
+				</el-select>
+			</span>
+			<span title="搜索菜单" class="tool-fox" @click="closeSearch()" v-if="!isShowSearchInput">
+				<i class="el-icon-search" style="font-weight: bold;"></i>
+			</span>
+			<span title="搜索菜单" class="tool-fox" @click="startSearch()" v-else>
+				<i class="el-icon-search" style="font-weight: bold;"></i>
+			</span>
+			<span title="刷新" class="tool-fox" @click="$root.f5Tab($root.nativeTab)">
+				<i class="el-icon-refresh-right" style="font-weight: bold;"></i>
+			</span>
+			<span title="当前时间" class="tool-fox">
+				<span style="font-size: 0.90em;">{{nowTime}}</span>
+			</span>
+		</div>
+		<div class="tools-right">
+			<span title="模拟登陆" v-if="runAsToken">
+				<span style="font-size: 0.8em; font-weight: bold; ">当前模拟登录账号:{{sa.$sys.getCurrUser().id}},</span>
+				<span style="font-size: 0.8em; color: #44f; text-decoration: underline; cursor: pointer;" @click="$root.closeRunAs()">退出</span>
+			</span>
+			<span title="点击登录" class="tool-fox" onclick="location.href='login.html'" v-if="$root.user == null">
+				<span style="font-size: 0.8em; font-weight: bold; position: relative; top: -2px;">未登录</span>
+			</span>
+			<span title="我的信息" class="tool-fox user-info" style="padding: 0;" v-if="$root.user != null">
+				<el-dropdown @command="handleCommand" trigger="click" size="medium">
+					<span class="el-dropdown-link user-name" style="height: 100%; padding: 0 1em; display: inline-block;">
+						<img :src="$root.user.avatar" class="user-avatar">
+						<span>{{$root.user.username}}</span>
+						<i class="el-icon-arrow-down el-icon--right"></i>
+					</span>
+					<el-dropdown-menu slot="dropdown">
+						<el-dropdown-item v-for="drop in $root.dropList" :command="drop.name" :key="drop.name">{{drop.name}}</el-dropdown-item>
+					</el-dropdown-menu>
+				</el-dropdown>
+			</span>
+			<span title="主题" class="tool-fox" style="padding: 0;">
+				<el-dropdown @command="toggleTheme" trigger="click" size="medium">
+					<span class="el-dropdown-link" style="height: 100%; padding: 0 1em; display: inline-block;">
+						<i class="el-icon-price-tag" style="font-weight: bold;"></i>
+						<span style="font-size: 0.9em;">主题</span>
+					</span>
+					<el-dropdown-menu slot="dropdown">
+						<el-dropdown-item :command="t.value" v-for="t in themeList" :key="t.name">
+							<span :style=" $root.themeV == t.value ? 'color: #44f' : '' ">{{t.name}} </span>
+						</el-dropdown-item>
+					</el-dropdown-menu>
+				</el-dropdown>
+			</span> 
+			<span title="便签" class="tool-fox" @click="openNote()">
+				<i class="el-icon-edit" style="font-weight: bold; font-size: 0.9em;"></i>
+				<span style="font-size: 0.9em;">便签</span>
+			</span>
+			<span title="全屏" class="tool-fox" v-if="isFullScreen == false" @click="fullScreen()">
+				<i class="el-icon-rank" style="font-weight: bold; transform: rotate(45deg)"></i>
+			</span>
+			<span title="退出全屏" class="tool-fox" v-if="isFullScreen == true" @click="outFullScreen()">
+				<i class="el-icon-bottom-left" style="font-weight: bold; "></i>
+			</span>
+		</div>
+		<!-- tab被拖拽时的遮罩(tab上拖拽:新窗口打开) -->
+		<div class="shade-fox" v-if="$root.isDrag" 
+			@dragover="$event.preventDefault();" 
+			@drop="$event.preventDefault(); $event.stopPropagation(); $root.newWinTab($root.dragTab);">
+			<span style="font-size: 16px;">新窗口打开</span>
+		</div>
+	</div>
+</template>
+
+<script>
+	module.exports = {
+		data() {
+			return {
+				isSearch: false,	// 当前是否处于搜索模式 
+				isShowSearchInput: true,	// 是否显示打开搜索图标 
+				searchText: '',		// 搜索框已经输入的字符 
+				searchList: [],			// 搜索框 待选列表 
+				
+				isFullScreen: false,	// 是否处于全屏状态 
+				
+				nowTime: '加载中...'	,	// 当前时间 
+				currInterval: null,		// 刷新当前时间的定时器 
+				
+				runAsToken: sessionStorage.runAsToken,	// 模拟登陆-Token
+				
+				themeList: [	// 主题数组
+					{name: '蓝色', value: '1'},
+					{name: '绿色', value: '2'},
+					{name: '白色', value: '3'},
+					{name: '灰色', value: '4'},
+					{name: '红色', value: '5'},
+					{name: '紫色', value: '9'},
+					{name: 'pro钛合金', value: '6'},
+					{name: '沉淀黑蓝', value: '7'},
+					{name: '简约灰色', value: '8'},
+					{name: '简约草绿', value: '10'},
+				],
+				
+			}
+		},
+		methods: {
+			// ------------------------------ 搜索相关 ------------------------------
+			// 开启搜索
+			startSearch: function() {
+				this.searchText = '';
+				this.isSearch = true;
+				this.f5SearchList();
+				setTimeout(function() {
+					this.isShowSearchInput = false;
+					this.$refs['search'].focus();	//.$refs['nav-tool-bar'].
+				}.bind(this), 200);
+			},
+			// 关闭搜索
+			closeSearch: function() {
+				this.searchText = '';
+				this.isSearch = false;
+				setTimeout(function() {
+					try{
+						this.isShowSearchInput = true;
+						document.querySelector('body>.el-select-dropdown.el-popper').style.display = 'none';
+					}catch(e){throw e}
+				}.bind(this), 200);
+			},
+			// 查找菜单 
+			findMenuBySearch: function(id) {
+				this.$root.showMenuById(id);
+				this.closeSearch();
+			},
+			// 刷新待选列表 
+			f5SearchList: function() {
+				var searchList = [];
+				
+				let index = 1;
+				function push(id, str) {
+					searchList.push({id: id, text: (index++) + ". " + str});
+				}
+				
+				// 遍历菜单 
+				let childList = this.$root.menuList;
+				let showList = this.$root.showList;
+				for (let menu1 of childList) {
+					if(menu1.isShow === false || showList.indexOf(menu1.id + '') == -1) continue;
+					if(menu1.childList) {
+						for (let menu2 of menu1.childList) {
+							if(menu2.isShow === false || showList.indexOf(menu2.id + '') == -1) continue;
+							if(menu2.childList) {
+								for (let menu3 of menu2.childList) {
+									if(menu3.isShow === false || showList.indexOf(menu3.id + '') == -1) continue;
+									if(menu3.childList) {
+										for (let menu4 of menu3.childList) {
+											if(menu4.isShow === false || showList.indexOf(menu4.id + '') == -1) continue;
+											push(menu4.id, menu1.name + ' > ' + menu2.name + ' > ' + menu3.name + ' > ' + menu4.name);
+										}
+									} else {
+										push(menu3.id, menu1.name + ' > ' + menu2.name + ' > ' + menu3.name);
+									}
+								}
+							} else {
+								push(menu2.id, menu1.name + ' > ' + menu2.name);
+							}
+						}
+					} else {
+						push(menu1.id, menu1.name);
+					}
+				}
+				
+				this.searchList = searchList;
+			},
+			
+			// ------------------------------ 主题 ------------------------------
+			// 切换主题
+			toggleTheme: function(command) {
+				// 开始切换
+				this.$root.themeV = command + "";
+				localStorage.setItem('themeV', command);
+				for (var i = 0; i < this.themeList.length; i++) {
+					if(this.themeList[i].value + '' == command + '') {
+						this.$message('切换成功,' + this.themeList[i].name);
+					}
+				}
+			},
+			
+			// ------------------------------ 全屏 ------------------------------
+			// 进入全屏 
+			fullScreen: function() {
+				this.isFullScreen = true;
+				if(document.documentElement.RequestFullScreen){
+					document.documentElement.RequestFullScreen();
+				}
+				//兼容火狐
+				if(document.documentElement.mozRequestFullScreen){
+					document.documentElement.mozRequestFullScreen();
+				}
+				//兼容谷歌等可以webkitRequestFullScreen也可以webkitRequestFullscreen
+				if(document.documentElement.webkitRequestFullScreen){
+					document.documentElement.webkitRequestFullScreen();
+				}
+				//兼容IE,只能写msRequestFullscreen
+				if(document.documentElement.msRequestFullscreen){
+					document.documentElement.msRequestFullscreen();
+				}
+			},
+			// 退出全屏
+			outFullScreen: function() {
+				this.isFullScreen = false;
+				if(document.exitFullScreen){
+					document.exitFullscreen()
+				}
+				//兼容火狐
+				if(document.mozCancelFullScreen){
+					document.mozCancelFullScreen()
+				}
+				//兼容谷歌等
+				if(document.webkitExitFullscreen){
+					document.webkitExitFullscreen()
+				}
+				//兼容IE
+				if(document.msExitFullscreen){
+					document.msExitFullscreen()
+				}
+			},
+			
+			// ------------------------------ 其它 ------------------------------
+			// 处理userinfo的下拉点击
+			handleCommand: function(command) {
+				this.$root.dropList.forEach(function(drop) {
+					if(drop.name == command) {
+						drop.click();
+					}
+				})
+			},
+			// 打开便签
+			openNote: function() {
+				var w = (document.body.clientWidth * 0.4) + 'px';
+				var h = (document.body.clientHeight * 0.6) + 'px';
+				var default_content = '一个简单的小便签, 关闭浏览器后再次打开仍然可以加载到上一次的记录, 你可以用它来记录一些临时资料';
+				var value = localStorage.getItem('sa_admin_note') || default_content;
+				var index = layer.prompt({
+					title: '一个小便签', 
+					value: value,
+					formType: 2,
+					area: [w, h],
+					btn: ['保存'],
+					maxlength: 99999999,
+					skin: 'layer-note-class' 
+				}, function(pass, index){
+					layer.close(index)					
+				});
+				var se = '#layui-layer' + index + ' .layui-layer-input';
+				var d = document.querySelector(se);
+				d.oninput = function() {
+					localStorage.setItem('sa_admin_note', this.value);
+				}
+			},
+			
+			// 刷新时间
+			initInterval: function() {
+				if(this.currInterval) {
+					clearInterval(this.currInterval);
+				}
+				// 一直更新时间
+				this.currInterval = setInterval(function() {
+					var da = new Date();
+					var Y = da.getFullYear(); //年
+					var M = da.getMonth() + 1; //月
+					var D = da.getDate(); //日
+					var h = da.getHours(); //小时
+					var sx = "凌晨";
+					if (h >= 6) {
+						sx = "上午"
+					}
+					if (h >= 12) {
+						sx = "下午";
+						if (h >= 18) {
+							sx = "晚上";
+						}
+						h -= 12;
+					}
+					var m = da.getMinutes(); //分
+					var s = da.getSeconds(); //秒
+					var z = ['日', '一', '二', '三', '四', '五', '六'][da.getDay()] ; //周几
+					// z = z == 0 ? '日' : z;
+					var zong = "";
+				
+					zong += Y + "-" + M + "-" + D + " " + sx + " " + h + ":" + m + ":" + s + " 周" + z;
+					this.nowTime = zong;
+				}.bind(this), 1000);
+			}
+		
+		},
+		created() {
+			this.initInterval();
+		}
+	}
+</script>
+
+<style scoped>
+	
+	.tools-left{border: 0px #000 solid; float: left;}
+	.tools-right{float: right;}
+	.tool-fox{padding: 0 1em; display: inline-block; cursor: pointer;}
+	.tool-fox, .tool-fox i{transition: all 0.2s;}
+	
+	.user-info{position: relative; top: -2px;}
+	.user-avatar{width: 30px; height: 30px; border-radius: 50%; vertical-align: middle;}
+	.user-info .user-name{font-size: 0.9em;} 
+	
+	/* 搜素框 */
+	.search-fox{display: inline-block; vertical-align: middle; overflow: hidden; max-width: 0px; padding: 0em 0em; margin-left: -5px; transition: all 0.2s;}
+	.search-fox-show{display: inline-block; max-width: 500px; margin-left: 0px; padding: 0 1em;}
+	.search-fox:hover{background-color: rgba(0,0,0,0) !important;}
+	.search-fox .el-input__inner{border-radius: 0px; border-width: 0px; border-bottom-width: 1px; background-color: rgba(0,0,0,0);}
+	.search-fox .el-input__icon{display: none;}
+	
+	/*800之下*/
+	@media(max-width: 800px) {
+		.tools-right{display: none;}
+	}
+</style>

+ 57 - 0
sa-frame/nav/nav-view-vessel.vue

@@ -0,0 +1,57 @@
+<template>
+	<div class="view-vessel">
+		<div class="a-view" v-for="tab in $root.viewList" :key="tab.id" :class="tab == $root.nativeTab ? 'a-view-native' : null">
+			<!-- vue视图 -->
+			<template v-if="tab.view">
+				<component :is="tab.view" class="vue-com-view" v-if="tab.isNeedLoad"></component>
+			</template>
+			<!-- iframe视图 -->
+			<template v-else>
+				<iframe :src="tab.url" :id=" 'iframe-' + tab.id " v-if="tab.isNeedLoad" @load="onloadIframe(tab.id)"></iframe>
+			</template>
+		</div>
+		<!-- tab被拖拽时的遮罩(下托拽:悬浮打开) -->
+		<div class="shade-fox" v-if="$root.isDrag" 
+			@dragover="$event.preventDefault();" 
+			@drop="$event.preventDefault(); $event.stopPropagation(); $root.xfTab($root.dragTab); $root.closeTab($root.dragTab);">
+			<span style="font-size: 24px;">拖拽至此:悬浮打开</span>
+		</div>
+	</div>
+</template>
+
+<script>
+	module.exports = {
+		data() {
+			return {
+				
+			}
+		},
+		methods: {
+			// iframe加载完毕后清除其背景 loading 图标
+			onloadIframe: function(iframeId) {
+				// console.log('iframeId', iframeId);
+				var iframe = document.querySelector('#iframe-' + iframeId);
+				if(iframe != null) {
+					iframe.parentElement.style.backgroundImage='none';
+				}
+			},
+		},
+		created() {
+			
+		}
+	}
+</script>
+
+<style scoped>
+	
+	.view-vessel{height: 100%; position: relative; border: 0px #000 solid;}
+	.a-view{width: 100%; height: 100%; background-color: #EEE; background: url(../index/admin-loading.gif) no-repeat center 50%; position: absolute; }
+	.a-view{opacity: 0; transition: all 0.2s;}
+	.a-view-native{z-index: 100000; opacity: 1;}
+	
+	.a-view>iframe{width: 100%; height: 100%; border: 0px #000 solid;}
+	.a-view>.vue-com-view{width: 100%; height: 100%; overflow: auto; background-color: #EEE;}
+	
+	/* .iframe-no-scroll{width: calc(100% + 22px); } */
+	
+</style>

+ 148 - 0
sa-frame/sa-code.js

@@ -0,0 +1,148 @@
+// 在使用时,不建议你直接魔改模板的代码,以免在运行时出现意外bug,而是在本文件中根据模板的提供的API,来适应你的业务逻辑 
+// sa-plus 快速开发平台:		http://sa-plus.dev33.cn
+// ....
+
+
+
+// ================================= 示例:一些基本信息 ================================= 
+
+// 设置模板标题 
+// sa_admin.title = "Sa-Admin";
+// sa_admin.logo = 'sa-frame/admin-logo.png';    // 设置logo图标地址   
+// sa_admin.icon = 'sa-frame/admin-logo.png';    // 设置icon图标地址 
+
+
+// ================================= 用户信息 和 菜单 =================================
+sa.ajax('/AccAdmin/getLoginInfo', function(res) {
+	console.log(res)
+	// 验证权限 
+	if(!(res.data.admin && res.data.perList.indexOf('in-system') > -1)) {
+		sa.$sys.setCurrUser(res.data.admin);
+		return sa.alert('当前账号暂无进入后台权限');
+	}	
+	
+	// 配置 
+	sa_admin.title = "智慧防疫后台";
+	sa_admin.logo = 'sa-frame/admin-logo.png';    // 设置logo图标地址 
+	sa_admin.icon = "sa-frame/admin-logo.png";    // 设置logo图标地址 
+	
+	
+	// 当前用户信息 
+	sa_admin.user = {
+		username: res.data.admin.name,
+		avatar: res.data.admin.avatar || 'sa-frame/admin-logo.png' // 使用logo作为头像 
+		// avatar: res.data.admin.avatar // 此写法为账号头像 
+	};		
+	sa.$sys.setCurrUser(res.data.admin);
+	
+	
+	// 所有菜单
+	// var myMenuList = window.menuList;    // window.menuList 在 menu-list.js 中定义 
+	sa_admin.initMenu(res.data.perList);    // 初始化菜单   
+	sa.setAuth(res.data.perList);		// 当前用户权限码集合  
+	
+	// 配置信息 
+	sa.$sys.setAppCfg(res.appCfg);
+	
+	// 初始化模板(必须调用) 
+	sa_admin.init();	
+	
+}.bind(this), {msg: '正在加载登录信息', login_url: 'login.html'});
+
+
+
+
+// ================================= 示例:设置登录后的头像处,下拉可以出现的选项  =================================
+sa_admin.dropList = [		// 头像点击处可操作的选项
+	{
+		name: '我的资料',
+		click: function() {
+			sa.showIframe('我的资料', 'sa-view-sp/sp-admin/admin-info.html', '700px', '600px');
+		}
+	},
+    {
+        name: '修改名称',
+        click: function () {
+			layer.prompt({title: '请输入新名称'}, function(pass, index){
+				layer.close(index);
+				sa.ajax('/admin/updateInfo', {name: pass}, function(res){
+					sa_admin.user.username = pass;
+					sa.ok2('修改成功');
+				});
+			});
+        }
+	},
+    {
+        name: '修改密码',
+        click: function () {
+			sa.showIframe('修改密码', 'sa-view-sp/sp-admin/update-password.html', '550px', '350px');
+        }
+	},
+    {
+        name: '切换账号',
+        click: function () {
+			// sa.showIframe('切换账号', 'login.html', '70%', '80%');
+			sa.$page.openLogin('login.html');
+        }
+	},
+	{
+		name: '退出登录',
+		click: function() {
+			layer.confirm('退出登录?', function() {
+				sa.ajax('/AccAdmin/doExit', function(res) {
+					layer.alert('注销成功', function() {
+						location.href="login.html";
+					})
+				})
+			});
+		}
+	}
+]
+
+// 退出模拟登陆的方法
+sa_admin.closeRunAs = function() {
+	layer.confirm('退出模拟登录?', function() {
+		sa.ajax('/AccAdmin/doExit', function(res) {
+			sa.ok('退出成功,即将刷新页面');
+			sessionStorage.removeItem('runAsToken');
+			setTimeout(function() {
+				top.location.reload(true);
+			}, 1000);
+		})
+	});
+}
+
+
+// ================================= 示例:js控制打开某个菜单 =================================
+
+// 显示主页选项卡 
+// sa_admin.showHome();
+
+// 显示一个选项卡, 根据id
+// sa_admin.showTabById('1-1');
+
+// 关闭一个选项卡,根据 id 
+// sa_admin.closeTabById('1-1');
+
+// 新增一个选项卡
+// sa_admin.addTab({id: 12345, name: '新页面', url: 'http://web.yanzhi21.com'});	// id不要和已有的菜单id冲突,其它属性均可参照菜单项 
+
+// 新增一个选项卡、并立即显示  
+// sa_admin.showTab({id: 12345, name: '新页面', url: 'http://web.yanzhi21.com'});	// 参数同上 
+
+// 打开一个 菜单,根据 id
+// sa_admin.showMenuById('1-1');	
+
+
+
+// ================================= 示例:调用另一个页面的代码 =================================
+// var win = sa_admin.getTabWindow('2-1');		// 根据id获取其页面的window对象   (如果此页面未打开,则返回空)(跨域模式下无法获取其window对象)
+// win.app.f5();
+
+// 注意:
+// 根据`iframe`的子父通信原则,在子页面中调用父页面的方法,需要加上parent前缀,例如:
+// parent.sa_admin.msg('啦啦啦');		// 调用父页面的弹窗方法 
+
+
+
+

+ 108 - 0
sa-view-sp/console/com-chart-1.vue

@@ -0,0 +1,108 @@
+<!-- 统计图1 -->
+<template>
+	<div class="echarts-div" id='bar-chart' ref="bar-chart"></div>
+</template>
+
+<script>
+	module.exports = {
+		data() {
+			return {
+				
+			}
+		},
+		methods: {
+			// 刷新柱状图
+			f5BarChart: function() {
+				// ===========================================  定义数据 
+				var x_name = '';	// new Date().getFullYear() + "年"; // x轴名称
+				var y_name = "注册数量"; // y轴名称
+				var dataArray = []; // 坐标X轴数据
+				var valueArray = []; //  坐标Y轴数据
+			
+				var arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
+				for (var i in arr) {
+					i = parseInt(i) + 1;
+					dataArray.push(i + '月');
+					if (i < 10) {
+						i = "0" + i;
+					}
+					i = i + "";
+					valueArray.push(sa.randomNum(100, 1000) || 0);
+				}
+				
+				// ===========================================  开始渲染
+			
+				var ele = this.$refs['bar-chart'];
+				var myChart = echarts.init(ele);
+				var option = {
+					tooltip: {
+						trigger: 'axis',
+						formatter: '{b}<br/> ' + y_name + ':{c}',
+						axisPointer: {
+							type: 'shadow'
+						}
+					},
+					grid:{x: 50, y: 30, x2: 25, y2: 25},	//设置canvas内部表格的内距
+					toolbox: {
+						show: true,
+						top: 0,
+						feature: {
+							saveAsImage: {
+								show: true
+							}
+						}
+					},
+					xAxis: {
+						name: x_name,
+						type: 'category',
+						// axisLabel: {
+						// 	'interval': 0
+						// }, //强制不缩略x轴刻度,
+						data: dataArray
+					},
+					yAxis: {
+						name: y_name,
+						type: 'value'
+					},
+					series: [{
+						name: y_name,
+						data: valueArray,
+						type: 'bar',
+						label: {
+							normal: {
+								show: true,
+								position: 'top',
+								formatter: '{c}'
+							}
+						},
+						itemStyle: {
+							normal: {
+								color: '#5DB1FF',
+								label: {
+									show: true,
+									textStyle: {
+										color: 'black'
+									}
+								}
+							}
+						}
+					}]
+				};
+				myChart.setOption(option);
+				window.myChartList.push(myChart);
+				// window.myChartList[0] = myChart;
+				// myChartList[1] = myChart;
+			},
+		},
+		created() {
+			// 刷新所有图标数据
+			this.$nextTick(function() {
+				this.f5BarChart();
+			});
+		}
+	}
+</script>
+
+<style scoped>
+	
+</style>

+ 114 - 0
sa-view-sp/console/com-chart-2.vue

@@ -0,0 +1,114 @@
+<!-- 统计图2 -->
+<template>
+	<div class="echarts-div" id='pic-chart' ref='pic-chart'></div>
+</template>
+
+<script>
+	module.exports = {
+		data() {
+			return {
+			}
+		},
+		methods: {
+			// 刷新饼图
+			f5PieChart: function() {
+				// ===========================================  定义数据
+				var dataArray = [
+					{name: '昵称注册', value: sa.randomNum(100, 1000)},
+					{name: '手机号注册', value: sa.randomNum(100, 1000)},
+					{name: '微信登陆', value: sa.randomNum(100, 1000)},
+					{name: 'QQ登陆', value: sa.randomNum(100, 1000)},
+					{name: '邮箱登录', value: sa.randomNum(100, 1000)},
+					{name: '小程序登录', value: sa.randomNum(100, 1000)},
+					{name: '管理员添加', value: sa.randomNum(100, 1000)},
+				]; // 坐标X轴数据
+			
+				// ===========================================  开始渲染
+			
+				var myChart = echarts.init(document.getElementById('pic-chart'));
+				option = {
+					title: {
+						text: '账号来源',
+						left: 'left',
+						top: 0,
+						textStyle: {
+							color: '#666',
+							fontSize: '14'
+						}
+					},
+					toolbox: {
+						show: true,
+						top: 0,
+						feature: {
+							saveAsImage: {
+								show: true
+							}
+						}
+					},
+					tooltip: {
+						trigger: 'item',
+						formatter: "{a} <br/>{b} : {c} ({d}%)"
+					},
+					series: [{
+						name: '账号来源',
+						type: 'pie',
+						radius: '70%', // 半径大小
+						center: ['50%', '60%'],
+						selectedMode: 'single',
+						roseType: 'radius',
+						data: dataArray.sort(function(a, b) {
+							return a.value - b.value;
+						}),
+						//roseType: 'radius', // 半径模式还是面积模式
+						itemStyle: {
+							normal: {
+								color: function(params) {
+									// build a color map as your need.
+									var colorList = [
+										'#ff7f50','#87cefa','#da70d6','#32cd32','#6495ed',
+										'#ff69b4','#ba55d3','#cd5c5c','#ffa500','#40e0d0',
+										'#1e90ff','#ff6347','#7b68ee','#00fa9a',
+										'#6699FF','#ff6666','#3cb371','#b8860b','#30e0e0'
+									];
+									// '#ffd700',
+									function GetRandomNum(Min, Max) {
+										var Range = Max - Min;
+										var Rand = Math.random();
+										return (Min + Math.round(Rand * Range));
+									}
+									var index = GetRandomNum(0, colorList.length - 1);
+									return colorList[index];
+									//return colorList[params.dataIndex]
+								}
+							}
+						},
+						label: {
+							normal: {
+								formatter: '{b|{b}:}{c}  {per|{d}%}  ',
+								rich: {}
+							}
+						},
+						// 弹出动画 
+						animationType: 'scale',
+						animationEasing: 'elasticOut',
+						animationDelay: function (idx) {
+							return Math.random() * 200;
+						}
+					}]
+				};
+				myChart.setOption(option);
+				window.myChartList.push(myChart);
+			},
+		},
+		created() {
+			// 刷新所有图标数据
+			this.$nextTick(function() {
+				this.f5PieChart();
+			});
+		}
+	}
+</script>
+
+<style scoped>
+	
+</style>

+ 113 - 0
sa-view-sp/console/com-chart-3.vue

@@ -0,0 +1,113 @@
+<!-- 统计图 3 -->
+<template>
+	<div class="echarts-div" id='line-chart' ref='line-chart'></div>
+</template>
+
+<script>
+	module.exports = {
+		data() {
+			return {
+				
+			}
+		},
+		methods: {
+			// 刷新折线图
+			f5LineChart: function() {
+				// ===========================================  定义数据
+				var x_name = '';	// "活跃数据"; // x轴名称
+				var y_name = "活跃数据"; // y轴名称
+				var typeArray = ['总计登录', '新增注册'];
+				var dataArray = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'];	//   坐标X轴数据
+				var valueArray0 = [84, 126, 262, 201, 148, 133, 86, 186, 232, 215, 326, 412];	// 	
+				var valueArray1 = [284, 296, 382, 501, 348, 273, 266, 327, 412, 515, 526, 712];	// 	
+			
+				// ===========================================  开始渲染
+			
+				var myChart = echarts.init(this.$refs['line-chart']);
+				var option = {
+					tooltip: {
+						trigger: 'axis',
+						axisPointer: {
+							type: 'cross',
+							label: {
+								backgroundColor: '#6a7985'
+							}
+						}
+					},
+					toolbox: {
+						show: true,
+						top: 0,
+						feature: {
+							saveAsImage: {
+								show: true
+							}
+						}
+					},
+					grid:{x: 50, y: 30, x2: 25, y2: 25},	//设置canvas内部表格的内距
+					legend: {
+						data: typeArray
+					},
+					xAxis: {
+						name: x_name,
+						type: 'category',
+						boundaryGap : false,
+						// axisLabel: {
+						// 	'interval': 0
+						// }, //强制不缩略x轴刻度,
+						data: dataArray
+					},
+					yAxis: {
+						name: y_name,
+						type: 'value'
+					},
+					series: [
+						{
+							name: '总计登录',
+							type:'line',
+							data: valueArray1,
+							smooth: true,	// 曲线形式
+							areaStyle: {
+								normal: {
+									color: 'rgba(0, 128, 0, 0.3)' //改变区域颜色
+								}
+							},
+							itemStyle: {
+								normal: {
+									color: 'rgba(0, 128, 0, 0.8)', //改变折线点的颜色
+								}
+							},
+						},
+						{
+							name: '新增注册',
+							type:'line',
+							data: valueArray0,
+							smooth: true,	// 曲线形式
+							areaStyle: {
+								normal: {
+									color: 'rgba(70, 128, 255, 0.3)' //改变区域颜色
+								}
+							},
+							itemStyle: {
+								normal: {
+									color: 'rgba(70, 128, 255, 0.8)', //改变折线点的颜色
+								}
+							},
+						},
+					]
+				};
+				myChart.setOption(option);
+				window.myChartList.push(myChart);
+			},
+		},
+		created() {
+			// 刷新所有图标数据
+			this.$nextTick(function() {
+				this.f5LineChart();
+			});
+		}
+	}
+</script>
+
+<style scoped>
+	
+</style>

+ 49 - 0
sa-view-sp/console/com-intro.vue

@@ -0,0 +1,49 @@
+<template>
+	<div>
+		<el-alert type="success" :closable="false" title="基础">
+			架构:基于iframe,无后台代码,纯html模板,可方便的适配任何后端语言 <br/>
+			模板:提供大量常见示例,以及各种表单的书写方式,助你快速CRUD  <br/>
+			菜单:支持一、二、三级菜单,并开放一系列接口方便的使用js操作菜单 <br/>
+			折叠:折叠或收缩菜单,并且监听窗口大小变化,在拉伸窗口时自动折叠或收缩菜单,自动响应式 <br/>
+			搜索:智能索引,快捷搜索打开某个菜单 <br/>
+			切换:集成swiper动画,滑动、淡入、方块、3D流、3D翻转,五种高大上切换动画,任你选择! <br/>
+			主题:目前保留八种主题:蓝色、绿色、白色、灰色、灰色-展开、pro钛合金、沉淀式黑蓝、简约式灰蓝(切换主题时,可自动保存你的喜好,下次打开时仍然有效) <br/>
+			便签:弹出窗口便签,一个临时小便签,可记录一些临时资料 <br/>
+			全屏:可以在全屏/非全屏之间自由切换 <br/>
+		</el-alert>
+		<el-alert type="warning" :closable="false" title="tabbar栏">	
+			卡片堆积:多卡片自动堆积,与菜单双向关联,切换tab卡时自动显示左侧菜单 <br/>
+			拖动手势:tab卡支持拖拽手势,上拖新窗口打开、下拽悬浮打开、左拽快速关闭 <br/>
+			双击全屏:当卡片属于悬浮状态时,双击标题区域可以快速全屏,再次双击取消全屏 <br/>
+			右键菜单:在tab上右击,可以:刷新、复制、关闭、关闭其它、关闭所有、悬浮打开、新窗口打开、取消 <br/>
+			双击菜单:双击tabbar空白处,可以显示添加新tab窗口 <br/>
+			保留高度:切换卡片时,可自动保留上个卡片的滚动条高度 <br/>
+		</el-alert>
+		<el-alert type="info" :closable="false" title="开放接口">	
+			开放一系列api,助你方便的使用js操作tabbar栏,具体请查看集成文档 <br/>
+			锚链:tab切换自动更改hash锚链,同时监听锚链改变tab,可灵活的用鼠标前后键切换tab窗口 (如不需要此功能可在初始化时关闭) <br/>
+			窗口:可在初始化时,设置是否显示tabbar栏,来控制它是多窗口还是单窗口,具体见使用文档 <br/>
+			更新:功能不断更新中... 你有好的想法也可以告诉我,加群一起交流吧 <br/>
+			文档:使用说明,见文档 <br/>
+		</el-alert>
+	</div>
+</template>
+
+<script>
+	module.exports = {
+		data() {
+			return {
+				
+			}
+		},
+		methods: {
+			
+		},
+		created() {
+			
+		}
+	}
+</script>
+
+<style scoped>
+</style>

+ 47 - 0
sa-view-sp/console/com-origin.vue

@@ -0,0 +1,47 @@
+<template>
+	<div>
+		<el-alert type="success" :closable="false" title="缘起" style="border: 0px;">
+			<p>虽然已经用过不少优秀的后台模板,但是一直都感觉不尽完美,于是在经过激烈的思想斗争后,我决定牺牲五一假期,
+			亲自做一个尝试一下, 一来正好以后自己的项目中使用,二来也算是为开源界做一点自己的贡献吧</p>
+			<p>开源不易,求鼓励,求star</p>
+		</el-alert>
+		<el-alert type="warning" :closable="false" title="Sa-Admin 介绍">
+			<p>Sa-Admin 是一个多窗口后台模板,纯 html 无后端代码,无需脚手架即可直接运行,流畅、易上手、提高生产力。核心技术栈:Vue + Element-UI + jquery + layer。</p>
+			<div style="height: 10px;"></div>
+			<p>
+				Sa-Admin 最大的特点是无需搭建 vue-cli 脚手架,随便一个 html 预览工具(比如 
+				<el-link style="font-size: 12px; color: #999;" href="https://www.dcloud.io/hbuilderx.html" target="_blank">HBuilderX</el-link>
+				)即可直接运行(采用 http-vue-loader 技术实现)。
+				更多信息请参考项目开源首页。
+			</p>
+		</el-alert>
+		<el-alert type="info" :closable="false" title="功能说明">
+			<li>视图:支持 iframe 和 .vue 两种视图方式,支持一、二、三、四级菜单。</li>
+			<li>操作:工具栏提供常见操作按钮:折叠、搜索、刷新、账号、便签、主题切换、全屏切换。</li>
+			<li>主题:内置十种主题,也可方便的扩展主题。</li>
+			<li>切换:支持拖拽排序、切换视图自动记录hash,刷新页面自动打开上次的视图。</li>
+			<li>右键:tabbar栏支持右键菜单:悬浮打开、新窗口打开、视图复制、快捷关闭等操作。</li>
+			<li>接口:开放一系列api,可方便的用js新建、打开、切换视图等动作。</li>
+			<li>示例:提供大量常见示例,以及各种表单组件的封装,助你快速CRUD。</li>
+		</el-alert>
+	</div>
+</template>
+
+<script>
+	module.exports = {
+		data() {
+			return {
+				
+			}
+		},
+		methods: {
+			
+		},
+		created() {
+			
+		}
+	}
+</script>
+
+<style scoped>
+</style>

+ 129 - 0
sa-view-sp/console/com-sta-data.vue

@@ -0,0 +1,129 @@
+<!-- 第一行统计数据 -->
+<template>
+	<el-row :gutter="14">
+		<el-col :lg="4" :sm="8" :xs="24">
+			<div class="sa-wnk">
+				<img src="../../static/icon/icon-user.png" >
+				<div class="sa-wnk-tv">
+					<p class="sa-wnk-title">用户</p>
+					<p class="sa-wnk-value">{{sta.userCount}}</p>
+				</div>
+			</div>
+		</el-col>
+		<el-col :lg="4" :sm="8" :xs="24">
+			<div class="sa-wnk">
+				<img src="../../static/icon/icon-goods.png" >
+				<div class="sa-wnk-tv">
+					<p class="sa-wnk-title">商品</p>
+					<p class="sa-wnk-value">{{sta.goodsCount}}</p>
+				</div>
+			</div>
+		</el-col>
+		<el-col :lg="4" :sm="8" :xs="24">
+			<div class="sa-wnk">
+				<img src="../../static/icon/icon-order.png" >
+				<div class="sa-wnk-tv">
+					<p class="sa-wnk-title">订单</p>
+					<p class="sa-wnk-value">{{sta.orderCount}}</p>
+				</div>
+			</div>
+		</el-col>
+		<el-col :lg="4" :sm="8" :xs="24">
+			<div class="sa-wnk">
+				<img src="../../static/icon/icon-article.png" >
+				<div class="sa-wnk-tv">
+					<p class="sa-wnk-title">文章</p>
+					<p class="sa-wnk-value">{{sta.articleCount}}</p>
+				</div>
+			</div>
+		</el-col>
+		<el-col :lg="4" :sm="8" :xs="24">
+			<div class="sa-wnk">
+				<img src="../../static/icon/icon-comment.png" >
+				<div class="sa-wnk-tv">
+					<p class="sa-wnk-title">评论</p>
+					<p class="sa-wnk-value">{{sta.commentCount}}</p>
+				</div>
+			</div>
+		</el-col>
+		<el-col :lg="4" :sm="8" :xs="24">
+			<div class="sa-wnk">
+				<img src="../../static/icon/icon-money.png" >
+				<div class="sa-wnk-tv">
+					<p class="sa-wnk-title">余额</p>
+					<p class="sa-wnk-value">{{sta.moneyCount}}</p>
+				</div>
+			</div>
+		</el-col>
+	</el-row>
+</template>
+
+<script>
+	module.exports = {
+		data() {
+			return {
+				// 统计数据 
+				sta: {
+					userCount: 0,
+					goodsCount: 0,
+					orderCount: 0,
+					articleCount: 0,
+					commentCount: 0,
+					moneyCount: 0,
+				},
+			}
+		},
+		methods: {
+			// 数值跳动 
+			// 对象、属性、结束值、所用时间 
+			slowMotion: function(obj, prop, endValue, time) {
+				let timeNow = 0; 
+				let fn = function() {
+					// 如果已经接近 or 时间已到,则立即结束 
+					var jdz = Math.abs(obj[prop] - endValue);
+					if(jdz < 2 || timeNow >= time) {
+						// console.log('到点了');
+						obj[prop] = endValue;
+					} else {
+						if(jdz < 100) {
+							obj[prop] += 1;
+						} else {
+							obj[prop] += parseInt((endValue - obj[prop]) / 10);		 // 平均一下 
+						}
+						timeNow += 30;
+						setTimeout(fn, 30);
+					}
+				}
+				fn();
+			},
+			// 设置统计数据的数值 
+			setStaDataValue: function(staData) {
+				for (let key in staData) {
+					this.slowMotion(this.sta, key, staData[key], 3000);
+				}
+			},
+		},
+		created() {
+			// 写入数据
+			this.setStaDataValue({
+				userCount: 12361,
+				goodsCount: 12541,
+				orderCount: 63222,
+				articleCount: 10368,
+				commentCount: 2048,
+				moneyCount: 13654.32,
+			});
+		}
+	}
+</script>
+
+<style scoped>
+	/* 第一行 */
+	.sa-wnk{background-color: #FFF; border: 1px #ddd solid; margin-bottom: 14px; min-height: 100px; 
+		cursor: pointer; transition: all 0.3s; overflow: hidden;}
+	.sa-wnk:hover{box-shadow: 0 0 20px #999;}
+	.sa-wnk img{float: left; line-height: 100px; margin: 25px 0px 0 20px; width: 50px; height: 50px; vertical-align: middle;}
+	.sa-wnk .sa-wnk-tv{float: left; margin-left: 10px; max-width: calc(100% - 100px);}
+	.sa-wnk-title{margin-top: 25px; font-size: 16px;}
+	.sa-wnk-value{margin-top: 4px; font-size: 24px; padding-bottom: 20px;}
+</style>

+ 66 - 0
sa-view-sp/console/com-stack.vue

@@ -0,0 +1,66 @@
+<!-- 第一行统计数据 -->
+<template>
+	<div>
+		<div class="btn-box">
+			<el-popover placement="top-start" trigger="hover">
+			    <el-button slot="reference" type="primary" size="small" @click="sa.open('https://jq.qq.com/?_wv=1027&k=NNBSOkeA')">QQ群(310293485)</el-button>
+				<div style="text-align: center;">
+					<img src="http://dev33-yxzj.oss-cn-beijing.aliyuncs.com/dyc/img/2020/01/17/157924554064970545739.png" style="width: 150px; height: 150px;" >
+				</div>
+			</el-popover>
+			<el-button type="success" size="small" @click="sa.open('https://github.com/click33/sa-plus')">GitHub 地址 (求star)</el-button>
+			<el-button type="danger" size="small" @click="sa.open('https://gitee.com/click33/sa-plus')">Gitee 地址</el-button>
+			<!-- <el-button type="info" size="small" @click="sa_admin.showMenuById('1-11')">意见吐槽</el-button> -->
+			<el-button type="info" size="small" @click="sa.open('http://sa-app.dev33.cn/wall.html?name=sa-plus')">需求征集</el-button>
+			<el-popover placement="top-start" trigger="hover">
+			    <el-button slot="reference" type="warning" size="small">打赏</el-button>
+				<div style="text-align: center;">
+					<h3 style="margin-bottom: 14px;">请作者喝杯咖啡</h3>
+					<img src="http://oss.dev33.cn/sa-admin/ds-zfb.jpg" style="width: 150px; height: 150px; cursor: pointer;" 
+						@click="sa.showImage('http://oss.dev33.cn/sa-admin/ds-zfb.jpg', '400px', '400px')" />
+					<img src="http://oss.dev33.cn/sa-admin/ds-wx.jpg" style="width: 150px; height: 150px; cursor: pointer;" 
+						@click="sa.showImage('http://oss.dev33.cn/sa-admin/ds-wx.jpg', '400px', '400px')" />
+				</div>
+			</el-popover>
+		</div>
+		<div>
+			<el-table ref="data-table" :data="frameList" size="small" border>
+				<el-table-column label="技术栈" prop="name"></el-table-column>
+				<el-table-column label="框架" prop="value"></el-table-column>
+				<el-table-column label="链接">
+					<template slot-scope="s">
+						<el-link type="primary" :href="s.row.link" target="_blank">{{s.row.link}}</el-link>
+					</template>
+				</el-table-column>
+			</el-table>
+		</div>
+	</div>
+</template>
+
+<script>
+	module.exports = {
+		data() {
+			return {
+				// 技术栈集合
+				frameList: [
+					{name: '基础框架', value: 'Vue @2.6.10', link: 'https://cn.vuejs.org/'},
+					{name: 'UI框架', value: 'Element-UI @2.13.0', link: 'https://element.eleme.cn/#/zh-CN'},
+					{name: 'web弹层', value: 'layer @3.1.1', link: 'http://layer.layui.com/'},
+					{name: '图表引擎', value: 'ECharts @4.2.1', link: 'https://echarts.baidu.com/'},
+					{name: '富文本编辑器', value: 'wangEditor @3.1.1', link: 'http://www.wangeditor.com/'},
+				],
+			}
+		},
+		methods: {
+			
+		},
+		created() {
+			
+		}
+	}
+</script>
+
+<style scoped>
+	.btn-box{margin-bottom: 4px; }
+	.btn-box .el-button{margin-bottom: 10px; }
+</style>

+ 289 - 0
sa-view-sp/console/com-update-log.vue

@@ -0,0 +1,289 @@
+<!-- 第一行统计数据 -->
+<template>
+	<el-timeline>
+		<!-- ---------- 一个版本 第40个----------- -->
+		<el-timeline-item timestamp="v1.40.0 &emsp; 2021-9-26" placement="top" type="primary">
+			<li>重构:使用 http-vue-loader 重构底层,脱胎换骨</li>
+		</el-timeline-item>
+		<!-- ---------- 一个版本 第39个----------- -->
+		<el-timeline-item timestamp="v2.4.4 &emsp; 2020-11-14" placement="top" type="primary">
+			<li>修复:当最后一个tab卡片有滑动条时,其它tab滚动条失效的bug</li>
+			<li>修复:sa.js增加部分判断,使之在不引入jquery时放弃执行部分代码</li>
+			<li>修复: layer弹窗回车事件影响到首页便签的bug</li>
+			<li>优化:页面初始打开时,按钮高亮,更鲜艳的颜色 </li>
+		</el-timeline-item>
+		<!-- ---------- 一个版本 第38个----------- -->
+		<el-timeline-item timestamp="v2.4.3 &emsp; 2020-10-02" placement="top" type="primary">
+			<li>修复:修复在没有成功初始化的情况下,调整窗口大小控制台报错的bug</li>
+			<li>修复:修复单窗口显示时,面包屑显示位置异常的bug</li>
+			<li>修复:修复main.html页一直显示loading图标的bug</li>
+			<li>新增:离线包新增swiper相关文件</li>
+			<li>优化:优化菜单id为number类型时不能显示的缺点</li>
+			<li>优化:layer的弹窗,双击可以全屏,再次双击缩小 </li>
+		</el-timeline-item>
+		<!-- ---------- 一个版本 第37个----------- -->
+		<el-timeline-item timestamp="v2.4.2 &emsp; 2020-09-03" placement="top" type="primary">
+			<li>新增:新增弹窗回车事件,可方便的关闭弹窗</li>
+			<li>新增:新增判断,考虑到table刷新高度时有横向滚动条对高度的影响</li>
+			<li>优化:改变c-item的min-width,避免了某些情况下无法对齐的问题</li>
+		</el-timeline-item>
+		<!-- ---------- 一个版本 第36个----------- -->
+		<el-timeline-item timestamp="v2.4.1 &emsp; 2020-08-25" placement="top" type="primary">
+			<li>优化:sort_type改为sortType 小驼峰风格</li>
+			<li>优化:查询列表页添加回车事件,更流畅的体验</li>
+			<li>优化:优化sa.showImageList函数,更智能的判断图片数组</li>
+			<li>优化:删除logo小图</li>
+			<li>修复:去掉了站长统计四个字,因为它影响到了布局</li>
+			<li>修复:修复弹出窗口底部按钮间距过大的bug</li>
+			<li>新增:集成登录验证与全局配置方法</li>
+		</el-timeline-item>
+		<!-- ---------- 一个版本 第35个----------- -->
+		<el-timeline-item timestamp="v2.4.0 &emsp; 2020-08-22" placement="top" type="primary">
+			<li>新增:多行textarea文本域示例</li>
+			<li>新增:评分组件示例</li>
+			<li>新增:新增数据导出功能,纯前端实现,不借助后端也能导出Excel数据</li>
+			<li>新增:增加弹出式新增、页面重置、多选删除、页面重置等快捷操作按钮</li>
+			<li>新增:表格查询页面,在input里回车时提交查询操作</li>
+			<li>新增:新增jq22搜集</li>
+			<li>新增:权限设置页面,新增全选按钮 </li>
+			<li>新增:菜单搜索功能 </li>
+			<li>新增:sa.js新增一系列API,更强大的工具类 </li>
+			<li>升级:二三级菜单也可以添加图标了</li>
+			<li>优化:优化表格增删改查动画,更加流畅的操作</li>
+			<li>优化:全面优化页面布局,更舒服的配色及排版</li>
+			<li>优化:优化登录页面方框圆角尺寸</li>
+			<li>优化:优化阴影样式 </li>
+			<li>集成:集成form-generator,在线拖拉拽代码生成器</li>
+		</el-timeline-item>
+		<!-- ---------- 一个版本 ----------- -->
+		<el-timeline-item timestamp="v2.3.7 &emsp; 2020-04-18" placement="top" type="primary">
+			<li>新增:首页增加懒加载功能</li>
+		</el-timeline-item>
+		<!-- ---------- 一个版本 ----------- -->
+		<el-timeline-item timestamp="v2.3.6 &emsp; 2020-04-17" placement="top" type="primary">
+			<li>优化:部分样式</li>
+		</el-timeline-item>
+		<!-- ---------- 一个版本 ----------- -->
+		<el-timeline-item timestamp="v2.2.6 &emsp; 2020-04-17" placement="top" type="primary">
+			<li>优化:部分样式</li>
+		</el-timeline-item>
+		<!-- ---------- 一个版本 ----------- -->
+		<el-timeline-item timestamp="v2.3.5 &emsp; 2020-04-17" placement="top" type="primary">
+			<li>优化:部分模板页样式</li>
+			<li>更换:更换堆表单方式为c-item</li>
+		</el-timeline-item>
+		<!-- ---------- 一个版本 ----------- -->
+		<el-timeline-item timestamp="v2.3.4 &emsp; 2020-03-05" placement="top" type="primary">
+			<li>去除:tab双击刷新</li>
+		</el-timeline-item>
+		<!-- ---------- 一个版本 ----------- -->
+		<el-timeline-item timestamp="v2.3.3 &emsp; 2020-03-02" placement="top" type="primary">
+			<li>新增:tab双击刷新</li>
+		</el-timeline-item>
+		<!-- ---------- 一个版本 ----------- -->
+		<el-timeline-item timestamp="v2.3.2 &emsp; 2020-3-1" placement="top" type="primary">
+			<li>新增:初始加载loading图标</li>
+			<li>新增:函数菜单(点击菜单执行一个函数)</li>
+			<li>更新:my-code.js重命名为sa-code.js</li>
+		</el-timeline-item>
+		<!-- ---------- 一个版本 ----------- -->
+		<el-timeline-item timestamp="v2.3.1 &emsp; 2020-2-29" placement="top" type="primary">
+			<li>修复:部分bug</li>
+			<li>
+				上线:vue单页版上线,传送门:
+				<el-link type="primary" href="http://sa-vue-admin.dev33.cn/" target="_blank">http://sa-vue-admin.dev33.cn/</el-link>
+			</li>
+		</el-timeline-item>
+		<!-- ---------- 一个版本 ----------- -->
+		<el-timeline-item timestamp="v2.3.0 &emsp; 2020-2-25" placement="top" type="primary">
+			<li>优化:改变部分文件夹结构</li>
+		</el-timeline-item>
+		<!-- ---------- 一个版本 ----------- -->
+		<el-timeline-item timestamp="v2.2.6 &emsp; 2020-2-17" placement="top" type="primary">
+			<li>新增:新增主题:简约式灰蓝</li>
+		</el-timeline-item>
+		<!-- ---------- 一个版本 ----------- -->
+		<el-timeline-item timestamp="v2.2.5 &emsp; 2020-2-14" placement="top" type="primary">
+			<li>
+				新增:新增在线论坛:
+				<el-link type="primary" href="javascript:parent.sa_admin.showMenuById('1-11');">点击打开,在线提交意见反馈(新)</el-link>
+			</li>
+		</el-timeline-item>
+		<!-- ---------- 一个版本 ----------- -->
+		<el-timeline-item timestamp="v2.2.4 &emsp; 2020-2-13" placement="top" type="primary">
+			<li>优化:优化整体样式</li>
+			<li>优化:loading加载框的样式</li>
+			<li>增加:tab悬浮打开的z-index自动切换功能</li>
+		</el-timeline-item>
+		<!-- ---------- 一个版本 ----------- -->
+		<el-timeline-item timestamp="v2.2.3 &emsp; 2020-2-9" placement="top" type="primary">
+			<li>增加:增加底部按钮式的弹窗示例</li>
+		</el-timeline-item>
+		<!-- ---------- 一个版本 ----------- -->
+		<el-timeline-item timestamp="v2.2.2 &emsp; 2019-7-16" placement="top" type="primary">
+			<li>增加:增加弹出式修改的示例</li>
+			<li>增加:增加窗口之间通信的方法,详细请查看集成文档</li>
+			<li>完善:完善readme.md</li>
+		</el-timeline-item>
+		<!-- ---------- 一个版本 ----------- -->
+		<el-timeline-item timestamp="v2.2.1 &emsp; 2020-1-31" placement="top" type="primary">
+			<li>修复:替换掉所有bootcss的cdn,因为它太不稳定了,三天一小瘫,五天一大瘫</li>
+		</el-timeline-item>
+		<!-- ---------- 一个版本 ----------- -->
+		<el-timeline-item timestamp="v2.2.0 &emsp; 2020-1-20" placement="top" type="primary">
+			<li>集成:集成鉴权功能,详细请查看文档 </li>
+			<li>新增:新增大量模板示例,可帮助你快速增删改查 </li>
+			<li>修复:在边缘处右键菜单文字变形的bug </li>
+			<li>重构:改了一下首页样式 </li>
+			<li>优化:将element-ui版本更换至了 2.13.0 </li>
+		</el-timeline-item>
+		<!-- ---------- 一个版本 ----------- -->
+		<el-timeline-item timestamp="v2.1.2 &emsp; 2020-1-18" placement="top" type="primary">
+			<li>修复:修复登录页鼠标不能与背景粒子交互的bug </li>
+			<li>优化:右键关闭其它和关闭全部时,首先滑到做左边,动画更直观 </li>
+		</el-timeline-item>
+		<!-- ---------- 一个版本 ----------- -->
+		<el-timeline-item timestamp="v2.1.0 &emsp; 2020-1-17" placement="top" type="primary">
+			<li>新增:更换了登录页</li>
+			<li>新增:右键菜单新增复制按钮,可直接复制一个tab在新窗口打开 </li>
+			<li>新增:右键菜单新增折叠关闭动画,失去焦点时和点击取消时,菜单以折叠动画的方式关闭 </li>
+			<li>新增:右键菜单新增盒子阴影,更有立体感</li>
+			<li>新增:新增主题:pro钛合金、沉淀式黑蓝 </li>
+			<li>修复:切换tab时,不能自动滑动的bug</li>
+			<li>重构:重新设置了UI样式,详细参考模板示例</li>
+			<li>注意:表格内操作按钮类样式 .c-button 换成了 .c-btn  </li>
+			<li>重构:修改了sa.js,取消$util对象,所有有关$util的函数全部移到sa对象上,</li>
+			<li>注意:原调用方式sa.$util.getUrlArgs('id') 现改为:sa.p('id')</li>
+		</el-timeline-item>
+		<!-- ---------- 一个版本 ----------- -->
+		<el-timeline-item timestamp="v2.0.2 &emsp; 2020-1-15" placement="top" type="primary">
+			<li>修复bug:tab卡文字向上偏移了1px的问题</li>
+		</el-timeline-item>
+		<!-- ---------- 一个版本 ----------- -->
+		<el-timeline-item timestamp="v2.0.1 &emsp; 2020-1-14" placement="top" type="primary">
+			<li>修复bug:在方块、3D流、3D翻转切换效果下,tab切换错乱的bug</li>
+			<li>修复bug:iframe的url发生改变后,刷新按钮刷新为旧地址的bug</li>
+		</el-timeline-item>
+		<!-- ---------- 一个版本 ----------- -->
+		<el-timeline-item timestamp="v2.0.0 &emsp; 2020-1-13" placement="top" type="primary">
+			<li>新增:卡片右击菜单弹出动画 </li>
+			<li>新增:tab右键菜单动画</li>
+			<li>新增:新增tab关闭动画, 在关闭tab、右键关闭其它、关闭全部时有了更流畅的体验</li>
+			<li>新增:tab选项卡拖拽效果,上拽新窗口打开,下拽悬浮打开,左拽关闭,在tab处拖拽一下体验一下吧</li>
+			<li>新增:在tab栏空白处,双击:可以打开添加新tab操作弹窗 </li>
+			<li>新增:增加记住上一次最后打开的窗口功能,刷新也可以记住窗口(在初始化模板时,增加is_reme_open配置项)</li>
+			<li>新增: hash链接跳转功能,可灵活的用鼠标前后键切换tab窗口 </li>
+			<li>新增:便签功能,可以方便的保存一些临时数据 </li>
+			<li>修复:首页114行有个重复的class</li>
+			<li>修复:版本号打印不对 </li>
+			<li>修复:首页homePage的url配置无效</li>
+			<li>
+				因为项目紧迫,这个版本拖了三个月,积攒了不少更新点,也算是憋了一个大招,
+				大家有什么意见或者想要添加功能,可以加入qq群尽情提出来,我只要有时间,一定第一时间更新
+			</li>
+		</el-timeline-item>
+		<!-- ---------- 一个版本 ----------- -->
+		<el-timeline-item timestamp="v1.1.4 &emsp; 2019-10-17" placement="top" type="primary">
+			<li>重写了一下简介</li>
+			<li>优化主题样式</li>
+		</el-timeline-item>
+		<!-- ---------- 一个版本 ----------- -->
+		<el-timeline-item timestamp="v1.1.3 &emsp; 2019-9-3" placement="top" type="primary">
+			<li>更改初始化方式</li>
+			<li>优化UI样式</li>
+			<li>增加新主题:灰色-展开</li>
+		</el-timeline-item>
+		<!-- ---------- 一个版本 ----------- -->
+		<el-timeline-item timestamp="v1.1.2 &emsp; 2019-7-16" placement="top" type="primary">
+			<li>增加右键菜单的失去焦点事件,失去焦点自动消失</li>
+		</el-timeline-item>
+		<!-- ---------- 一个版本 ----------- -->
+		<el-timeline-item timestamp="v1.0.1 &emsp; 2019-6-26" placement="top" type="primary">
+			<li>优化卡片切换动画,更流畅了,并且添加loading图标,视觉上更加顺畅</li>
+			<li>新增悬浮窗口功能,在卡片标题处右击试试吧</li>
+			<li>更换登录模板页,更漂亮了</li>
+		</el-timeline-item>
+		<!-- ---------- 一个版本 ----------- -->
+		<el-timeline-item timestamp="v1.1.0 &emsp; 2019-6-24" placement="top" type="primary">
+			<li>修复bug:鼠标悬浮tab-title时,偶尔动画混乱的bug</li>
+			<li>修复bug:优化折叠动画,更流畅了</li>
+			<li>集成swiper,窗口切换,更加高大上了</li>
+		</el-timeline-item>
+		<el-timeline-item timestamp="v1.0.8 &emsp; 2019-5-28" placement="top" type="primary">
+			<li>修复bug:菜单折叠时,菜单项箭头仍然显示的问题</li>
+			<li>修复bug:在手机端菜单折叠时,右侧信息排版发生混乱的问题</li>
+			<li>调整了一下字体大小,看着更顺眼了</li>
+		</el-timeline-item>
+		<!-- ---------- 一个版本 ----------- -->
+		<el-timeline-item timestamp="v1.0.7 &emsp; 2019-5-25" placement="top" type="primary">
+			<li>优化一些动画效果</li>
+			<li>增加模板页:轮播图管理</li>
+			<li>一些样式优化</li>
+		</el-timeline-item>
+		<!-- ---------- 一个版本 ----------- -->
+		<el-timeline-item timestamp="v1.0.6 &emsp; 2019-5-22" placement="top" type="primary">
+			<li>添加了菜单预览模板(tree树)</li>
+			<li>修复bug:菜单分配权限时,父子级不关联的问题</li>
+			<li>一些样式优化</li>
+		</el-timeline-item>
+		<!-- ---------- 一个版本 ----------- -->
+		<el-timeline-item timestamp="v1.0.5 &emsp; 2019-5-18" placement="top" type="primary">
+			<li>添加了权限中心模板(tree权限树)</li>
+			<li>优化用户列表模板,弹出式的修改</li>
+			<li>一些其它样式优化</li>
+		</el-timeline-item>
+		<!-- ---------- 一个版本 ----------- -->
+		<el-timeline-item timestamp="v1.0.4 &emsp; 2019-5-15" placement="top" type="primary">
+			<li>优化折叠动画,避免文字闪动现象</li>
+			<li>sp.setMenuList();接口增加show_list参数,可灵活控制部分菜单的显示与隐藏</li>
+			<li>一些样式优化</li>
+		</el-timeline-item>
+		<!-- ---------- 一个版本 ----------- -->
+		<el-timeline-item timestamp="v1.0.3 &emsp; 2019-5-14" placement="top" type="primary">
+			<li>添加文章管理模板(wangEditor富文本编辑器)</li>
+			<li>改写了表格里按钮的样式,更鲜艳,增加点击感</li>
+			<li>菜单列表里可以指定is_show=false,使菜单成为隐藏菜单</li>
+			<li>重写了一些接口,可以更加方便的与你的系统集成</li>
+		</el-timeline-item>
+		<!-- ---------- 一个版本 ----------- -->
+		<el-timeline-item timestamp="v1.0.1 &emsp; 2019-5-5" placement="top" type="primary">
+			<li>修复:周日显示周0的bug</li>
+			<li>新增:增加三级菜单支持</li>
+			<li>新增:主题更换时高亮显示,方便区分</li>
+		</el-timeline-item>
+		<!-- ---------- 一个版本 ----------- -->
+		<el-timeline-item timestamp="v1.0.0 &emsp; 2019-5-2" placement="top" type="primary">
+			<li>第一个版本出炉</li>
+			<li>功能持续更新中...</li>
+		</el-timeline-item>
+	</el-timeline>
+</template>
+
+<script>
+	module.exports = {
+		data() {
+			return {
+				// 技术栈集合
+				frameList: [
+					{name: 'JS引擎', value: 'Vue @2.6.10', link: 'https://cn.vuejs.org/'},
+					{name: 'UI框架', value: 'Element-UI @2.13.0', link: 'https://element.eleme.cn/#/zh-CN'},
+					{name: 'web弹层', value: 'layer @3.1.1', link: 'http://layer.layui.com/'},
+					{name: '切页动画', value: 'Swiper @4.5.0', link: 'https://www.swiper.com.cn/'},
+					{name: '图表引擎', value: 'ECharts @4.2.1', link: 'https://echarts.baidu.com/'},
+					{name: '富文本编辑器', value: 'wangEditor @3.1.1', link: 'http://www.wangeditor.com/'},
+				],
+			}
+		},
+		methods: {
+			
+		},
+		created() {
+			
+		}
+	}
+</script>
+
+<style scoped>
+	.el-timeline-item__timestamp { color: #207EFF;}
+	.el-timeline-item__content{color: #666;}
+</style>

+ 119 - 0
sa-view-sp/console/console-main.html

@@ -0,0 +1,119 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>Sa-Admin 控制台</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css & js 资源 -->
+		<link rel="stylesheet" href="https://unpkg.com/element-ui@2.13.0/lib/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css">
+		<style type="text/css">
+			.vue-box{margin: 0; padding: 0; height: 100%;}
+			.el-card{border-radius: 0px; border: 1px #ddd solid ; margin-bottom: 14px;}
+			.s-row{/* background-color: antiquewhite; */ padding: 0 14px; padding-bottom: 0px;}
+			.s-row-1{padding-top: 14px;}
+			.s-row-2{/* margin-top: -10px; */}
+			.s-row-2 .el-card .el-card__body{height: 250px;}
+			.s-row-3 .el-card{/* height: 100%; */}
+			
+			.echarts-div{height: 100%;}
+			.s-row-3 .el-alert{margin-bottom: 14px;}
+			
+			.vue-box .el-alert{padding: 1em 0.5em; }
+		</style>
+	</head>
+	<body>
+		<div class="vue-box" style="display: none;" :style="'display: block;'">
+			
+			<!-- ------------ 第一栏 - 统计数据 ------------- -->
+			<div class="s-row s-row-1">
+				<com-sta-data></com-sta-data>
+			</div>
+			
+			<!-- ------------ 第二栏 - 图表 ------------- -->
+			<div class="s-row s-row-2">
+				<el-row :gutter="14">
+					<el-col :lg="8" :xs="24">
+						<el-card shadow="never" header="柱状图">
+							<com-chart-1></com-chart-1>
+						</el-card>
+					</el-col>
+					<el-col :lg="8" :xs="24">
+						<el-card shadow="never" header="饼图">
+							<com-chart-2></com-chart-2>
+						</el-card>
+					</el-col>
+					<el-col :lg="8" :xs="24">
+						<el-card shadow="never" header="折线图">
+							<com-chart-3></com-chart-3>
+						</el-card>
+					</el-col>
+				</el-row>
+			</div>
+			
+			<!-- ------------ 第三栏 - 框架信息 ------------- -->
+			<div class="s-row s-row-3">
+				<el-row :gutter="14" type="flex" style="flex-wrap: wrap-reverse;">
+					<!-- 左边 -->
+					<el-col :lg="12" :xs="24">
+						<!-- 技术选型 -->
+						<el-card shadow="never" header="技术选型">
+							<com-stack></com-stack>
+						</el-card>
+						<!-- 更新日志 -->
+						<el-card shadow="never" header="更新日志">
+							<com-update-log></com-update-log>
+						</el-card>
+					</el-col>
+					<!-- 右边 -->
+					<el-col :lg="12" :xs="24">
+						<el-card shadow="never" header="Sa-Admin ">
+							<com-origin></com-origin>
+						</el-card>
+					</el-col>
+				</el-row>
+			</div>
+			
+		</div>
+		<script src="https://unpkg.com/vue@2.6.10/dist/vue.js"></script>
+		<script src="https://unpkg.com/element-ui@2.13.0/lib/index.js"></script>
+		<script src="https://unpkg.com/http-vue-loader@1.4.2/src/httpVueLoader.js"></script>
+		<script src="https://unpkg.com/jquery@3.4.1/dist/jquery.js"></script>
+		<script src="https://www.layuicdn.com/layer-v3.1.1/layer.js"></script>
+		<script src="https://unpkg.com/echarts@4.6.0/dist/echarts-en.min.js"></script>
+		<script src="../../static/sa.js"></script>
+		<script type="text/javascript">
+			var app = new Vue({
+				components: {
+					'com-sta-data': httpVueLoader('com-sta-data.vue'),
+					'com-chart-1': httpVueLoader('com-chart-1.vue'),
+					'com-chart-2': httpVueLoader('com-chart-2.vue'),
+					'com-chart-3': httpVueLoader('com-chart-3.vue'),
+					'com-stack': httpVueLoader('com-stack.vue'),
+					'com-update-log': httpVueLoader('com-update-log.vue'),
+					'com-origin': httpVueLoader('com-origin.vue'),
+					// 'com-intro': httpVueLoader('com-intro.vue'),
+				},
+				el: '.vue-box',
+				data: {
+				},
+				methods: {
+				},
+				mounted: function() {
+				}
+			})
+			
+			// 设置监听,改变窗口大小时重绘图表 
+			window.myChartList = [];
+			window.onresize = function() {
+				myChartList.forEach(function(myChart) {
+					myChart.resize();
+				})
+			}
+		</script>
+		<!-- 百度统计(下载到本地后请删除) -->
+		<div style="height: 0px; overflow: hidden;">
+			<script type="text/javascript" src="https://v1.cnzz.com/z_stat.php?id=1279021391&web_id=1279021391"></script>
+		</div>
+	</body>
+</html>

+ 117 - 0
sa-view-sp/per-tips/alert-tips.html

@@ -0,0 +1,117 @@
+
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>演示提示</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css & js 资源 -->
+		<link rel="stylesheet" href="https://unpkg.com/element-ui@2.13.0/lib/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css"> 
+		<script src="https://unpkg.com/vue@2.6.10/dist/vue.min.js"></script>
+		<script src="https://unpkg.com/element-ui@2.13.0/lib/index.js"></script>
+		<script src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"></script>
+		<script src="https://www.layuicdn.com/layer-v3.1.1/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+		<style type="text/css">
+			.vue-box{background-color: #FFF;}
+			.c-panel{line-height: 26px; padding: 1em 0.5em;}
+			.q-a-box{margin-bottom: 20px;}
+			.q-a-box h2{margin-bottom: 10px; font-size: 18px;}
+			.q-a-box p{color: #333;}
+			.s-bottom{position: fixed; width: 100%; bottom: 0px; background-color: #FFF; border-top: 1px #ccc solid; line-height: 60px; margin-bottom: 0px;}
+		</style>
+	</head>
+	<body>
+		<div class="vue-box" style="display: none;" :style="'display: block;'">
+			<div class="c-panel">
+				<!-- <h1>演示说明</h1>
+				<br> -->
+				<!-- ----------- 一个问题 ------------ -->
+				<div class="q-a-box">
+					<h2>1、为什么在演示环境里很多功能无法使用? SQL监控台为何无法打开?</h2>
+					<p>为确保系统正常运行,部分可能影响到系统正常运行的接口已被禁用,将代码下载到本地部署后即可体验这些功能</p>
+				</div>
+				<!-- ----------- 一个问题 ------------ -->
+				<div class="q-a-box">
+					<h2>2、这个演示环境看到的功能有点少?</h2>
+					<p>
+						当前演示环境只负责展示基础功能,被称为 [简洁版] <br>
+						与之对应的还有一个版本,包含一些常见业务,即 [深度定制版] <br>
+						两者有何不同?比如说你从百度上下载一个项目模板,要用到自己的项目之前,一定会先把大部分用不到的功能统统删掉,只留下最基础的功能,然后再开始Lu代码, <br>
+						这个删减之前就是 [深度定制版],删减之后就是 [简洁版] <br>
+						大多数框架都是只给提供 [深度定制版],想要获得 [简洁版],只能通过自己手动删减出来 <br>
+						而当前搭建的演示环境是 [简洁版],没有加入一些深度定制的功能模块 <br>
+						[深度定制版] 目前正在搭建中,敬请期待...  
+					</p>
+				</div>
+				<!-- ----------- 一个问题 ------------ -->
+				<div class="q-a-box">
+					<h2>3、看不到代码生成器?</h2>
+					<p>
+						前端表单构建,在 系统配置 -> 在线表单构建  <br>
+						后端的代码生成器,是和项目框架分开的,是分成的两个项目,将项目下载到本地就能运行代码生成器
+					</p>
+				</div>
+				<!-- ----------- 一个问题 ------------ -->
+				<div class="q-a-box">
+					<h2>4、商品模块为何没有规格管理?</h2>
+					<p>
+						参考问题2,下面的四个菜单,是代码生成器生成的代码直接放上去的,没有进行二次改动,这里展示的不是模板的能力,而是代码生成器的能力  <br>
+						可以看到,对于大多数简单模块,框架生成的代码已包含完善的增删改差,不用修改一行代码即可集成 <br>
+						如需使用商品规格添加页,可查看前端模板示例:<el-link type="primary" href="http://sa-admin.dev33.cn/#3-3-1" target="_blank">http://sa-admin.dev33.cn/#3-3-1</el-link>
+					</p>
+				</div>
+				<!-- ----------- 一个问题 ------------ -->
+				<div class="q-a-box">
+					<h2>5、演示环境代表最终效果吗?</h2>
+					<p>
+						不代表最终效果。 <br>
+						为了方便演示,演示环境对代码进行了一定程度的改动,并不与原始代码完全一致,且基础版本可能会有一定程度的滞后,最终效果请以GitHub仓库最新代码为准
+					</p>
+				</div>
+				<div style="height: 100px;"></div>
+				<!-- ----------- 一个s问题 ------------ -->
+				<div class="q-a-box s-bottom">
+					<el-checkbox v-model="is_not_alert">我已知晓,一小时内不再弹出</el-checkbox>
+					<el-button type="primary" size="small" icon="el-icon-close" @click="close()">关闭</el-button>
+				</div>
+				
+				
+			</div>
+		</div>
+        <script>
+			var app = new Vue({
+				el: '.vue-box',
+				data: {
+					sa: sa, 	// 超级对象
+					is_not_alert: false,
+					p: {	// 查询参数 
+						role_name: '',
+						pageNo: 1,
+						pageSize: 10000,
+					},
+					dataList: [],	// 数据集合
+				},
+				methods: {
+					// 关闭
+					close: function() {
+						if(this.is_not_alert) {
+							sessionStorage.setItem('not_alert_time', new Date().getTime() + '');
+							parent.location.href = parent.location.href;
+							sa.closeCurrIframe();
+							return;
+						}
+						sa.closeCurrIframe();
+					}
+				},
+				created: function(){
+					
+				}
+			})
+			
+			
+		
+		</script>
+	</body>
+</html>

+ 133 - 0
sa-view-sp/sp-admin-login/sp-admin-login-list.html

@@ -0,0 +1,133 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>管理员登录日志</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css & js 资源 -->
+		<link rel="stylesheet" href="https://unpkg.com/element-ui@2.13.0/lib/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css">
+		<script src="https://unpkg.com/vue@2.6.10/dist/vue.min.js"></script>
+		<script src="https://unpkg.com/element-ui@2.13.0/lib/index.js"></script>
+		<script src="https://unpkg.com/http-vue-loader@1.4.2/src/httpVueLoader.js"></script>
+		<script src="https://unpkg.com/jquery@3.4.1/dist/jquery.js"></script>
+		<script src="https://www.layuicdn.com/layer-v3.1.1/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+		<style type="text/css">
+			.vue-box{padding: 0; height: 100%; background-color: #FFF;}
+		</style>
+	</head>
+	<body>
+		<div class="vue-box" style="display: none;" :style="'display: block;'">
+			<div class="c-panel">
+				<!-- ------------- 检索参数 ------------- -->
+				<div class="c-title">检索参数</div>
+				<el-form ref="form" :model='p' @submit.native.prevent>
+					<sa-item type="num" name="记录id" v-model="p.id"></sa-item>
+					<sa-item type="num" name="管理员id" v-model="p.accId"></sa-item>
+					<sa-item type="text" name="登录Token" v-model="p.accToken"></sa-item>
+					<sa-item type="text" name="登陆IP" v-model="p.loginIp"></sa-item>
+					<el-button type="primary" icon="el-icon-search" @click="p.pageNo = 1; f5()">查询</el-button>
+				</el-form>
+				<!-- ------------- 快捷按钮 ------------- -->
+				<!-- <sa-item type="fast-btn" show="delete,export,reset"></sa-item> -->
+				<div style="height: 10px;"></div>
+				<!-- ------------- 数据列表 ------------- -->
+				<el-table class="data-table" ref="data-table" :data="dataList">
+					<sa-td type="selection"></sa-td>
+					<sa-td name="记录id" prop="id" width="100px"></sa-td>
+					<sa-td name="登录账号" width="180px">
+						<template slot-scope="s">
+							<img :src="s.row.spAdminAvatar" @click="sa.showImage(s.row.spAdminAvatar, '400px', '400px')"  
+								class="td-img" style="vertical-align: middle; margin-right: 5px;" />
+							<el-link type="primary" @click="sa.showIframe('id = ' + s.row.accId + ' 详细信息', '../sp-admin/admin-info.html?id=' + s.row.accId)">
+								<b style="font-weight: 400;">{{s.row.spAdminName}}</b>
+							</el-link>
+						</template>
+					</sa-td>
+					<sa-td name="登陆IP" prop="loginIp"></sa-td>
+					<sa-td name="客户端标识" prop="device"></sa-td>
+					<sa-td name="所属系统" prop="system"></sa-td>
+					<sa-td name="登录地" prop="address"></sa-td>
+					<sa-td name="本次登录Token" prop="accToken"></sa-td>
+					<sa-td name="登录时间" width="220px">
+						<template slot-scope="s">
+							<span>{{sa.forDate(s.row.createTime, 2)}}</span> - 
+							<b style="color: green;">{{sa.isNull(sa.forDate2(s.row.createTime), '无')}}</b>
+						</template>
+					</sa-td>
+					<el-table-column label="操作" fixed="right"  width="120px">
+						<template slot-scope="s">
+							<el-button class="c-btn" type="danger" icon="el-icon-delete" @click="del(s.row)">删除</el-button>
+						</template>
+					</el-table-column>
+				</el-table>
+				<!-- ------------- 分页 ------------- -->
+				<sa-item type="page" :curr.sync="p.pageNo" :size.sync="p.pageSize" :total="dataCount" @change="f5()"></sa-item>
+			</div>
+		</div>
+		<script>
+			var app = new Vue({
+				components: {
+					"sa-item": httpVueLoader('../../sa-frame/com/sa-item.vue'),
+					"sa-td": httpVueLoader('../../sa-frame/com/sa-td.vue')
+				},
+				el: '.vue-box',
+				data: {
+					p: { // 查询参数  
+						id: '',		// id号 
+						accId: sa.p('accId', ''),		// 管理员id 
+						accToken: '',		// 本次登录Token 
+						loginIp: '',		// 登陆IP 
+						pageNo: 1,		// 当前页 
+						pageSize: 10,	// 页大小 
+						sortType: 0		// 排序方式 
+					},
+					dataCount: 0,
+					dataList: [], // 数据集合 
+				},
+				methods: {
+					// 刷新
+					f5: function() {
+						sa.ajax('/SpAdminLogin/getList', sa.removeNull(this.p), function(res) {
+							this.dataList = res.data; // 数据
+							this.dataCount = res.dataCount; // 数据总数 
+							sa.f5TableHeight();		// 刷新表格高度 
+						}.bind(this));
+					},
+					// 删除
+					del: function(data) {
+						sa.confirm('是否删除,此操作不可撤销', function() {
+							sa.ajax('/SpAdminLogin/delete?id=' + data.id, function(res) {
+								sa.arrayDelete(this.dataList, data);
+								sa.ok('删除成功');
+								sa.f5TableHeight();		// 刷新表格高度 
+							}.bind(this))
+						}.bind(this));
+					},
+					// 批量删除
+					deleteByIds: function() {
+						// 获取选中元素的id列表 
+						let selection = this.$refs['data-table'].selection;
+						let ids = sa.getArrayField(selection, 'id');
+						if(selection.length == 0) {
+							return sa.msg('请至少选择一条数据')
+						}
+						// 提交删除 
+						sa.confirm('是否批量删除选中数据?此操作不可撤销', function() {
+							sa.ajax('/SpAdminLogin/deleteByIds', {ids: ids.join(',')}, function(res) {
+								sa.arrayDelete(this.dataList, selection);
+								sa.ok('删除成功');
+								sa.f5TableHeight();		// 刷新表格高度 
+							}.bind(this))
+						}.bind(this));
+					},
+				},
+				created: function() {
+					this.f5();
+					sa.onInputEnter();
+				}
+			})
+		</script>
+	</body>
+</html>

+ 127 - 0
sa-view-sp/sp-admin/admin-add.html

@@ -0,0 +1,127 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>添加管理员</title>
+		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+		<meta name="viewport"
+			content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css & js 资源 -->
+		<link rel="stylesheet" href="https://unpkg.com/element-ui@2.13.0/lib/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css">
+		<script src="https://unpkg.com/vue@2.6.10/dist/vue.min.js"></script>
+		<script src="https://unpkg.com/element-ui@2.13.0/lib/index.js"></script>
+		<script src="https://unpkg.com/http-vue-loader@1.4.2/src/httpVueLoader.js"></script>
+		<script src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"></script>
+		<script src="https://www.layuicdn.com/layer-v3.1.1/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+		<script src="../../static/kj/upload-util.js"></script>
+	</head>
+	<body>
+		<div class="vue-box" style="display: none;" :style="'display: block;'">
+			<!-- 参数栏 -->
+			<div class="c-panel">
+				<h4 class="c-title">添加用户</h4>
+				<el-form>
+					<!-- 防止密码框被填充 -->
+					<div style="height: 0px; overflow: hidden;">
+						<el-input></el-input>
+						<el-input type="password"></el-input>
+					</div>
+					<sa-item type="text" name="姓名" v-model="m.nickname" br need></sa-item>
+					<sa-item style="display: inline;" type="text" name="手机号" v-model="m.phone" br need></sa-item>
+					<span style="display: inline;">即登录账号</span>
+					<div class="c-item">
+						<label class="c-label"><span style="color: red;">*</span>部门:</label>
+						<el-select v-model="m.deptId" :disabled="currentUser.deptId!==9999999">
+							<el-option label="请选择" v-for="(item,index) in deptList" :key="item.id" :label="item.name"
+								:value="item.id"></el-option>
+
+						</el-select>
+					</div>
+					<sa-item type="password" name="密码" v-model="m.password" br need></sa-item>
+					<sa-item name="角色" br>
+						<el-select v-model="m.roleId">
+							<el-option label="请选择" :value="0" disabled></el-option>
+							<el-option v-for="role in roleList" :key="role.id" :label="role.name" :value="role.id">
+							</el-option>
+						</el-select>
+					</sa-item>
+					<sa-item name="" br>
+						<el-button type="primary" icon="el-icon-plus" @click="ok()">保存</el-button>
+					</sa-item>
+				</el-form>
+			</div>
+		</div>
+		<script>
+			var app = new Vue({
+				components: {
+					"sa-item": httpVueLoader('../../sa-frame/com/sa-item.vue')
+				},
+				el: '.vue-box',
+				data: {
+					sa: sa, // 超级对象
+					m: {
+						id: 0,
+						name: '',
+						deptId:'',
+						avatar: '',
+						password: '',
+						roleId: 0
+					},
+					roleList: [],
+					deptList: [],
+					currentUser: sa.$sys.getCurrUser(),
+				},
+				methods: {
+					getDeptList() {
+						sa.ajax('/TbDept/getList', {
+							pageNo: 1,
+							pageSize: 100
+						}, function(resp) {
+							let list = resp.data;
+							let deptId = this.currentUser.deptId;
+							if (deptId != 9999999) {
+								this.m.deptId = deptId;
+							}
+							this.deptList = list;
+						}.bind(this))
+					},
+
+					// 修改
+					ok: function() {
+						// 表单校验 
+						let m = this.m;
+						sa.checkNull(m.nickname, '请输入姓名');
+						let phone = m.phone;
+						if (!sa.isPhone(phone)) {
+							sa.error('请输入正确的手机号');
+							return;
+						}
+						sa.checkNull(m.password, '请输入密码');
+						sa.checkNull(m.roleId, '请选择角色');
+
+						// 添加
+						sa.ajax('/admin/add', m, function(res) {
+							sa.alert('增加成功', this.clean);
+							parent.app.f5(); // 刷新父页面列表
+							sa.closeCurrIframe();
+						}.bind(this));
+					},
+					getRoleList() {
+						sa.ajax('/role/getList', function(res) {
+							this.roleList = res.data; // 数据  
+						}.bind(this), {
+							msg: null
+						});
+					}
+				},
+				mounted: function() {
+					// 加载角色 
+					this.getRoleList();
+					this.getDeptList();
+
+				}
+			})
+		</script>
+	</body>
+</html>

+ 83 - 0
sa-view-sp/sp-admin/admin-info.html

@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>资料详情</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css & js 资源 -->
+		<link rel="stylesheet" href="https://unpkg.com/element-ui@2.13.0/lib/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css"> 
+		<script src="https://unpkg.com/vue@2.6.10/dist/vue.min.js"></script>
+		<script src="https://unpkg.com/element-ui@2.13.0/lib/index.js"></script>
+		<script src="https://unpkg.com/http-vue-loader@1.4.2/src/httpVueLoader.js"></script>
+		<script src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"></script>
+		<script src="https://www.layuicdn.com/layer-v3.1.1/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+		<style type="text/css">
+			/* 使长度更长点 */
+			/* .c-panel .c-label{width: 10em;} */
+			.vue-box,.c-panel{background-color: #EEE;}
+			.c-panel .c-title{margin-bottom: 20px;}
+			.c-item .c-label{width: 150px;}
+			.c-item .el-input{width: 300px;}
+			/* 链接样式  */
+			.my-link{position: relative; top: -1px; margin-left: 0.5em;}
+		</style>
+	</head>
+	<body>
+		<div class="vue-box sbot" style="display: none;" :style="'display: block;'">
+			<!-- ------- 内容部分 ------- -->
+			<div class="s-body">
+				<div class="c-panel">
+					<el-form v-if="m">
+						<div style="height: 20px;"></div>
+						<sa-info name="姓名" br>{{m.nickname || '无'}}</sa-info>
+						<sa-info name="手机/账号" br>{{m.phone || m.name}}</sa-info>
+						<sa-info name="角色" br>{{m.roleName}}</sa-info>
+						<sa-info name="创建时间" br>{{sa.forDate(m.createTime, 2)}}</sa-info>
+						<sa-info name="最后登录" br>{{sa.forDate(m.loginTime, 2) || '无'}}</sa-info>
+						<sa-info name="最后登录IP" br>{{m.loginIp || '无'}}</sa-info>
+					</el-form>
+				</div>
+			</div>
+			<!-- ------- 底部按钮 ------- -->
+			<div class="s-foot">
+				<el-button type="success" @click="sa.closeCurrIframe()">确定</el-button>
+				<el-button @click="sa.closeCurrIframe()">取消</el-button>
+			</div>
+			
+		</div>
+		
+		<script type="text/javascript">
+			var app = new Vue({
+				components: {
+					"sa-info": httpVueLoader('../../sa-frame/com/sa-info.vue')
+				},
+				el: '.vue-box',
+				data: {
+					id: parseInt(sa.p('id', 0)),
+					sa: sa,
+					m: null
+				},
+				methods: {
+					// ok
+					ok: function(pageNo) {
+						sa.closeCurrIframe();
+					},
+				},
+				created: function() {
+					if(this.id == 0 || this.id == sa.$sys.getCurrUser().id) {
+						sa.ajax('/admin/getByCurr', function(res) {
+							this.m = res.data;
+						}.bind(this));
+					} else {
+						sa.ajax('/admin/getById?id=' + this.id, function(res) {
+							this.m = res.data;
+						}.bind(this));
+					}
+				}
+			})
+			
+		</script>
+	</body>
+</html>

+ 288 - 0
sa-view-sp/sp-admin/admin-list.html

@@ -0,0 +1,288 @@
+<!DOCTYPE html>
+<html>
+	<head>
+	    <title>管理员列表</title>
+	    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+	    <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css & js 资源 -->
+		<link rel="stylesheet" href="https://unpkg.com/element-ui@2.13.0/lib/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css"> 
+		<script src="https://unpkg.com/vue@2.6.10/dist/vue.min.js"></script>
+		<script src="https://unpkg.com/element-ui@2.13.0/lib/index.js"></script>
+		<script src="https://unpkg.com/http-vue-loader@1.4.2/src/httpVueLoader.js"></script>
+		<script src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"></script>
+		<script src="https://www.layuicdn.com/layer-v3.1.1/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+		<script src="../../static/kj/upload-util.js"></script>
+		<style type="text/css">
+			.el-radio{margin-right: 10px;}
+		</style>
+	</head>
+	<body>
+		<div class="vue-box" style="display: none;" :style="'display: block;'">
+			<div class="c-panel">
+				<!-- ------------- 检索参数 ------------- -->
+				<h4 class="c-title">检索参数</h4>
+				<el-form>
+					<sa-item type="text" name="名称" v-model="p.name"></sa-item>
+					<sa-item name="角色">
+						<el-select v-model="p.roleId">
+							<el-option label="全部" value=""></el-option>
+							<el-option v-for="role in roleList" :key="role.id" :label="role.name" :value="role.id"></el-option>
+						</el-select>
+					</sa-item>
+					<el-button type="primary" icon="el-icon-search" @click="p.pageNo = 1; f5()">查询</el-button>
+					<br>
+					<sa-item name="综合排序" class="s-radio-text">
+						<el-radio-group v-model="p.sortType">
+							<el-radio label="id">最近添加</el-radio>
+							<el-radio label="loginTime">最近登录</el-radio>
+							<el-radio label="loginCount">登录次数</el-radio>
+						</el-radio-group>
+					</sa-item>
+				</el-form>
+				<!-- ------------- 快捷按钮 ------------- -->
+				<div class="fast-btn">
+					<el-button type="primary" icon="el-icon-plus" @click="add" v-if="sa.isAuth('sp-admin-add')">新增</el-button>
+					<el-button type="info"  icon="el-icon-refresh"  @click="f5">重置</el-button>
+				</div>
+				<!-- ------------- 数据列表 ------------- -->
+				<el-table class="data-table" ref="data-table" :data="dataList">
+					<sa-td type="selection"></sa-td>
+					<sa-td type="text" name="姓名" prop="nickname"></sa-td>
+					<sa-td type="text" name="手机/账号" prop="phone" min-width="120px">
+						<template slot-scope="s">
+							<span v-if="s.row.phone">{{s.row.phone}}</span>
+							<span v-else>{{s.row.name}}</span>
+						</template>
+					</sa-td>
+					<sa-td type="text" name="部门" prop="deptName" not='系统管理'></sa-td>
+					<sa-td type="text" name="所属角色" prop="roleName"></sa-td>
+					<sa-td type="datetime" name="创建日期" prop="createTime" width="150px"></sa-td>
+					<sa-td type="datetime" name="最后登录" prop="loginTime" width="150px"></sa-td>
+					<sa-td type="text" name="登录次数" prop="loginCount" not="0" width="100px"></sa-td>
+					<sa-td type="switch" name="账号状态" prop="status" :jv="{1: '正常', 2: '禁用[#ff4949]'}" @change="s => updateStatus(s.row)" width="120px"></sa-td>
+					<el-table-column label="操作" fixed="right" width="330px">
+						<template slot-scope="s">
+							<span @click="getInfo(s.row)">
+								<el-button type="success" class="c-btn" icon="el-icon-view">查看</el-button>
+							</span>
+							<el-dropdown trigger="click" style="font-size: 0.85em;" v-if="sa.isAuth('sp-admin-edit')">
+								<el-button type="primary" class="c-btn">
+									修改资料 <i class="el-icon-arrow-down el-icon--right"></i>
+								</el-button>
+								<el-dropdown-menu slot="dropdown">
+									<span @click="updateName(s.row)">
+										<el-dropdown-item>改手机号</el-dropdown-item>
+									</span>
+									<span @click="updateNickName(s.row)">
+										<el-dropdown-item>改姓名</el-dropdown-item>
+									</span>
+								
+									<span @click="updatePassword(s.row)">
+										<el-dropdown-item>改密码</el-dropdown-item>
+									</span>
+								</el-dropdown-menu>
+							</el-dropdown>
+							<el-dropdown trigger="click" style="font-size: 0.85em;" v-if="sa.isAuth('sp-admin-edit')">
+								<el-button type="primary" class="c-btn">
+									修改角色为 <i class="el-icon-arrow-down el-icon--right"></i>
+								</el-button>
+								<el-dropdown-menu slot="dropdown">
+									<span v-for="role in roleList" :key="role.id" @click="updateRoleId(s.row, role.id, role.name)">
+										<el-dropdown-item :style=" s.row.roleId == role.id ? {color: 'blue'} : null ">{{role.name}}</el-dropdown-item>
+									</span>
+								</el-dropdown-menu>
+							</el-dropdown>
+							<span @click="del(s.row)">
+								<el-button v-if="sa.isAuth('sp-admin-del')" type="danger" class="c-btn" icon="el-icon-delete">删除</el-button>
+							</span>
+						</template>
+					</el-table-column>
+				</el-table>
+				<!-- 分页 -->
+				<sa-item type="page" :curr.sync="p.pageNo" :size.sync="p.pageSize" :total="dataCount" @change="f5()"></sa-item>
+			</div>
+			
+		</div>
+        <script>
+			var app = new Vue({
+				components: {
+					"sa-item": httpVueLoader('../../sa-frame/com/sa-item.vue'),
+					"sa-td": httpVueLoader('../../sa-frame/com/sa-td.vue')
+				},
+				el: '.vue-box',
+				data: {
+					sa: sa, 	// 超级对象
+					p: {		// 查询参数
+						id: '',
+						name: '',
+						roleId: '',
+						sortType: 'id',
+						pageNo: 1,
+						pageSize: 10,
+					},
+					dataCount: 0,
+					dataList: [],	// 数据集合
+					roleList: [],	// 角色集合 
+				},
+				methods: {
+					// 刷新
+					f5: function(isPage){
+						sa.ajax('/admin/getList', this.p, function(res){
+							this.dataList = res.data;	// 数据
+							this.dataCount = res.dataCount;
+							sa.f5TableHeight();		// 刷新表格高度 
+						}.bind(this));
+					},
+					// 新增
+					add: function() {
+						sa.showIframe('添加用户','admin-add.html','600px','80%');
+					},
+					// 查看详情
+					getInfo: function(data) {
+						//sa.showIframe('账号详情', 'admin-info.html?id=' + data.id, '700px', '80%');
+						sa.$page.openAdminInfo(data.id, data.name);
+					},
+					// 查看登录日志 
+					getAdminLogin: function(data) {
+						sa.showIframe('登录日志', '../sp-admin-login/sp-admin-login-list.html?accId=' + data.id, '90%', '90%');
+					},
+					// 查看 - 根据选中的
+					getBySelect: function(data) {
+						var selection = this.$refs['data-table'].selection;
+						if(selection.length == 0) {
+							return sa.msg('请选择一条数据')
+						}
+						this.getInfo(selection[0]);
+					},
+					updateNickName: function(data) {
+						layer.prompt({title: '修改姓名'}, function(pass, index){
+							layer.close(index);
+							sa.ajax('/admin/updateNickName', {id: data.id, nickname: pass}, function(res){
+								data.nickname = pass;
+								layer.msg('修改成功');
+							})
+						});
+					},
+					// 修改名称 
+					updateName: function(data) {
+						layer.prompt({title: '请输入新的手机号'}, function(pass, index){
+							layer.close(index);
+							sa.ajax('/admin/update', {id: data.id, name: pass}, function(res){
+								data.name = pass;
+								layer.msg('修改成功');
+							})
+						});
+					},
+					// 修改头像 
+					updateAvatar: function(data) {
+						sa.uploadImage(function(src) {
+							var p = {id: data.id, avatar: src};
+							sa.ajax('/admin/updateAvatar', p, function(res) {
+								sa.msg('上传成功');
+								data.avatar = src;  
+							}.bind(this));
+						})
+					},
+					// 修改密码
+					updatePassword: function(data) {
+						layer.prompt({title: '修改密码'}, function(pass, index){
+							layer.close(index);
+							if(pass.length < 4) {
+								return layer.msg('新密码长度请不要低于4位');
+							}
+							sa.ajax('/admin/updatePassword', {id: data.id, password: pass}, function(res){
+								layer.msg('修改成功');
+							})
+						});
+					},
+					// 修改角色 
+					updateRoleId: function(data, roleId, roleName) {
+						if(data.id == sa.$sys.getCurrUser().id) {
+							return sa.alert('不能自己修改自己的角色');  
+						}
+						if(data.roleId == roleId) {
+							return sa.alert('该用户已经是' + roleName + '了');	
+						}
+						var str = '将此账号修改为 [' + roleName + '], 请确认?';
+						layer.confirm(str, {title: '请确认'}, function() {
+							sa.ajax('/admin/updateRole', {id: data.id, roleId: roleId}, function(res) {
+								sa.msg('修改成功');
+								data.roleId = roleId;
+								data.roleName = roleName;
+							}.bind(this));
+						}.bind(this));
+					},
+					// 修改用户的状态
+					updateStatus: function(data) {
+						if(data.id == sa.$sys.getCurrUser().id) {
+							data.status = 3 - data.status;  
+							return sa.alert('不能自己封禁自己');  
+						}
+						var is_ok = false;	// 记录是否成功 
+						var ajax = sa.ajax('/admin/updateStatus', {id: data.id, status: data.status}, function(res) {
+							sa.msg('修改成功');
+							is_ok = true;
+						}.bind(this));
+						// 如果未能修改成功, 则回滚 
+						$.when(ajax).done(function() {
+							if(is_ok == false) {
+								data.status = 3 - data.status; 
+							}
+						})
+					},
+					// 模拟登陆
+					runAs: function(data) {
+						// 提交删除 
+						sa.confirm('将要以账号 [ ' + data.name + ' ] 模拟登录,是否确认?', function() {
+							sa.ajax('/admin/runAs?adminId=' + data.id, function(res) {
+								sa.ok('登录成功,即将刷新页面');
+								sessionStorage.runAsToken = res.data;
+								setTimeout(function() {
+									top.location.reload(true);
+								}, 1000)
+							}.bind(this))
+						}.bind(this));
+					},
+					// 删除 
+					del: function (data) {
+						sa.confirm('是否删除,此操作不可撤销', function(){
+							sa.ajax('/admin/delete', {id: data.id},function(res){
+								sa.arrayDelete(app.dataList, data);
+								sa.ok('删除成功');
+								sa.f5TableHeight();		// 刷新表格高度 
+							})
+						});
+					},
+					// 批量删除
+					deleteByIds: function() {
+						// 获取选中元素的id列表
+						let selection = this.$refs['data-table'].selection;
+						let ids = sa.getArrayField(selection, 'id');
+						if(selection.length == 0) {
+							return sa.msg('请至少选择一条数据')
+						}
+						// 提交删除 
+						sa.confirm('是否批量删除选中数据?此操作不可撤销', function() {
+							sa.ajax('/admin/deleteByIds', {ids: ids.join(',')}, function(res) {
+								sa.arrayDelete(this.dataList, selection);
+								sa.ok('删除成功');
+								sa.f5TableHeight();		// 刷新表格高度 
+							}.bind(this))
+						}.bind(this));
+					},
+				},
+				created: function(){
+					this.f5();
+					sa.onInputEnter();	// 监听回车执行查询 
+					// 加载角色 
+					sa.ajax('/role/getList', function(res){
+						this.roleList = res.data;	// 数据  
+					}.bind(this), {msg: null});
+				}
+			})
+			
+		</script>
+	</body>
+</html>

+ 82 - 0
sa-view-sp/sp-admin/update-password.html

@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>修改密码</title>
+		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+		<!-- 所有的 css & js 资源 -->
+		<link rel="stylesheet" href="https://unpkg.com/element-ui@2.13.0/lib/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css"> 
+		<script src="https://unpkg.com/vue@2.6.10/dist/vue.min.js"></script>
+		<script src="https://unpkg.com/element-ui@2.13.0/lib/index.js"></script>
+		<script src="https://unpkg.com/http-vue-loader@1.4.2/src/httpVueLoader.js"></script>
+		<script src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"></script>
+		<script src="https://www.layuicdn.com/layer-v3.1.1/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+		<style>
+			/* body,.sbot.vue-box{background-color: #EEE !important;} */
+			.c-item .c-label{width: 10em;}
+			.c-item .el-input__inner{width: 300px;}
+        </style>
+	</head>
+	<body>
+		<div class="vue-box sbot" style="display: none;" :style="'display: block;'">
+			<!-- ------- 内容部分 ------- -->
+			<div class="s-body">
+				<div class="c-panel">
+					<div class="c-title">数据添加</div>
+					<el-form v-if="m">
+						<sa-item type="password" name="旧密码" v-model="m.oldPwd" br></sa-item>
+						<sa-item type="password" name="再次输入旧密码" v-model="m.oldPwd2" br></sa-item>
+						<sa-item type="password" name="新密码" v-model="m.newPwd" br></sa-item>
+						<sa-item type="password" name="再次输入新密码" v-model="m.newPwd2" br></sa-item>
+					</el-form>
+				</div>
+			</div>
+			<!-- ------- 底部按钮 ------- -->
+			<div class="s-foot">
+				<el-button type="primary" @click="ok()">确定</el-button>
+				<el-button @click="sa.closeCurrIframe()">取消</el-button>
+			</div>
+
+		</div>
+		<script>
+			var app = new Vue({
+				components: {
+					"sa-item": httpVueLoader('../../sa-frame/com/sa-item.vue')
+				},
+				el: '.vue-box',
+				data: {
+					sa: sa,
+					m: {
+						oldPwd: '',
+						oldPwd2: '',
+						newPwd: '',
+						newPwd2: ''
+					},
+				},
+				methods: {
+					// 提交 
+					ok: function() {
+						// 表单校验 
+						let m = this.m;
+						sa.checkNull(m.oldPwd && m.oldPwd2 && m.newPwd && m.newPwd2, '请填写'); 
+						sa.check(m.oldPwd != m.oldPwd2, '旧密码两次输入不一致');
+						sa.check(m.newPwd != m.newPwd2, '新密码两次输入不一致');
+						sa.check(m.newPwd.length < 4, '新密码请不要低于六位数');
+						// 开始修改 
+						sa.ajax('/AdminPassword/update', this.m, function(res) {
+							if(parent != window) {
+								sa.closeCurrIframe();
+								parent.sa.ok2('修改成功');
+							}
+						})
+						//sa.$fast.fastUpdate2('/SysUser/updatePassword', this.m);
+					},
+				},
+				mounted: function() {
+					
+				}
+			})
+		</script>
+	</body>
+</html>

+ 88 - 0
sa-view-sp/sp-apilog/api-log-list-delete.html

@@ -0,0 +1,88 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>角色-添加/修改</title>
+		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css js 资源 -->
+		<link rel="stylesheet" href="https://unpkg.com/element-ui@2.13.0/lib/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css"> 
+		<script src="https://unpkg.com/vue@2.6.10/dist/vue.min.js"></script>
+		<script src="https://unpkg.com/element-ui@2.13.0/lib/index.js"></script>
+		<script src="https://unpkg.com/jquery@3.4.1/dist/jquery.js"></script>
+		<script src="https://www.layuicdn.com/layer-v3.1.1/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+		<style type="text/css">
+			.c-panel .el-form .c-label{width: 6em !important;}
+			.c-panel .el-form .el-input{width: calc(100% - 120px);}
+		</style>
+	</head>
+	<body>
+		<div class="vue-box sbot" style="display: none;" :style="'display: block;'">
+			<!-- ------- 内容部分 ------- -->
+			<div class="s-body">
+				<div class="c-panel">
+					<div class="c-title">数据添加</div>
+					<el-form v-if="m">
+						<!-- no字段: m.id - id -->
+						<div class="c-item br">
+							<label class="c-label">开始日期:</label>
+							<el-date-picker v-model="m.startTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" placeholder="开始日期"></el-date-picker>
+						</div>
+						<div class="c-item br">
+							<label class="c-label">结束日期:</label>
+							<el-date-picker v-model="m.endTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" placeholder="结束日期"></el-date-picker>
+						</div>
+						<div class="c-item br">
+							<label class="c-label">已选范围:</label>
+							<span style="color: red;">{{sa.forDate(m.startTime, 2)}}</span> - 
+							<span style="color: red;">{{sa.forDate(m.endTime, 2)}} </span>
+						</div>
+						<div class="c-item br">
+							<label class="c-label">操作注意:</label>
+							<span style="color: red;">日志删除后不可恢复,请谨慎操作</span>
+						</div>
+					</el-form>
+				</div>
+			</div>
+			<!-- ------- 底部按钮 ------- -->
+			<div class="s-foot">
+				<el-button type="primary" @click="ok()">确定</el-button>
+				<el-button @click="sa.closeCurrIframe()">取消</el-button>
+			</div>
+		</div>
+        <script>
+			
+			var app = new Vue({
+				el: '.vue-box',
+				data: {
+					id: sa.p('id', 0),		// 获取超链接中的id参数(0=添加,非0=修改) 
+					m: {
+						startTime: '',
+						endTime: '',
+					},		// 实体对象 
+				},
+				methods: {
+					// 提交数据 
+					ok: function(){
+						if(sa.isNull(this.m.startTime) || sa.isNull(this.m.endTime) ) {
+							return sa.error('请选择一个时间范围')
+						}
+						// 开始删除
+						sa.ajax('/SgApilog/deleteByStartEnd', this.m, function(res){
+							sa.alert('操作成功, 共删除 ' + res.data + ' 条请求记录', function() {
+								if(parent.app) {
+									parent.app.f5();
+									sa.closeCurrIframe();	// 关闭本页 
+								} 
+							}.bind(this)); 
+						}.bind(this));
+					},
+				},
+				mounted: function(){
+					
+				}
+			})
+		</script>
+	</body>
+</html>

+ 333 - 0
sa-view-sp/sp-apilog/api-log-list.html

@@ -0,0 +1,333 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>api访问记录-列表</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css & js 资源 -->
+		<link rel="stylesheet" href="https://unpkg.com/element-ui@2.13.0/lib/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css">
+		<script src="https://unpkg.com/vue@2.6.10/dist/vue.min.js"></script>
+		<script src="https://unpkg.com/element-ui@2.13.0/lib/index.js"></script>
+		<script src="https://unpkg.com/jquery@3.4.1/dist/jquery.js"></script>
+		<script src="https://www.layuicdn.com/layer-v3.1.1/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+		<style>
+			.req-type-box{color: #FFF;}
+			.req-type-box b{padding: 3px 7px; border-radius: 3px; font-size: 12px;}
+			/* .req-api span{background-color: ; color: #409EFF; border: 1px #409EFF solid; border-radius: 2px; padding: 3px 5px;} */
+			.req-api span{color: #44f; font-weight: 700; margin-left: 3px;}
+			.req-p{display: inline-block; line-height: 1.4; padding: 2px 4px; border-radius: 4px; cursor: pointer;background-color: #fff2f4; color: #c7254e;}
+			.req-ip{background-color: ; color: #409EFF; border: 1px #409EFF solid; border-radius: 2px; padding: 3px 5px;}
+			
+			.req-string{line-height: 1.4; padding: 2px 4px; border-radius: 4px;cursor: pointer; background-color: #ECF5FF;}
+			
+		</style>
+	</head>
+	<body>
+		<div class="vue-box" style="display: none;" :style="'display: block;'">
+			<div class="c-panel">
+				<!-- 参数栏 -->
+				<div class="c-title">检索参数</div>
+				<el-form :inline="true" @submit.native.prevent>
+					<el-form-item label="记录id:">
+						<el-input v-model="p.id" placeholder="精确定位"></el-input>
+					</el-form-item>
+					<el-form-item label="请求ip:">
+						<el-input v-model="p.reqIp" placeholder="IP筛选"></el-input>
+					</el-form-item>
+					<el-form-item label="请求api:">
+						<el-input v-model="p.reqApi" placeholder="API接口筛选"></el-input>
+					</el-form-item>
+					<el-form-item style="min-width: 0px;">
+						<el-button type="primary" icon="el-icon-search" @click="p.pageNo = 1; f5()">查询</el-button>
+					</el-form-item>
+					<br>
+					<el-form-item label="请求token:">
+						<el-input v-model="p.reqToken" placeholder="定向跟踪token"></el-input>
+					</el-form-item>
+					<el-form-item label="userId:">
+						<el-input v-model="p.userId" placeholder="定向跟踪用户"></el-input>
+					</el-form-item>
+					<el-form-item label="adminId:">
+						<el-input v-model="p.adminId" placeholder="定向跟踪用户"></el-input>
+					</el-form-item>
+					<br>
+					<el-form-item label="res状态码:">
+						<el-input v-model="p.resCode" placeholder="状态码筛选"></el-input>
+					</el-form-item>
+					<el-form-item label="请求时间:">
+						<el-date-picker v-model="p.sTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" placeholder="开始日期"></el-date-picker>
+						-
+						<el-date-picker v-model="p.eTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" placeholder="结束日期"></el-date-picker>
+					</el-form-item>
+					<br />
+					<el-form-item label="综合排序:" class="s-radio-text">
+						<el-radio-group v-model="p.sortType">
+							<el-radio label="id">默认</el-radio>
+							<el-radio label="start_time">请求时间</el-radio>
+							<el-radio label="cost_time">请求耗时</el-radio>
+						</el-radio-group>
+					</el-form-item>
+				</el-form>
+				<!-- ------------- 快捷按钮 ------------- -->
+				<div class="fast-btn" style="margin-top: -10px;">
+					<el-button type="primary" icon="el-icon-plus" @click="copyAll()">复制全部</el-button>
+					<!-- <el-button type="success" icon="el-icon-edit" @click="update($refs['data-table'].selection[0])"
+						:disabled="!$refs['data-table'] || $refs['data-table'].selection.length != 1">修改</el-button> -->
+					<el-button type="danger" icon="el-icon-delete" @click="deleteByIds()">删除</el-button>
+					<el-button type="warning" icon="el-icon-download" @click="sa.exportExcel()">导出</el-button>
+					<el-button type="info"  icon="el-icon-refresh"  @click="sa.f5()">重置</el-button>
+					<el-button type="danger" icon="el-icon-delete" @click="deleteByStartEnd()">范围删除</el-button>
+					<el-button type="success" icon="el-icon-view" @click="f5StaData()">统计数据</el-button>
+				</div>
+				<!-- ------------- 数据列表 ------------- -->
+				<el-table class="data-table" ref="data-table" :data="dataList" size="small">
+					<el-table-column type="selection" width="45px"></el-table-column>
+					
+					<el-table-column label="请求id" width="110px">
+						<template slot-scope="s">
+							<div style="font-weight: bold;">{{s.row.id}}</div>
+						</template>
+					</el-table-column>
+					<el-table-column label="请求ip" width="130px">
+						<template slot-scope="s">
+							<span class="req-ip">{{(s.row.reqIp)}}</span>
+						</template>
+					</el-table-column>
+					<el-table-column label="请求方式" width="100px">
+						<template slot-scope="s">
+							<span class="req-type-box">
+								<b style="background-color: #00A65A;" v-if=" s.row.reqType == 'GET' ">GET</b>
+								<b style="background-color: #0073B7;" v-else-if=" s.row.reqType == 'POST' ">POST</b>
+								<b style="background-color: #FF6A00;" v-else>{{s.row.reqType}}</b>
+							</span>
+						</template>
+					</el-table-column>
+					<el-table-column label="请求接口" width="250px">
+						<template slot-scope="s">
+							<p class="req-api"><span>{{sa.maxLength(s.row.reqApi)}}</bspan>
+							</p>
+							<span class="req-p" @click="seeReqParame(s.row)">{{sa.maxLength(s.row.reqParame, 70)}}</span>
+						</template>
+					</el-table-column>
+					<el-table-column label="请求返回" min-width="370px">
+						<template slot-scope="s">
+							<p style="padding-left: 3px;">
+								<b style="color: green;" v-if="s.row.resCode == 200">{{s.row.resCode}} - {{s.row.resMsg}}</b>
+								<b style="color: red;" v-else-if="s.row.resCode == 500 || s.row.resCode == 501">{{s.row.resCode}} - {{s.row.resMsg}}</b>
+								<b style="color: blue;" v-else>{{s.row.resCode}} - {{s.row.resMsg}}</b>
+							</p>
+							<p class="req-string" @click="seeResString(s.row)">
+								{{sa.maxLength(s.row.resString, 70)}}
+							</p>
+						</template>
+					</el-table-column>
+					<el-table-column label="请求账号" width="150px">
+						<template slot-scope="s">
+							<p v-if="s.row.userId == 0">userId:&nbsp;&nbsp;&nbsp;0</p>
+							<p v-else>userId:&nbsp;&nbsp;&nbsp;<el-link @click="sa.alert(s.row.userId)">{{s.row.userId}}</el-link>
+							</p>
+							<p v-if="s.row.adminId == 0">adminId:0</p>
+							<p v-else>adminId:<el-link @click="sa.$page.openAdminInfo(s.row.adminId)">{{s.row.adminId}}</el-link>
+							</p>
+						</template>
+					</el-table-column>
+					<el-table-column label="请求token" width="150px" v-if="!sa.isNull(p.reqToken) ">
+						<template slot-scope="s">
+							<div style="width: 130px;">{{s.row.reqToken}}</div>
+						</template>
+					</el-table-column>
+					<el-table-column label="请求时间" width="280px">
+						<template slot-scope="s">
+							<p>
+								开始:{{sa.forDate(s.row.startTime, 'yyyy-MM-dd HH:mm:ss.ms')}} -
+								<b>{{sa.isNull(sa.forDate2(s.row.startTime), '无')}}</b>
+							</p>
+							<p>
+								结束:{{sa.forDate(s.row.endTime, 'yyyy-MM-dd HH:mm:ss.ms')}} -
+								<b style="color: green;">耗时:{{(s.row.costTime + 0.0) / 1000}}s</b>
+							</p>
+						</template>
+					</el-table-column>
+					<el-table-column label="操作" width="120px">
+						<template slot-scope="s">
+							<el-button type="text" @click="copy(s.row)">复制</el-button>
+							<el-button type="text" @click="del(s.row)" v-if="way == 1">删除</el-button>
+						</template>
+					</el-table-column>
+				</el-table>
+				<!-- 分页 -->
+				<div class="page-box">
+					<el-pagination 
+						background 
+						layout="total, prev, pager, next, sizes, jumper" 
+						:current-page.sync="p.pageNo"
+						:page-size.sync="p.pageSize" 
+						:total="dataCount" 
+						:page-sizes="[1, 5, 10, 20, 30, 40, 50, 100, 1000]" 
+						@current-change="f5()"
+						@size-change="f5()">
+					</el-pagination>
+				</div>
+			</div>
+		</div>
+		<script>
+			var app = new Vue({
+				el: '.vue-box',
+				data: {
+					sa: sa,
+					p: { // 查询参数  
+						pageNo: 1,
+						pageSize: 10,
+						id: '',
+						reqToken: '',
+						reqIp: '',
+						reqApi: '',
+						resCode: '',
+						userId: '',
+						adminId: '',
+						sTime: '',
+						eTime: '',
+						sortType: 'id'
+					},
+					way: sa.p('way', 1),
+					dataCount: 0,
+					dataList: [], // 数据集合
+					isNewestSta: false,	// 当前是否为最新统计数据 
+					staData: {
+						cost_time_count: 0,		// 总计耗时 
+					}
+				},
+				methods: {
+					// 刷新
+					f5: function() {
+						sa.ajax('/SgApilog/getList', sa.removeNull(this.p), function(res) {
+							this.dataList = res.data; // 数据
+							this.dataCount = res.dataCount; // 数据总数 
+							sa.f5TableHeight();		// 刷新表格高度 
+							this.isNewestSta = false;
+						}.bind(this));
+					},
+					// 统计数据
+					f5StaData: function() {
+						var fn = function() {
+							var str = '<b>总计请求:' + this.dataCount + ' 次</b><br/>'
+								+ '<b>总计耗时:' + getDuration(this.staData.cost_time_count) + ' </b>';
+							str = '<big>' + str + '</big>'
+							layer.alert(str, {title: '统计数据'});
+						}.bind(this);
+						if(this.isNewestSta) {
+							fn()
+						} else {
+							sa.ajax('/SgApilog/staBy', sa.removeNull(this.p), function(res) {
+								this.staData = res.data;
+								this.isNewestSta = true;
+								fn();
+							}.bind(this));
+						}
+					},
+					// 复制 
+					copy: function(data) {
+						// sa.showIframe('数据详情', 'api-log-info.html?id=' + data.id);
+						sa.copyText(JSON.stringify(data));
+						sa.ok2('已成功复制到剪贴板');
+					},
+					// 复制全部
+					copyAll: function() {
+						// sa.showIframe('数据详情', 'api-log-info.html?id=' + data.id);
+						sa.copyText(JSON.stringify(this.dataList));
+						sa.ok2('已成功复制到剪贴板');
+					},
+					// 查看:访问参数 
+					seeReqParame: function(data) {
+						var jsonStr = data.reqParame;
+						jsonStr = JSON.stringify(JSON.parse(jsonStr), null, "\t");
+						layer.prompt({
+							title: '请求参数',
+							shadeClose: true,	// 点击遮罩关闭 
+							formType: 2,		// 多行输入 
+							value: jsonStr,		// 要显示的字符串
+							maxlength: 9999999999,	// 最大输入字符长度
+							area: ['600px', '400px'],	// 弹窗尺寸
+							yes: function(index, layero){
+							    layer.close(index); //如果设定了yes回调,需进行手工关闭
+							}
+						})
+					},
+					// 查看:返回参数  
+					seeResString: function(data) {
+						var jsonStr = data.resString;
+						jsonStr = JSON.stringify(JSON.parse(jsonStr), null, "\t");
+						layer.prompt({
+							title: '返回参数',
+							shadeClose: true,	// 点击遮罩关闭 
+							formType: 2,		// 多行输入 
+							value: jsonStr,		// 要显示的字符串
+							maxlength: 9999999999,	// 最大输入字符长度
+							area: ['600px', '400px'],	// 弹窗尺寸
+							yes: function(index, layero){
+							    layer.close(index); //如果设定了yes回调,需进行手工关闭
+							}
+						})
+					},
+					// 删除  
+					del: function(data) {
+						sa.confirm('是否删除,此操作不可撤销', function() {
+							sa.ajax('/SgApilog/delete?id=' + data.id, function(res) {
+								sa.arrayDelete(this.dataList, data);
+								sa.ok('删除成功');
+								sa.f5TableHeight();		// 刷新表格高度 
+							}.bind(this))
+						}.bind(this));
+					},
+					// 批量删除
+					deleteByIds: function() {
+						// 获取选中元素的id列表
+						let selection = this.$refs['data-table'].selection;
+						let ids = sa.getArrayField(selection, 'id');
+						if(selection.length == 0) {
+							return sa.msg('请至少选择一条数据')
+						}
+						// 提交删除 
+						sa.confirm('是否批量删除选中数据?此操作不可撤销', function() {
+							sa.ajax('/SgApilog/deleteByIds', {ids: ids.join(',')}, function(res) {
+								sa.arrayDelete(this.dataList, selection);
+								sa.ok('删除成功');
+								sa.f5TableHeight();		// 刷新表格高度 
+							}.bind(this))
+						}.bind(this));
+					},
+					// 批量删除
+					deleteByStartEnd: function() {
+						sa.showIframe('批量删除', 'api-log-list-delete.html', '600px', '550px');
+					}
+				},
+				created: function() {
+					this.f5();
+					sa.onInputEnter();	// 监听表单动作 
+				}
+			})
+			
+			function getDuration(my_time) {
+				var days = my_time / 1000 / 60 / 60 / 24;
+				var daysRound = Math.floor(days);
+				var hours = my_time / 1000 / 60 / 60 - (24 * daysRound);
+				var hoursRound = Math.floor(hours);
+				var minutes = my_time / 1000 / 60 - (24 * 60 * daysRound) - (60 * hoursRound);
+				var minutesRound = Math.floor(minutes);
+				var seconds = my_time / 1000 - (24 * 60 * 60 * daysRound) - (60 * 60 * hoursRound) - (60 * minutesRound);
+				seconds = parseInt(seconds);
+				if(daysRound >= 1) {
+					return daysRound + '天' + hoursRound + '小时';
+				} else if(hoursRound >= 1) {
+					return hoursRound + '小时' + hoursRound + '分';
+				} else if(minutesRound >= 1) {
+					return minutesRound + '分' + seconds + '秒';
+				} else {
+					return seconds + '秒';
+				}
+			}
+		</script>
+	</body>
+</html>

+ 139 - 0
sa-view-sp/sp-cfg/app-cfg.html

@@ -0,0 +1,139 @@
+<!DOCTYPE html>
+<html>
+	<head>
+	    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+	    <title>应用对公配置</title>
+	    <!-- 所有的 css js 资源 -->
+		<link rel="stylesheet" href="https://unpkg.com/element-ui@2.13.0/lib/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css">
+		<script src="https://unpkg.com/vue@2.6.10/dist/vue.min.js"></script>
+		<script src="https://unpkg.com/element-ui@2.13.0/lib/index.js"></script>
+		<script src="https://unpkg.com/jquery@3.4.1/dist/jquery.js"></script>
+		<script src="https://www.layuicdn.com/layer-v3.1.1/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+		<script src="../../static/kj/upload-util.js"></script>
+		<style type="text/css">
+			html,body,.vue-box{height: 100%; overflow: hidden;}
+			/* .vue-box{padding: 0px;} */
+			.c-panel{height: calc(100% - 4em); position: relative;}
+			.c-panel .c-label{width: 10em;}
+			.c-panel .el-input{width: 500px;}
+			.c-panel .el-textarea{width: 500px;}
+			.logo-img{
+				width: 35px; 
+				height: 35px; 
+				border-radius: 2px; 
+				vertical-align: middle; 
+				margin-right: 0.5em;
+				cursor: pointer;
+			}
+			.s-tab{height: 100%; }
+			.el-tabs__content{height: calc(100% - 130px); overflow: auto;}
+		</style>
+	</head>
+	<body>
+		<div class="vue-box" style="display: none;" :style="'display: block;'">
+			<div class="c-panel" v-if="m != null">
+				<!-- 提示 -->
+				<el-alert style="margin: 10px 0;"
+					type="blue" show-icon 
+					title="App-Config 对外开放,用来配置一些非敏感信息。"
+					>
+				</el-alert>
+				<!-- tab卡片 -->
+				<el-tabs class="s-tab" v-model="activeTab">
+					<!-- ---------------------------------- 系统参数 ---------------------------------- -->
+				    <el-tab-pane label="系统参数" name="tab1">
+						<div class="c-item br">
+							<label class="c-label">系统logo:</label>
+							<img :src="m.logoUrl" class="logo-img" v-if="sa.isNull(m.logoUrl) == false" @click="sa.showImage(m.logoUrl, '400px', '400px')">
+							<el-link type="primary" @click="sa.uploadImage(src => {m.logoUrl = src; sa.ok2('上传成功');})">选择上传</el-link>
+						</div>
+						<div class="c-item br">
+							<label class="c-label">系统名称:</label>
+							<el-input v-model="m.appName"></el-input>
+						</div>
+						<div class="c-item br">
+							<label class="c-label">版本编号:</label>
+							<el-input v-model="m.appVersionNo"></el-input>
+						</div>
+						<div class="c-item br">
+							<label class="c-label">更新描述:</label>
+							<el-input v-model="m.appVersionLog"></el-input>
+						</div>
+					</el-tab-pane>
+					
+					<!-- ---------------------------------- 其它配置 ---------------------------------- -->
+					<el-tab-pane label="其它配置" name="tab2">
+						<br>
+						<span>其它配置</span>
+					</el-tab-pane>
+					
+				</el-tabs>
+				
+				<!-- 确定按钮 -->
+				<div style="position: absolute; bottom: 0px; width: calc(100% - 3em); line-height: 80px; background-color: #FFF;">
+					<hr style="height: 2px;">
+					<div class="c-item">
+						<label class="c-label"></label>
+						<el-button type="primary" icon="el-icon-check" @click="ok">保存修改</el-button>
+						<el-button type="primary" icon="el-icon-refresh-right" @click="f5">重置</el-button>
+					</div>
+				</div>
+			</div>
+		</div>
+		<script type="text/javascript">
+			// 创建一个默认的配置对象
+			function create_m() {
+				return {
+					logoUrl: '',	// 系统logo地址 
+					appName: 'sa-plus快速开发框架'	,// 系统名称
+					appVersionNo: 'v1.0.0',	// 系统版本
+					appVersionLog: '更新于2099-10-1',	// 更新日志 
+				}
+			}
+		</script>
+        <script>
+			var app = new Vue({
+				el: '.vue-box',
+				data: {
+					sa: sa, 	// 超级对象
+					m: null,		// 
+					activeTab: 'tab1',  // 当前显示的tab
+					textareaCfg: { minRows: 3, maxRows: 14} // 文本域的默认配置
+				},
+				methods: {
+					// 初始化配置
+					init: function(str) {
+						// 获取 
+						var cfg = sa.JSONParse(str, {});	// 用户配置
+						var default_cfg = create_m();		// 默认配置
+						// 遍历 
+						for(var key in default_cfg) {
+							if(cfg[key] !== undefined && cfg[key] !== null) {
+								default_cfg[key] = cfg[key];
+							}
+						}
+						// 赋值
+						this.m = default_cfg;
+					},
+					// 刷新
+					f5: function(){
+						sa.ajax('/SpCfg/getCfg', {cfgName: 'app_cfg'}, function(res){
+							this.init(res.data);
+						}.bind(this));
+					},
+					// 提交 
+					ok: function(){
+						sa.ajax('/SpCfg/updateCfg', {cfgName: 'app_cfg', cfgValue: JSON.stringify(this.m)}, function(res){
+							sa.ok2('保存成功');
+						}.bind(this));
+					},
+				},
+				created: function(){
+					this.f5(); 
+				}
+			})
+		</script>
+	</body>
+</html>

+ 147 - 0
sa-view-sp/sp-cfg/server-cfg.html

@@ -0,0 +1,147 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+		<title>服务器私有配置</title>
+		<!-- 所有的 css js 资源 -->
+		<link rel="stylesheet" href="https://unpkg.com/element-ui@2.13.0/lib/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css">
+		<script src="https://unpkg.com/vue@2.6.10/dist/vue.min.js"></script>
+		<script src="https://unpkg.com/http-vue-loader@1.4.2/src/httpVueLoader.js"></script>
+		<script src="https://unpkg.com/element-ui@2.13.0/lib/index.js"></script>
+		<script src="https://unpkg.com/jquery@3.4.1/dist/jquery.js"></script>
+		<script src="https://www.layuicdn.com/layer-v3.1.1/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+		<script src="../../static/kj/upload-util.js"></script>
+		<style type="text/css">
+			html,body,.vue-box{height: 100%; overflow: hidden;}
+			/* .vue-box{padding: 0px;} */
+			.c-panel{height: calc(100% - 4em); position: relative;}
+			.c-panel .c-label{width: 10em;}
+			.c-panel .el-input{width: 500px;}
+			.c-panel .el-textarea{width: 500px;}
+			.logo-img{
+				width: 35px; 
+				height: 35px; 
+				border-radius: 2px; 
+				vertical-align: middle; 
+				margin-right: 0.5em;
+				cursor: pointer;
+			}
+			.s-tab{height: 100%; }
+			.el-tabs__content{height: calc(100% - 130px); overflow: auto;}
+			
+			/* 让头像样式小一点 */
+			.c-item-mline{width: 600px;}
+			.c-item-mline .image-box-2{width: 60px; height: 100px;}
+			.c-item-mline .image-box-2 img{width: 60px; height: 60px;}
+			.c-item-mline .image-box-2.up_img{height: 60px;}
+			.c-item-mline .image-box-2.up_img img{margin: 15px; width: 30px; height: 30px;}
+		</style>
+	</head>
+	<body>
+		<div class="vue-box" style="display: none;" :style="'display: block;'">
+			<div class="c-panel" v-if="m != null">
+				<!-- 提示 -->
+				<el-alert style="margin: 10px 0;"
+					type="blue" show-icon 
+					title="Server-Config 非对外开放,用来配置一些敏感信息。"
+					>
+				</el-alert>
+				<!-- tab卡片 -->
+				<el-tabs class="s-tab" v-model="activeTab">
+					<!-- ---------------------------------- 系统参数 ---------------------------------- -->
+					<el-tab-pane label="系统参数" name="tab1">
+						<div class="c-item br">
+							<label class="c-label">预留信息:</label>
+							<el-input v-model="m.reserveInfo"></el-input>
+						</div>
+						<div style="height: 1px;"></div>
+						<sa-item type="img-list" name="新用户默认头像" v-model="m.userDefaultAvatar" br></sa-item>
+						<sa-item name="" style="margin-top: -20px;" br>
+							<span style="color: #999;">系统将从以上图片中随机选择一张作为新用户头像</span>
+						</sa-item>
+					</el-tab-pane>
+				</el-tabs>
+				
+				<!-- ---------------------------------- 其它配置 ---------------------------------- -->
+				<el-tab-pane label="其它配置" name="tab2">
+					<br>
+					<span>其它配置</span>
+				</el-tab-pane>
+
+				<!-- 确定按钮 -->
+				<div style="position: absolute; bottom: 0px; width: calc(100% - 3em); line-height: 80px; background-color: #FFF;">
+					<hr style="height: 2px;">
+					<div class="c-item">
+						<label class="c-label"></label>
+						<el-button type="primary" icon="el-icon-check" @click="ok">保存修改</el-button>
+						<el-button type="primary" icon="el-icon-refresh-right" @click="f5">重置</el-button>
+					</div>
+				</div>
+			</div>
+		</div>
+		<script type="text/javascript">
+			// 创建一个默认的配置对象
+			function create_m() {
+				return {
+					reserveInfo: '预留信息', // 预留信息 
+					
+					userDefaultAvatar: '',	// 新用户默认头像 
+				}
+			}
+		</script>
+		<script>
+			var app = new Vue({
+				components: {
+					"sa-item": httpVueLoader('../../sa-frame/com/sa-item.vue'),
+				},
+				el: '.vue-box',
+				data: {
+					sa: sa, // 超级对象
+					m: null, // 
+					activeTab: 'tab1'
+				},
+				methods: {
+					// 初始化配置
+					init: function(str) {
+						// 获取 
+						var cfg = sa.JSONParse(str, {}); // 用户配置  
+						var default_cfg = create_m(); // 默认配置  
+						// 遍历 
+						for (var key in default_cfg) {
+							if (cfg[key] !== undefined && cfg[key] !== null) {
+								default_cfg[key] = cfg[key];
+							}
+						}
+						// 赋值
+						this.m = default_cfg;
+					},
+					// 刷新
+					f5: function() {
+						sa.ajax('/SpCfg/getCfg', {
+							cfgName: 'server_cfg'
+						}, function(res) {
+							this.init(res.data);
+						}.bind(this));
+					},
+					// 提交 
+					ok: function() {
+						sa.ajax('/SpCfg/updateCfg', {
+							cfgName: 'server_cfg',
+							cfgValue: JSON.stringify(this.m)
+						}, function(res) {
+							sa.ok2('保存成功');
+						}.bind(this));
+					}
+				},
+				created: function() {
+					this.f5();
+				}
+			})
+
+
+
+		</script>
+	</body>
+</html>

+ 344 - 0
sa-view-sp/sp-console/redis-console.html

@@ -0,0 +1,344 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>redis控制台</title>
+		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css & js 资源 -->
+		<link rel="stylesheet" href="https://unpkg.com/element-ui@2.13.0/lib/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css">
+		<script src="https://unpkg.com/vue@2.6.10/dist/vue.min.js"></script>
+		<script src="https://unpkg.com/element-ui@2.13.0/lib/index.js"></script>
+		<script src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"></script>
+		<script src="https://www.layuicdn.com/layer-v3.1.1/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+		<style type="text/css">
+			.card-box {
+				min-width: 230px;
+				margin-right: 10px;
+				margin-bottom: 10px;
+				display: inline-block;
+				background-color: #f5f5f5;
+				cursor: pointer;
+				transition: all 0.3s;
+			}
+			.card-box:hover{box-shadow: 0 0 20px #999;}
+
+			.card-box .prop-name {
+				padding-left: 14px;
+				padding-top: 14px;
+				color: #666;
+			}
+
+			.card-box .prop-value {
+				/* border: 1px #000 solid; */
+				padding-left: 14px;
+				height: 40px;
+				line-height: 40px;
+				padding-top: 10px;
+				padding-bottom: 10px;
+				font-size: 26px;
+				color: green;
+			}
+			
+			.f5-pre-btn:hover{cursor: pointer; text-decoration: underline;}
+			
+			.key-div{color: green; cursor: pointer;font-weight: bold;}
+			.not-show,.is-show{padding: 5px 10px; background-color: #eee; cursor: pointer; }
+			.is-show{background-color: rgba(0,0,0,0);}
+			
+			/* .k-input input{font-weight: bold;} */
+			
+		</style>
+	</head>
+	<body>
+		<div class="vue-box" style="display: none;" :style="'display: block;'">
+			<!-- ------------- 总览 ------------- -->
+			<div class="c-panel">
+				<div class="c-title">
+					Redis 控制台
+					<span class="f5-pre-btn" @click="f5_pre(false)">刷新</span>
+				</div>
+				<div style="height: 10px;"></div>
+				<div>
+					<div class="card-box" @click="sa.msg('表点我 >_<')">
+						<p class="prop-name">键值总数</p>
+						<p class="prop-value">{{preData.keys_count}}</p>
+					</div>
+					<div class="card-box" @click="sa.msg('表点我 >_<')">
+						<p class="prop-name">命中次数</p>
+						<p class="prop-value">{{preData.keyspace_hits}}</p>
+					</div>
+					<div class="card-box" @click="sa.msg('表点我 >_<')">
+						<p class="prop-name">已用内存</p>
+						<p class="prop-value">{{preData.used_memory_human}}</p>
+					</div>
+					<div class="card-box" @click="sa.msg('表点我 >_<')">
+						<p class="prop-name">内存峰值</p>
+						<p class="prop-value">{{preData.used_memory_peak_human}}</p>
+					</div>
+					<div class="card-box" @click="sa.msg('表点我 >_<')">
+						<p class="prop-name">启动时间</p>
+						<p class="prop-value">{{preData.uptime_in_seconds_str}}</p>
+					</div>
+				</div>
+			</div>
+			<!-- ------------- 检索参数 ------------- -->
+			<div class="c-panel c-table">
+				<div class="c-title">搜索键值</div>
+				<div class="c-item">
+					<!-- <label class="c-label">搜索键值:</label> -->
+					<el-input v-model="p.k" class="k-input" :placeholder="isLike ? '当前为模糊搜索' : '当前为精确搜索'" @keyup.native.enter="f5()" style="width: 400px;"></el-input>
+				</div>
+				<div class="c-item" style="min-width: 0px;">
+					<el-button type="primary" icon="el-icon-search" @click="f5()">查询</el-button>
+					<el-button type="success" icon="el-icon-plus" @click="add()">添加</el-button>
+					<el-button type="danger" icon="el-icon-delete" @click="deleteByKeys()">删除</el-button>
+					<el-button type="info" icon="el-icon-sort" @click="isLike = !isLike; sa.ok('切换成功')">
+						{{isLike ? '切换为精确搜索' : '切换为模糊搜索'}}
+					</el-button>
+				</div>
+				<div style="height: 10px;"></div>
+				<el-table class="data-table" ref="data-table" :data="dataListShow" size="small">
+					<!-- <el-table-column label="键"></el-table-column> -->
+					<el-table-column type="selection" width="45px"></el-table-column>
+					<el-table-column label="键">
+						<template slot-scope="s">
+							<div class="key-div" @click="sa.copyText(s.row.key); sa.msg('复制成功')">{{s.row.key}}</div>
+						</template>
+					</el-table-column>
+					<el-table-column label="值">
+						<template slot-scope="s">
+							<div class="not-show" @click="get(s.row)" v-if="s.row.is_show == false">点击加载</div>
+							<div class="is-show" v-if="s.row.is_show == true" @click="sa.copyText(s.row.value); sa.msg('复制成功')">{{s.row.value}}</div>
+						</template>
+					</el-table-column>
+					<el-table-column label="TTL (秒)" prop="ttl" width="150px"></el-table-column>
+					<el-table-column label="操作" width="250px">
+						<template slot-scope="s">
+							<el-button type="text" @click="get(s.row)">查询</el-button>
+							<el-button type="text" @click="updateValue(s.row)">修改值</el-button>
+							<el-button type="text" @click="updateTTL(s.row)">修改TTL</el-button>
+							<el-button type="text" @click="del(s.row)">删除</el-button>
+						</template>
+					</el-table-column>
+				</el-table>
+				<div class="page-box">
+					<el-pagination background
+						layout="total, prev, pager, next, sizes, jumper" 
+						:current-page.sync="p.pageNo" 
+						:page-size.sync="p.pageSize" 
+						:total="dataCount" 
+						:page-sizes="[1, 10, 20, 50, 100, 1000]" 
+						@current-change="f5ByPage()" 
+						@size-change="f5ByPage()">
+					</el-pagination>
+				</div>
+			</div>
+
+		</div>
+
+		<script>
+			var app = new Vue({
+				el: '.vue-box',
+				data: {
+					sa: sa, // 超级对象
+					p: { // 查询参数 
+						k: '',
+						pageNo: 1,		// 当前页 
+						pageSize: 10,	// 页大小 
+						sortType: 0	// 排序方式 
+					},
+					isLike: true,	// 是否为模糊匹配
+					dataCount: 0,
+					preData: {
+						keys_count: 0, // key 总数 
+						keyspace_hits: 0,	// 被命中次数 
+						used_memory_human: 0, // 已经占用内存数量 
+						used_memory_peak_human: 0, // 内存消耗峰值 
+						uptime_in_seconds: 0, // redis 已经启动的秒数 
+						uptime_in_seconds_str: '0', // redis 已经启动的时间 
+					},
+					dataList: [],
+					dataListShow: [],
+				},
+				methods: {
+					// 根据分页信息显示出来
+					f5ByPage: function() {
+						var dataListShow = [];
+						var start = (this.p.pageNo - 1) * this.p.pageSize;
+						var end = this.p.pageNo * this.p.pageSize;
+						for (var i = start; i < end; i++) {
+							if(i >= this.dataList.length) {
+								break;
+							}
+							dataListShow.push(this.dataList[i]);
+						}
+						this.dataListShow  = dataListShow;
+						sa.f5TableHeight();		// 刷新表格高度 
+					},
+					// 查询key列表 
+					f5: function() {
+						let k = this.p.k;
+						if(this.isLike && k != '') {
+							k = '*' + k + '*';
+						}
+						sa.ajax('/RedisConsole/getKeys', {k: k}, function(res) {
+							var dataList = [];
+							for (var i = 0; i < res.data.length; i++) {
+								dataList.push({
+									key: res.data[i],	// key
+									value: '',		// value 
+									is_show: false,	// 是否已经显示详情 
+									ttl: '未加载',			// 过期时间 
+								})
+							}
+							this.dataList = dataList;
+							this.f5ByPage();
+							this.dataCount = this.dataList.length;
+						}.bind(this), {
+							success501: function(res) {
+								sa.msg(res.msg);
+								this.dataList = [];
+								this.f5ByPage();
+								this.dataCount = 0;
+							}.bind(this)
+						}); 
+					},
+					// 刷新预览 
+					f5_pre: function(is_f5_keys) {
+						// 基本预览信息
+						sa.ajax('/RedisConsole/getPreInfo', this.p, function(res) {
+							res.data.uptime_in_seconds_str = getDuration(parseInt(res.data.uptime_in_seconds) * 1000);
+							this.preData = res.data;
+							// 如果指定不查询keys列表 
+							if(is_f5_keys === false) {
+								return;
+							}
+							// 如果超过了最大值,则提示一下
+							if(res.data.isGtMax) {
+								var tipStr = 'key值数量已达' + this.preData.keys_count + ',为了避免卡顿已取消返回结果列表(您可以增加筛选条件缩短记录总数)';
+								tipStr = '<b style="color: red;">' + tipStr + '</b>';
+								sa.alert(tipStr);
+							} else {
+								this.f5();
+							}
+						}.bind(this)); 
+					},
+					// 加载详情
+					get: function(data) {
+						sa.ajax('/RedisConsole/getByKey?key=' + data.key, function(res) {
+							data.value = res.data.value;
+							data.ttl = res.data.ttl;
+							data.is_show = true;
+							sa.f5TableHeight();	// 刷新表格高度
+						}.bind(this)); 
+					},
+					// 删除
+					del: function(data) {
+						sa.confirm('是否删除,此操作不可撤销', function() {
+							sa.ajax('/RedisConsole/del?key=' + data.key, function(res) {
+								sa.arrayDelete(app.dataListShow, data);
+								sa.ok('删除成功');
+								sa.f5TableHeight();		// 刷新表格高度 
+							})
+						});
+					},
+					// 修改键值 
+					updateValue: function(data) {
+						layer.prompt({
+							title: '修改键值',
+							// shadeClose: true,	// 点击遮罩关闭 
+							formType: 2,		// 多行输入 
+							maxlength: 9999999999,	// 最大输入字符长度
+							area: ['600px', '400px'],	// 弹窗尺寸
+						}, function(pass, index, elem){
+							layer.close(index); //如果设定了yes回调,需进行手工关闭
+							sa.ajax('/RedisConsole/updateValue', {key: data.key, value: pass}, function(res){
+								data.value = pass;
+								layer.msg('修改成功');
+								sa.f5TableHeight();	// 刷新表格高度
+							})
+						});
+					},
+					// 修改ttl 
+					updateTTL: function(data) {
+						sa.prompt('修改TTL', function(pass, index){
+							if(isNaN(pass)) {
+								return sa.error('请输入一个数值');
+							}
+							sa.ajax('/RedisConsole/updateTtl', {key: data.key, ttl: pass}, function(res){
+								data.ttl = pass;
+								sa.ok('修改成功');
+							})
+						});
+					},
+					// 添加
+					add: function() {
+						sa.showIframe('添加键值', 'redis-key-add.html', '800px', '510px');
+					},
+					// 根据id列表删除 
+					deleteByKeys: function() {
+						// 获取选中元素的id列表
+						let selection = this.$refs['data-table'].selection;
+						let keys = sa.getArrayField(selection, 'key');
+						
+						// 判断
+						if(keys.length < 1) {
+							return sa.error('请至少选择一行');
+						}
+						// 删除 
+						sa.confirm('是否删除选中记录,此操作不可撤销', function() {
+							sa.ajax('/RedisConsole/deleteByKeys', {keys: keys.join(',')}, function(res) {
+								sa.arrayDelete(this.dataListShow, selection);
+								sa.ok2('删除成功');
+								sa.f5TableHeight();		// 刷新表格高度 
+							}.bind(this))
+						}.bind(this));
+					},
+					// 刷新秒数 
+					auto_f5_run_time: function() {
+						setInterval(function() {
+							if(this.preData.uptime_in_seconds <= 0 || this.preData.uptime_in_seconds > 60 * 60 * 24) {
+								return;
+							}
+							this.preData.uptime_in_seconds++;
+							this.preData.uptime_in_seconds_str = getDuration(parseInt(this.preData.uptime_in_seconds) * 1000);
+						}.bind(this), 1000);
+					}
+				},
+				created: function() {
+					this.f5_pre();
+					this.auto_f5_run_time();
+				}
+			})
+
+
+
+			function getDuration(my_time) {
+				var days = my_time / 1000 / 60 / 60 / 24;
+				var daysRound = Math.floor(days);
+				var hours = my_time / 1000 / 60 / 60 - (24 * daysRound);
+				var hoursRound = Math.floor(hours);
+				var minutes = my_time / 1000 / 60 - (24 * 60 * daysRound) - (60 * hoursRound);
+				var minutesRound = Math.floor(minutes);
+				var seconds = my_time / 1000 - (24 * 60 * 60 * daysRound) - (60 * 60 * hoursRound) - (60 * minutesRound);
+				seconds = parseInt(seconds);
+				// console.log('转换时间:', daysRound + '天', hoursRound + '时', minutesRound + '分', seconds + '秒');
+				// var time = hoursRound + ':' + minutesRound + ':' + seconds
+				// return time;
+				if(daysRound >= 1) {
+					return daysRound + '天' + hoursRound + '小时';
+				} else if(hoursRound >= 1) {
+					return hoursRound + '小时' + hoursRound + '分';
+				} else if(minutesRound >= 1) {
+					return minutesRound + '分' + seconds + '秒';
+				} else {
+					return seconds + '秒';
+				}
+			}
+			// console.log(getDuration(200000));;
+		</script>
+
+	</body>
+</html>

+ 106 - 0
sa-view-sp/sp-console/redis-key-add.html

@@ -0,0 +1,106 @@
+<!DOCTYPE html>
+<html>
+	<head>
+	    <title>Redis-key值添加</title>
+	    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css & js 资源 -->
+		<link rel="stylesheet" href="https://unpkg.com/element-ui@2.13.0/lib/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css"> 
+		<script src="https://unpkg.com/vue@2.6.10/dist/vue.min.js"></script>
+		<script src="https://unpkg.com/element-ui@2.13.0/lib/index.js"></script>
+		<script src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"></script>
+		<script src="https://www.layuicdn.com/layer-v3.1.1/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+		<style type="text/css">
+			.c-panel .c-label{width: 6em;}
+			/*  普通文本和富文本一起变长  */
+			.c-panel .el-form .el-input, .c-panel .el-form .el-textarea__inner{width: 600px;}
+			body{background-color: #FFF;}
+		</style>
+ 
+	</head>
+	<body>
+		<div class="vue-box sbot" style="display: none;" :style="'display: block;'">
+			<!-- ------- 内容部分 ------- -->
+			<div class="s-body">
+				<div class="c-panel">
+					<div class="c-title">数据添加</div>
+					<el-form v-if="m">
+						<div class="c-item br">
+							<label class="c-label">key:</label>
+							<el-input v-model="m.key"></el-input>
+						</div>
+						<div class="c-item br">
+							<label class="c-label" style="vertical-align: top;">value:</label>
+							<div style="display: inline-block;">
+								<el-input v-model="m.value" type="textarea" :autosize="{ minRows: 14, maxRows: 20}"></el-input>
+							</div>
+						</div>
+						<div class="c-item br">
+							<label class="c-label">ttl:</label>
+							<el-input v-model="m.ttl" placeholder="过期时间 单位/毫秒"></el-input>
+						</div>
+					</el-form>
+				</div>
+			</div>
+			<!-- ------- 底部按钮 ------- -->
+			<div class="s-foot">
+				<el-button type="primary" @click="ok()">确定</el-button>
+				<el-button @click="sa.closeCurrIframe()">取消</el-button>
+			</div>
+		</div>
+		<script type="text/javascript">
+			function crate_model() {
+				return {
+					key: '',
+					value: '',
+					ttl: '',
+					is_show: true
+				}
+			}
+		</script>
+        <script>
+			
+			var app = new Vue({
+				el: '.vue-box',
+				data: {
+					sa: sa, 	// 超级对象
+					m: crate_model()
+				},
+				methods: {
+					// 修改
+					ok: function(){
+						// 开始验证
+						var m = this.m;
+						if(m.key == ''){
+							return sa.error('请输入键');
+						}
+						if(m.value == ''){
+							return sa.error('请输入值');
+						}
+						if(m.ttl == ''){
+							return sa.error('请输入ttl (过期时间)');
+						}
+						if(isNaN(m.ttl)) {
+							return sa.error('ttl 必须是一个数字 ');
+						}
+						// 添加
+						m.ttl = parseInt(m.ttl);
+						sa.ajax('/RedisConsole/set', m, function(res){
+							sa.closeCurrIframe();
+							parent.app.dataListShow.unshift(m);
+							parent.sa.msg('添加成功');
+							parent.sa.f5TableHeight();	// 刷新表格高度
+						}.bind(this));
+					}
+				},
+				mounted: function(){
+					
+				}
+			})
+			
+			
+		</script>
+	</body>
+</html>

+ 20 - 0
sa-view-sp/sp-console/sql-console.html

@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<meta charset="utf-8">
+		<title>SQL控制台</title>
+	</head>
+	<body>
+		<div style="padding: 1em;">
+			加载中...
+		</div>
+		<script src="../../static/sa.js"></script>
+		<script type="text/javascript">
+			setTimeout(function() {
+				// 跳转到sql监控页
+				console.log(sa.cfg.api_url + '/druid/sql.html');
+				location.href = sa.cfg.api_url + '/druid/sql.html';
+			}, 100)
+		</script>
+	</body>
+</html>

+ 63 - 0
sa-view-sp/sp-role/menu-list.html

@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>菜单预览</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css & js 资源 -->
+		<link rel="stylesheet" href="https://unpkg.com/element-ui@2.13.0/lib/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css"> 
+		<script src="https://unpkg.com/vue@2.6.10/dist/vue.min.js"></script>
+		<script src="https://unpkg.com/element-ui@2.13.0/lib/index.js"></script>
+		<script src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"></script>
+		<script src="https://www.layuicdn.com/layer-v3.1.1/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+        <style>
+			body,.el-tree{background-color: #eee;}
+			.el-tree-node{margin: 0.15em 0 !important;}
+			/* 悬浮时颜色更深一点 */
+			.el-tree-node__content:hover{background-color: #CFE8FC !important;}
+        </style>
+	</head>
+	<body>
+		<div class="vue-box" style="display: none;" :style="'display: block;'">
+			<!-- 表格 -->
+			<div style="padding: 0 1em;">
+				<div class="c-title">菜单预览</div>
+		        <!-- 树插件 -->
+				<el-tree
+					ref="tree"
+					:data="dataList"
+					node-key="id"
+					:default-expand-all="true"
+					>
+					<span class="custom-tree-node" slot-scope="s">
+						<span style="color: #2D8CF0;" v-if="s.data.isShow == undefined || s.data.isShow == true">{{ s.data.name }}</span>
+						<span style="color: #999;" v-if="s.data.isShow == false">{{ s.data.name }} (隐藏)</span>
+						<span style="color: #999;" v-if="s.data.info">&emsp;———— {{s.data.info}} </span>
+					</span>
+				</el-tree>
+				<br><br><br>
+			</div>
+		</div>
+		<script src="../../sa-frame/menu-list.js"></script>
+		<script src="../../sa-frame/menu-list-sp.js"></script>
+		<script src="../../sa-frame/index/admin-util.js"></script>
+        <script>
+			var app = new Vue({
+				el: '.vue-box',
+				data: {
+					dataList: [],	// 数据集合 
+				},
+				created: function(){
+					// 全部
+					sa.ajax2('/SysMenu/getList', function(res){
+						menuList = sa_admin_code_util.arrayToTree(menuList);	// 一维转tree 
+						menuList = sa_admin_code_util.refMenuList(menuList);	// 属性处理 
+						this.dataList = menuList;	// 数据  
+					}.bind(this));
+				}
+			})
+		</script>
+	</body>
+</html>

+ 166 - 0
sa-view-sp/sp-role/menu-setup.html

@@ -0,0 +1,166 @@
+
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>权限分配</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css & js 资源 -->
+		<link rel="stylesheet" href="https://unpkg.com/element-ui@2.13.0/lib/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css"> 
+		<script src="https://unpkg.com/vue@2.6.10/dist/vue.min.js"></script>
+		<script src="https://unpkg.com/element-ui@2.13.0/lib/index.js"></script>
+		<script src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"></script>
+		<script src="https://www.layuicdn.com/layer-v3.1.1/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+        <style>
+			body,.el-tree{background-color: #eee;}
+			.el-tree-node{margin: 0.15em 0 !important;}
+			/* 悬浮时颜色更深一点 */
+			.el-tree-node__content:hover{background-color: #CFE8FC !important;}
+        </style>
+	</head>
+	<body>
+		<div class="vue-box sbot" style="display: none;" :style="'display: block;'">
+			<!-- ------- 内容部分 ------- -->
+			<div class="s-body">
+				<!-- 表格 -->
+				<div style="padding: 1em 2em;">
+					<el-form>
+						<div class="c-title">所有权限</div>
+						<!-- 此扩展能递归渲染一个权限树,点击深层次节点,父级节点中没有被选中的节点会被自动选中,单独点击父节点,子节点会全部 选中/去选中 -->
+						<el-tree
+							ref="tree"
+							:data="dataList"
+							show-checkbox 
+							node-key="id"
+							:default-expand-all="true"
+							:default-checked-keys="selectList" 
+							:expand-on-click-node="false"
+							:check-on-click-node="true"
+							:check-strictly="true"
+							@node-click="node_click"
+							@check="node_click"
+							>
+							<span class="custom-tree-node" slot-scope="s">
+								<span style="color: #2D8CF0;" v-if="s.data.isShow == undefined || s.data.isShow == true">{{ s.data.name }}</span>
+								<span style="color: #999;" v-if="s.data.isShow == false">{{ s.data.name }} (按钮权限)</span>
+								<span style="color: #999;" v-if="s.data.info">&emsp;———— {{s.data.info}} </span>
+							</span>
+						</el-tree>
+					</el-form>
+				</div>
+			</div>
+			<!-- ------- 底部按钮 ------- -->
+			<div class="s-foot">
+				<el-button type="success" @click="checkedAll()">全选</el-button>
+				<el-button type="primary" @click="ok()">确定</el-button>
+				<el-button @click="sa.closeCurrIframe()">取消</el-button>
+			</div>
+		</div>
+		<script src="../../sa-frame/menu-list.js"></script> 
+		<script src="../../sa-frame/menu-list-sp.js"></script> 
+		<script src="../../sa-frame/index/admin-util.js"></script>
+        <script>
+			var roleId = sa.p('roleId');
+			var app = new Vue({
+				el: '.vue-box',
+				data: {
+					p: [],
+					dataList: [],	// 数据集合 
+					selectList: [],	// 默认选中
+					ywList: [],		// 一维数组 
+					haveList: []		// 这个角色用的权限id,拷贝 
+				},
+				methods: {
+					// 保存
+					ok: function(clickCount){
+						if(clickCount === undefined) {
+							clickCount = 5;
+						}
+						// 判断是否改掉了关键权限 
+						var keys = this.$refs.tree.getCheckedKeys();		// 设置完拥有的id列表 
+						var rArr = ['1', '99', 'auth', 'role-list'];		// 敏感菜单id列表 
+						var isR = false;									// 是否给改掉了 
+						rArr.forEach(function(item) {
+							// 只有原先有,现在没有,才会被这样判定 
+							if(this.haveList.indexOf(item) > -1 && keys.indexOf(item) == -1) {
+								isR = true;
+								console.log(item);
+								console.log(this.haveList);
+							}
+						}.bind(this))
+						// 提示 
+						if(isR) {
+							var tipStr = '危险!系统检测到您取消了此角色的重要权限,这将导致与之关联的账号可能会无法正常使用后台,您无论如何都要这样设置吗?';
+							tipStr += '<br/>为保证您不是误操作,您还需要继续点击按钮: ' + clickCount + '次'
+							tipStr = '<b style="color: red;">' + tipStr + '</b>';
+							sa.confirm(tipStr, function(res) {
+								if(clickCount <= 1) {
+									this.ok2();
+								} else {
+									clickCount--;
+									this.ok(clickCount);
+								}
+							}.bind(this))
+						} else {
+							this.ok2();
+						}
+					},
+					// 开始设置 
+					ok2: function() {
+						var keys = this.$refs.tree.getCheckedKeys();
+						var url = '/SpRolePermission/updatePcodeByRid';
+						sa.ajax(url, {roleId: roleId, codes: keys.join(',')}, function (res) {
+							sa.alert('设置成功', function(){
+								sa.closeCurrIframe();
+							});
+							// 如果设置的角色与当前登录者的角色一致,则立即显示出来							
+							if(roleId == sa.$sys.getCurrUser().roleId) {
+								top.sa_admin.initMenu(keys);
+								sa.setAuth(keys);
+							}
+						}.bind(this))
+					},
+					// 点击回调, 处理其子节点跟随父节点的选中
+					node_click: function(node) {
+						console.log(node);
+						var is_select = this.$refs.tree.getCheckedKeys().indexOf(node.id) != -1;	// 此节点现在是否被选中 
+						if(node.children){
+							node.children.forEach(function(item) {
+								this.$refs.tree.setChecked(item.id, is_select);
+								// 递归
+								if(item.children) {
+									this.node_click(item);
+								}
+							}.bind(this))
+						}
+					},
+					// 全选/ 取消全选
+					checkedAll: function() {
+						// console.log(this.$refs.tree.getCheckedKeys().length);
+						// console.log(this.ywList.length);
+						if(this.$refs.tree.getCheckedKeys().length != this.ywList.length) {
+							this.$refs['tree'].setCheckedNodes(this.ywList);
+						} else {
+							this.$refs['tree'].setCheckedNodes([]);
+						}
+					}
+				},
+				created: function(){
+					// 全部
+					menuList = sa_admin_code_util.arrayToTree(menuList);	// 一维转tree 
+					menuList = sa_admin_code_util.refMenuList(menuList);	// 属性处理 
+					this.dataList = menuList;	// 数据  
+					this.ywList = sa_admin_code_util.treeToArray(this.dataList);
+						
+					// 拉取此 roleId 的
+					sa.ajax('/SpRolePermission/getPcodeByRid?roleId=' + roleId, function(res) {
+						this.selectList = res.data;		// 选中的列表 
+						this.haveList = [].concat(this.selectList);
+					}.bind(this))
+				}
+			})
+		</script>
+	</body>
+</html>

+ 104 - 0
sa-view-sp/sp-role/role-add.html

@@ -0,0 +1,104 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>角色-添加/修改</title>
+		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css js 资源 -->
+		<link rel="stylesheet" href="https://unpkg.com/element-ui@2.13.0/lib/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css"> 
+		<script src="https://unpkg.com/vue@2.6.10/dist/vue.min.js"></script>
+		<script src="https://unpkg.com/element-ui@2.13.0/lib/index.js"></script>
+		<script src="https://unpkg.com/http-vue-loader@1.4.2/src/httpVueLoader.js"></script>
+		<script src="https://unpkg.com/jquery@3.4.1/dist/jquery.js"></script>
+		<script src="https://www.layuicdn.com/layer-v3.1.1/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+		<style type="text/css">
+			.c-panel .el-form .c-label{width: 6em !important;}
+			.c-panel .el-form .el-input{width: 250px;}
+		</style>
+	</head>
+	<body>
+		<div class="vue-box" :class="{sbot: id}" style="display: none;" :style="'display: block;'">
+			<!-- ------- 内容部分 ------- -->
+			<div class="s-body">
+				<div class="c-panel">
+					<div class="c-title">数据添加</div>
+					<el-form v-if="m">
+						<!-- no字段: m.id - id -->
+						<sa-item type="text" name="角色id" v-model="m.id" br></sa-item>
+						<sa-item type="text" name="角色昵称" v-model="m.name" br></sa-item>
+						<sa-item type="text" name="责任描述" v-model="m.info" br></sa-item>
+						<sa-item name="" class="s-ok" br>
+							<el-button type="primary" icon="el-icon-plus" @click="ok()">保存</el-button>
+						</sa-item>
+					</el-form>
+				</div>
+			</div>
+			<!-- ------- 底部按钮 ------- -->
+			<div class="s-foot">
+				<el-button type="primary" @click="ok()">确定</el-button>
+				<el-button @click="sa.closeCurrIframe()">取消</el-button>
+			</div>
+		</div>
+        <script>
+			
+			var app = new Vue({
+				components: {
+					"sa-item": httpVueLoader('../../sa-frame/com/sa-item.vue'),
+				},
+				el: '.vue-box',
+				data: {
+					id: sa.p('id', 0),		// 获取超链接中的id参数(0=添加,非0=修改) 
+					m: null,		// 实体对象 
+				},
+				methods: {
+					// 创建一个 默认Model 
+					createModel: function() {
+						return {
+							id: '', 
+							name: '',
+							info: '',
+							isLock: 2,
+							// createTime: new Date(),
+							is_update: false,
+						}
+					},
+					// 提交数据 
+					ok: function(){
+						// 验证 
+						let m = this.m;		// 获取 m对象 
+						sa.checkNull(m.name, '请输入角色名字');
+						sa.checkNull(m.info, '请输入责任描述');
+						
+						// 开始增加
+						sa.ajax('/role/add', this.m, function(res){
+							sa.alert('增加成功', function() {
+								if(parent.app) {
+									res.data.is_update = false;
+									parent.app.dataList.push(res.data);
+									parent.sa.f5TableHeight();		// 刷新表格高度 
+									sa.closeCurrIframe();	// 关闭本页 
+								} else {
+									app.m = this.createModel();
+								}
+							}.bind(this)); 
+						}.bind(this));
+					},
+					// 添加/修改 完成后的动作
+					clean: function() {
+						if(this.id == 0) {
+							this.m = this.createModel();
+						} else {
+							parent.app.f5();		// 刷新父页面列表
+							sa.closeCurrIframe();	// 关闭本页 
+						}
+					}
+				},
+				mounted: function(){
+					this.m = this.createModel();
+				}
+			})
+		</script>
+	</body>
+</html>

+ 142 - 0
sa-view-sp/sp-role/role-list.html

@@ -0,0 +1,142 @@
+
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>角色列表</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css & js 资源 -->
+		<link rel="stylesheet" href="https://unpkg.com/element-ui@2.13.0/lib/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css"> 
+		<script src="https://unpkg.com/vue@2.6.10/dist/vue.min.js"></script>
+		<script src="https://unpkg.com/element-ui@2.13.0/lib/index.js"></script>
+		<script src="https://unpkg.com/http-vue-loader@1.4.2/src/httpVueLoader.js"></script>
+		<script src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"></script>
+		<script src="https://www.layuicdn.com/layer-v3.1.1/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+		<style type="text/css">
+			.el-tag{border-radius: 0px;}
+		</style>
+	</head>
+	<body>
+		<div class="vue-box" style="display: none;" :style="'display: block;'">
+			<div class="c-panel">
+				<!-- 参数栏 -->
+				<div class="c-title">检索参数</div>
+				<el-form @submit.native.prevent>
+					<sa-item type="text" name="角色名称" v-model="p.name"></sa-item>
+					<el-button type="primary" icon="el-icon-search" @click="f5()">查询</el-button>
+					<el-button v-if="sa.isAuth('sp-role-add')" type="primary" icon="el-icon-plus" @click="add()">新增</el-button>
+				</el-form>
+				<!-- 数据列表 -->
+				<el-table class="data-table" ref="data-table" :data="dataList">
+					<el-table-column label="编号" prop="id" width="70px" > </el-table-column>
+					<el-table-column label="角色名称">
+						<template slot-scope="s">
+							<el-input v-if="s.row.is_update" v-model="s.row.name"></el-input>
+							<span v-else>{{s.row.name}}</span>
+						</template>
+					</el-table-column>
+					<el-table-column label="责任描述">
+						<template slot-scope="s">
+							<el-input v-if="s.row.is_update" v-model="s.row.info"></el-input>
+							<span v-else>{{s.row.info}}</span>
+						</template>
+					</el-table-column>
+					<el-table-column label="是否锁定" title="锁定的角色为系统维持正常运行的重要角色,不可删除">
+						<template slot-scope="s">
+							<el-tag v-if="s.row.isLock == 1">已锁定</el-tag>
+							<el-tag v-else type="success">未锁定</el-tag>
+						</template>
+					</el-table-column>
+					<el-table-column label="创建日期">
+						<template slot-scope="s">
+							{{sa.forDate(s.row.createTime, 2)}}
+						</template>
+					</el-table-column>
+					<el-table-column label="操作" width="220px">
+						<template slot-scope="s">
+							<el-button v-if="sa.isAuth('sp-role-edit')" type="text" @click="update(s.row)">
+								<span :style="s.row.is_update ? 'color: red;' : ''">修改</span>
+							</el-button>
+							<el-button v-if="sa.isAuth('sp-role-del')" type="text" @click="del(s.row)">删除</el-button>
+							<el-button v-if="sa.isAuth('sp-role-permission')" type="text" @click="menu_setup(s.row)">分配权限</el-button>
+						</template>
+					</el-table-column>
+				</el-table>
+				<!-- ------------- 分页 ------------- -->
+				<sa-item type="page" :curr.sync="p.pageNo" :size.sync="p.pageSize" :total="dataList.length" :sizes="[1000]" @change="f5()"></sa-item>
+			</div>
+		</div>
+        <script>
+			var app = new Vue({
+				components: {
+					"sa-item": httpVueLoader('../../sa-frame/com/sa-item.vue'),
+				},
+				el: '.vue-box',
+				data: {
+					sa: sa, 	// 超级对象
+					p: {	// 查询参数 
+						name: '',
+						pageNo: 1,
+						pageSize: 1000,
+					},
+					dataList: [],	// 数据集合
+				},
+				methods: {
+					// 刷新
+					f5: function(){
+						sa.ajax('/role/getList', this.p, function(res) {
+							this.dataList = sa.listAU(res.data);
+							sa.f5TableHeight();		// 刷新表格高度 
+						}.bind(this));
+					},
+					// 修改
+					update: function (data) {
+						if(data.is_update == false) {
+							data.is_update = true;
+						} else {
+							sa.confirm('是否修改数据?', function(){
+								var data2 = sa.copyJSON(data);
+								data2.createTime = undefined;
+								sa.ajax('/role/update', data2, function(res){
+									sa.ok('修改成功');
+									data.is_update = false;
+								})
+							})
+						}
+					},
+					// 删除
+					del: function (data) {
+						if(data.isLock == 1){
+							return layer.alert('此角色是维持系统正常运行的重要角色,已被锁定,不可删除');
+						};
+						sa.confirm('是否删除,此操作不可撤销', function(){
+							sa.ajax('/role/delete', {id: data.id},function(res){
+								sa.arrayDelete(app.dataList, data);
+								sa.ok('删除成功');
+								sa.f5TableHeight();		// 刷新表格高度 
+							})
+						});
+					},
+					// 添加
+					add: function () {
+						sa.showIframe('新增角色', 'role-add.html?id=-1', '420px', '280px');
+					}, 
+					// 修改权限菜单
+					menu_setup: function(data){
+						var title = '为 ['+data.name+'] 分配权限';
+						sa.showIframe(title, 'menu-setup.html?roleId=' + data.id, '700px', '600px');
+					}
+				},
+				created: function(){
+					this.f5();
+					sa.onInputEnter();	// 监听表单回车执行查询 
+				}
+			})
+			
+			
+		
+		</script>
+	</body>
+</html>

+ 113 - 0
sa-view/sys-dict/sys-dict-add.html

@@ -0,0 +1,113 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>字典管理-添加/修改</title>
+		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css js 资源 -->
+		<link rel="stylesheet" href="../../static/kj/element-ui/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css">
+		<script src="../../static/kj/vue.min.js"></script>
+		<script src="../../static/kj/element-ui/index.js"></script>
+		<script src="../../static/kj/httpVueLoader.js"></script>
+		<script src="../../static/kj/jquery.min.js"></script>
+		<script src="../../static/kj/layer/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+		<style type="text/css">
+			.c-panel .el-form .c-label{width: 7em !important;}
+			.c-panel .el-form .el-input, .c-panel .el-form .el-textarea__inner{width: 250px;}
+		</style>
+	</head>
+	<body>
+		<div class="vue-box" :class="{sbot: id}" style="display: none;" :style="'display: block;'">
+			<!-- ------- 内容部分 ------- -->
+			<div class="s-body">
+				<div class="c-panel">
+                    <div class="c-title" v-if="id == 0">数据添加</div>
+					<div class="c-title" v-else>数据修改</div>
+					<el-form v-if="m">
+						<sa-item type="text" name="父ID" v-model="m.pid" v-if="sa.p('pid', 'nof') == 'nof'" br></sa-item>
+						<sa-item type="text" name="数据编码" v-model="m.dataCode" br></sa-item>
+						<sa-item type="text" name="数据名称/值" v-model="m.dataValue" br></sa-item>
+						<sa-item type="text" name="数据描述" v-model="m.dataDesc" br></sa-item>
+						<sa-item name="" class="s-ok" br>
+							<el-button type="primary" icon="el-icon-plus" @click="ok()">保存</el-button>
+						</sa-item>
+					</el-form>
+				</div>
+			</div>
+			<!-- ------- 底部按钮 ------- -->
+			<div class="s-foot">
+				<el-button type="primary" @click="ok()">确定</el-button>
+				<el-button @click="sa.closeCurrIframe()">取消</el-button>
+			</div>
+		</div>
+        <script>
+			
+			var app = new Vue({
+				components: {
+					"sa-item": httpVueLoader('../../sa-frame/com/sa-item.vue')
+				},
+				el: '.vue-box',
+				data: {
+					id: sa.p('id', 0),		// 获取超链接中的id参数(0=添加,非0=修改) 
+					m: null,		// 实体对象 
+				},
+				methods: {
+					// 创建一个 默认Model 
+					createModel: function() {
+						return {
+							id: '',		// id 
+							pid: sa.p('pid', '-1'),		//  父ID  
+							dataCode: '',		//  数据编码  
+							dataValue: '',		//  数据名称/值  
+							dataDesc: '',		// 数据描述 
+						}
+					},
+					// 提交数据 
+					ok: function(){
+						// 表单校验 
+						let m = this.m;
+						sa.checkNull(m.dataCode, '请输入 [数据编码]');
+						sa.checkNull(m.dataValue, '请输入 [数据名称/值]');
+						sa.checkNull(m.dataDesc, '请输入 [数据描述]');
+				
+						// 开始增加或修改
+						if(this.id <= 0) {	// 添加
+							sa.ajax('/SysDict/add', m, function(res){
+								sa.alert('增加成功', this.clean); 
+							}.bind(this));
+						} else {	// 修改
+							sa.ajax('/SysDict/update', m, function(res){
+								sa.alert('修改成功', this.clean);
+							}.bind(this));
+						}
+					},
+					// 添加/修改 完成后的动作
+					clean: function() {
+						if(this.id == 0) {
+							this.m = this.createModel();
+						} else {
+							parent.app.f5();		// 刷新父页面列表
+							sa.closeCurrIframe();	// 关闭本页 
+						}
+					}
+				},
+				mounted: function(){
+					// 初始化数据 
+					if(this.id <= 0) {	
+						this.m = this.createModel();
+					} else {	
+						sa.ajax('/SysDict/getById?id=' + this.id, function(res) {
+							this.m = res.data;
+							if(res.data == null) {
+								sa.alert('未能查找到 id=' + this.id + " 详细数据");
+							}
+						}.bind(this))
+					}
+				}
+			})
+			
+		</script>
+	</body>
+</html>

+ 64 - 0
sa-view/sys-dict/sys-dict-info.html

@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>字典管理-详情</title>
+		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css js 资源 -->
+		<link rel="stylesheet" href="../../static/kj/element-ui/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css">
+		<script src="../../static/kj/vue.min.js"></script>
+		<script src="../../static/kj/element-ui/index.js"></script>
+		<script src="../../static/kj/httpVueLoader.js"></script>
+		<script src="../../static/kj/jquery.min.js"></script>
+		<script src="../../static/kj/layer/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+		<style type="text/css">
+			.c-panel .c-label{width: 8em;}
+		</style>
+	</head>
+	<body>
+		<div class="vue-box sbot" style="display: none;" :style="'display: block;'">
+			<!-- ------- 内容部分 ------- -->
+			<div class="s-body">
+				<div class="c-panel">
+					<el-form v-if="m">
+						<sa-info name="id" br>{{m.id}}</sa-info>
+						<sa-info name="父ID" br>{{m.pid}}</sa-info>
+						<sa-info name="数据编码" br>{{m.dataCode}}</sa-info>
+						<sa-info name="数据名称/值" br>{{m.dataValue}}</sa-info>
+						<sa-info name="数据描述" br>{{m.dataDesc}}</sa-info>
+					</el-form>
+				</div>
+			</div>
+			<!-- ------- 底部按钮 ------- -->
+			<div class="s-foot">
+				<el-button type="success" @click="sa.closeCurrIframe()">确定</el-button>
+				<el-button @click="sa.closeCurrIframe()">取消</el-button>
+			</div>
+		</div>
+		<script>
+			var app = new Vue({
+				components: {
+					"sa-info": httpVueLoader('../../sa-frame/com/sa-info.vue')
+				},
+				el: '.vue-box',
+				data: {
+					id: sa.p('id', 0),	// 获取数据ID 
+					m: null
+				},
+				methods: {
+				},
+				mounted: function() {
+					sa.ajax('/SysDict/getById?id=' + this.id, function(res) {
+						this.m = res.data;
+						if(res.data == null) {
+							sa.alert('未能查找到 id=' + this.id + " 详细数据");
+						}
+					}.bind(this))
+				}
+			})
+			
+		</script>
+	</body>
+</html>

+ 134 - 0
sa-view/sys-dict/sys-dict-list.html

@@ -0,0 +1,134 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>字典管理-列表</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css & js 资源 -->
+		<link rel="stylesheet" href="../../static/kj/element-ui/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css">
+		<script src="../../static/kj/vue.min.js"></script>
+		<script src="../../static/kj/element-ui/index.js"></script>
+		<script src="../../static/kj/httpVueLoader.js"></script>
+		<script src="../../static/kj/jquery.min.js"></script>
+		<script src="../../static/kj/layer/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+	</head>
+	<body>
+		<div class="vue-box" style="display: none;" :style="'display: block;'">
+			<div class="c-panel">
+				<!-- ------------- 检索参数 ------------- -->
+				<div class="c-title">检索参数</div>
+				<el-form ref="form" :model='p' @submit.native.prevent>
+					<sa-item type="text" name="数据编码" v-model="p.dataCode"></sa-item>
+					<el-button type="primary" icon="el-icon-search" @click="p.pageNo = 1; f5()">查询</el-button>
+				</el-form>
+				<div class="fast-btn">
+					<el-button v-if="sa.isAuth('sys-dict-add')" type="primary" icon="el-icon-plus" @click="add">新增</el-button>
+					<el-button type="info"  icon="el-icon-refresh"  @click="f5">重置</el-button>
+					<slot></slot>
+				</div>
+				<!-- ------------- 数据列表 ------------- -->
+				<el-table class="data-table" ref="data-table" :data="dataList"  row-key="id" border @expand-change="sa.f5TableHeight()">
+					<sa-td name="序号" type="index"></sa-td>
+					<sa-td name="数据编码" prop="dataCode" ></sa-td>
+					<sa-td name="数据名称/值" prop="dataValue" ></sa-td>
+					<sa-td name="数据描述" prop="dataDesc" ></sa-td>
+					<el-table-column label="操作" fixed="right"  width="320px">
+						<template slot-scope="s">
+							<el-button v-if="sa.isAuth('sys-dict-edit')" class="c-btn" type="primary" icon="el-icon-edit" @click="update(s.row)">修改</el-button>
+							<el-button v-if="sa.isAuth('sys-dict-add')" class="c-btn" type="primary" icon="el-icon-plus" @click="addChildren(s.row)">添加子级</el-button>
+							<el-button v-if="sa.isAuth('sys-dict-del')" class="c-btn" type="danger" icon="el-icon-delete" @click="del(s.row)">删除</el-button>
+						</template>
+					</el-table-column>
+				</el-table>
+				<!-- ------------- 分页 ------------- -->
+				<sa-item type="page" :curr.sync="p.pageNo" :size.sync="p.pageSize" :total="dataCount" @change="f5()" :sizes="[1000]"></sa-item>
+			</div>
+		</div>
+		<script>
+			var app = new Vue({
+				components: {
+					"sa-item": httpVueLoader('../../sa-frame/com/sa-item.vue'),  
+					"sa-td": httpVueLoader('../../sa-frame/com/sa-td.vue'),		
+				},
+				el: '.vue-box',
+				data: {
+					p: { // 查询参数  
+						id: '',		// id 
+						pid: '',		//  父ID  
+						dataCode: '',		//  数据编码  
+						dataValue: '',		//  数据名称/值  
+						dataDesc: '',		// 数据描述 
+						pageNo: 1,		// 当前页 
+						pageSize: 10,	// 页大小 
+						sortType: 0		// 排序方式 
+					},
+					dataCount: 0,
+					dataList: [], // 数据集合 
+				},
+				methods: {
+					// 刷新
+					f5: function() {
+						sa.ajax('/SysDict/getTree', sa.removeNull(this.p), function(res) {
+							this.dataList = res.data; // 数据
+							this.dataCount = res.dataCount; // 数据总数 
+							sa.f5TableHeight();		// 刷新表格高度 
+						}.bind(this));
+					},
+					// 查看
+					get: function(data) {
+						sa.showIframe('数据详情', 'sys-dict-info.html?id=' + data.id, '1050px', '90%');
+					},
+					// 查看 - 根据选中的
+					getBySelect: function(data) {
+						var selection = this.$refs['data-table'].selection;
+						if(selection.length == 0) {
+							return sa.msg('请选择一条数据')
+						}
+						this.get(selection[0]);
+					},
+					// 修改
+					update: function(data) {
+						sa.showIframe('修改数据', 'sys-dict-add.html?id=' + data.id, '1000px', '90%');
+					},
+					// 新增
+					add: function(data) {
+						sa.showIframe('新增数据', 'sys-dict-add.html?id=-1', '600px', '90%');
+					},
+					// 新增子级
+					addChildren: function(data) {
+						sa.showIframe('新增数据', 'sys-dict-add.html?id=-1&pid=' + data.id, '600px', '60%');
+					},
+					// 删除
+					del: function(data) {
+						sa.confirm('是否删除,此操作不可撤销', function() {
+							sa.ajax('/SysDict/delete?id=' + data.id, function(res) {
+								this.f5();
+							}.bind(this))
+						}.bind(this));
+					},
+					// 批量删除
+					deleteByIds: function() {
+						// 获取选中元素的id列表 
+						let selection = this.$refs['data-table'].selection;
+						let ids = sa.getArrayField(selection, 'id');
+						if(selection.length == 0) {
+							return sa.msg('请至少选择一条数据')
+						}
+						// 提交删除 
+						sa.confirm('是否批量删除选中数据?此操作不可撤销', function() {
+							sa.ajax('/SysDict/deleteByIds', {ids: ids.join(',')}, function(res) {
+								this.f5();
+							}.bind(this))
+						}.bind(this));
+					},
+				},
+				created: function() {
+					this.f5();
+					sa.onInputEnter();
+				}
+			})
+		</script>
+	</body>
+</html>

+ 148 - 0
sa-view/tb-car-black/tb-car-black-add.html

@@ -0,0 +1,148 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>车辆黑名单-添加/修改</title>
+		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+		<meta name="viewport"
+			content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css js 资源 -->
+		<link rel="stylesheet" href="../../static/kj/element-ui/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css">
+		<script src="../../static/kj/vue.min.js"></script>
+		<script src="../../static/kj/element-ui/index.js"></script>
+		<script src="../../static/kj/httpVueLoader.js"></script>
+		<script src="../../static/kj/jquery.min.js"></script>
+		<script src="../../static/kj/layer/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+		<style type="text/css">
+			.c-panel .el-form .c-label {
+				width: 7em !important;
+			}
+
+			.c-panel .el-form .el-input,
+			.c-panel .el-form .el-textarea__inner {
+				width: 250px;
+			}
+		</style>
+	</head>
+	<body>
+		<div class="vue-box" :class="{sbot: id}" style="display: none;" :style="'display: block;'">
+			<!-- ------- 内容部分 ------- -->
+			<div class="s-body">
+				<div class="c-panel">
+					<div class="c-title" v-if="id == 0">数据添加</div>
+					<div class="c-title" v-else>数据修改</div>
+					<el-form v-if="m">
+						<div class="c-item">
+							<label class="c-label"><span style="color: red;">*</span>部门:</label>
+							<el-select v-model="m.deptId" :disabled="currentUser.deptId!==9999999">
+								<el-option label="请选择" v-for="(item,index) in deptList" :key="item.id"
+									:label="item.name" :value="item.id"></el-option>
+
+							</el-select>
+						</div>
+						<sa-item type="text" name="车牌号" v-model="m.carNo" br need></sa-item>
+						
+						<sa-item type="datetime" name="开始时间" v-model="m.startTime" br need></sa-item>
+						<sa-item type="datetime" name="结束时间" v-model="m.endTime" br need></sa-item>
+						<sa-item type="textarea" name="禁行原因" v-model="m.reason" br need></sa-item>
+						<sa-item name="" class="s-ok" br>
+							<el-button type="primary" icon="el-icon-plus" @click="ok()">保存</el-button>
+						</sa-item>
+					</el-form>
+				</div>
+			</div>
+			<!-- ------- 底部按钮 ------- -->
+			<div class="s-foot">
+				<el-button type="primary" @click="ok()">确定</el-button>
+				<el-button @click="sa.closeCurrIframe()">取消</el-button>
+			</div>
+		</div>
+		<script>
+			var app = new Vue({
+				components: {
+					"sa-item": httpVueLoader('../../sa-frame/com/sa-item.vue')
+				},
+				el: '.vue-box',
+				data: {
+					currentUser: sa.$sys.getCurrUser(),
+					deptList:[],
+					id: sa.p('id', 0), // 获取超链接中的id参数(0=添加,非0=修改) 
+					m: null, // 实体对象 
+				},
+				methods: {
+					getDeptList() {
+						sa.ajax('/TbDept/getList', {
+							pageNo: 1,
+							pageSize: 100
+						}, function(resp) {
+							let list = resp.data;
+							let deptId = this.currentUser.deptId;
+							if (deptId != 9999999) {
+								this.m.deptId = deptId;
+							}
+							this.deptList = list;
+						}.bind(this))
+					},
+					// 创建一个 默认Model 
+					createModel: function() {
+						return {
+							id: '', // 主键 
+							deptId: '', // 部门ID 
+							carNo: '', // 车牌号 
+							reason: '', // 禁行原因 
+							startTime: '', // 禁行开始 
+							endTime: '', // 禁行结束 
+							createTime: '', // 创建时间 
+							createBy: '', // 创建人 
+							updateTime: '', // 更新时间 
+							updateBy: '', // 更新人 
+						}
+					},
+					// 提交数据 
+					ok: function() {
+						// 表单校验 
+						let m = this.m;
+						sa.checkNull(m.deptId, '请选择部门');
+						sa.checkNull(m.startTime, '请输入 [禁行开始]');
+						sa.checkNull(m.endTime, '请输入 [禁行结束]');
+						m=sa.removeNull(m);
+						// 开始增加或修改
+						if (this.id <= 0) { // 添加
+							sa.ajax('/TbCarBlack/add', m, function(res) {
+								sa.alert('增加成功', this.clean);
+							}.bind(this));
+						} else { // 修改
+							sa.ajax('/TbCarBlack/update', m, function(res) {
+								sa.alert('修改成功', this.clean);
+							}.bind(this));
+						}
+					},
+					// 添加/修改 完成后的动作
+					clean: function() {
+						if (this.id == 0) {
+							this.m = this.createModel();
+						} else {
+							parent.app.f5(); // 刷新父页面列表
+							sa.closeCurrIframe(); // 关闭本页 
+						}
+					}
+				},
+				mounted: function() {
+					this.getDeptList();
+					// 初始化数据 
+					if (this.id <= 0) {
+						this.m = this.createModel();
+					} else {
+						sa.ajax('/TbCarBlack/getById?id=' + this.id, function(res) {
+							this.m = res.data;
+							if (res.data == null) {
+								sa.alert('未能查找到 id=' + this.id + " 详细数据");
+							}
+						}.bind(this))
+					}
+				}
+			})
+		</script>
+	</body>
+</html>

+ 73 - 0
sa-view/tb-car-black/tb-car-black-info.html

@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>车辆黑名单-详情</title>
+		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css js 资源 -->
+		<link rel="stylesheet" href="../../static/kj/element-ui/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css">
+		<script src="../../static/kj/vue.min.js"></script>
+		<script src="../../static/kj/element-ui/index.js"></script>
+		<script src="../../static/kj/httpVueLoader.js"></script>
+		<script src="../../static/kj/jquery.min.js"></script>
+		<script src="../../static/kj/layer/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+		<style type="text/css">
+			.c-panel .c-label{width: 8em;}
+		</style>
+	</head>
+	<body>
+		<div class="vue-box sbot" style="display: none;" :style="'display: block;'">
+			<!-- ------- 内容部分 ------- -->
+			<div class="s-body">
+				<div class="c-panel">
+					<el-form v-if="m">
+						<el-row>
+							<el-col :span="12">
+								<sa-info name="部门" br>{{m.deptName}}</sa-info>
+								<sa-info name="车牌号" br>{{m.carNo}}</sa-info>
+								<sa-info name="禁行原因" br>{{m.reason}}</sa-info>
+							</el-col>
+							<el-col :span="12">
+								<sa-info name="禁行开始" br>{{m.startTime}}</sa-info>
+								<sa-info name="禁行结束" br>{{m.endTime}}</sa-info>
+								<sa-info name="创建时间" br>{{m.createTime}}</sa-info>
+								<sa-info name="更新时间" br>{{m.updateTime}}</sa-info>
+							</el-col>
+						</el-row>
+						
+					</el-form>
+				</div>
+			</div>
+			<!-- ------- 底部按钮 ------- -->
+			<div class="s-foot">
+				<el-button type="success" @click="sa.closeCurrIframe()">确定</el-button>
+				<el-button @click="sa.closeCurrIframe()">取消</el-button>
+			</div>
+		</div>
+		<script>
+			var app = new Vue({
+				components: {
+					"sa-info": httpVueLoader('../../sa-frame/com/sa-info.vue')
+				},
+				el: '.vue-box',
+				data: {
+					id: sa.p('id', 0),	// 获取数据ID 
+					m: null
+				},
+				methods: {
+				},
+				mounted: function() {
+					sa.ajax('/TbCarBlack/getById?id=' + this.id, function(res) {
+						this.m = res.data;
+						if(res.data == null) {
+							sa.alert('未能查找到 id=' + this.id + " 详细数据");
+						}
+					}.bind(this))
+				}
+			})
+			
+		</script>
+	</body>
+</html>

+ 142 - 0
sa-view/tb-car-black/tb-car-black-list.html

@@ -0,0 +1,142 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>车辆黑名单-列表</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css & js 资源 -->
+		<link rel="stylesheet" href="../../static/kj/element-ui/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css">
+		<script src="../../static/kj/vue.min.js"></script>
+		<script src="../../static/kj/element-ui/index.js"></script>
+		<script src="../../static/kj/httpVueLoader.js"></script>
+		<script src="../../static/kj/jquery.min.js"></script>
+		<script src="../../static/kj/layer/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+	</head>
+	<body>
+		<div class="vue-box" style="display: none;" :style="'display: block;'">
+			<div class="c-panel">
+				<!-- ------------- 检索参数 ------------- -->
+				<div class="c-title">检索参数</div>
+				<el-form ref="form" :model='p' @submit.native.prevent>
+					<sa-item type="text" name="车牌号" v-model="p.carNo"></sa-item>
+					<el-button type="primary" icon="el-icon-search" @click="p.pageNo = 1; f5()">查询</el-button>
+					<el-button type="primary" icon="el-icon-plus" @click="add" v-if="sa.isAuth('tb-car-black-add')">新增</el-button>
+					<el-button type="info"  icon="el-icon-refresh"  @click="f5">重置</el-button>
+					
+				</el-form>
+				
+				<!-- ------------- 数据列表 ------------- -->
+				<el-table class="data-table" ref="data-table" :data="dataList" style="margin-top: 10px;">
+					<sa-td name="序号" type="index"></sa-td>
+					<sa-td name="部门" prop="deptName" ></sa-td>
+					<sa-td name="车牌号" prop="carNo" ></sa-td>
+					<sa-td name="禁行原因" prop="reason" ></sa-td>
+					<sa-td name="开始时间" prop="startTime" ></sa-td>
+					<sa-td name="结束时间" prop="endTime" ></sa-td>
+					<sa-td name="创建时间" prop="createTime" ></sa-td>
+					<sa-td name="更新时间" prop="updateTime" ></sa-td>
+					<el-table-column label="操作" fixed="right"  width="240px">
+						<template slot-scope="s">
+							<el-button class="c-btn" type="success" icon="el-icon-view" @click="get(s.row)">查看</el-button>
+							<el-button v-if="sa.isAuth('tb-car-black-edit')" class="c-btn" type="primary" icon="el-icon-edit" @click="update(s.row)">修改</el-button>
+							<el-button v-if="sa.isAuth('tb-car-black-del')" class="c-btn" type="danger" icon="el-icon-delete" @click="del(s.row)">删除</el-button>
+						</template>
+					</el-table-column>
+				</el-table>
+				<!-- ------------- 分页 ------------- -->
+				<sa-item type="page" :curr.sync="p.pageNo" :size.sync="p.pageSize" :total="dataCount" @change="f5()"></sa-item>
+			</div>
+		</div>
+		<script>
+			var app = new Vue({
+				components: {
+					"sa-item": httpVueLoader('../../sa-frame/com/sa-item.vue'),  
+					"sa-td": httpVueLoader('../../sa-frame/com/sa-td.vue'),		
+				},
+				el: '.vue-box',
+				data: {
+					p: { // 查询参数  
+						id: '',		// 主键 
+						deptId: '',		// 部门ID 
+						carNo: '',		// 车牌号 
+						reason: '',		// 禁行原因 
+						startTime: '',		// 禁行开始 
+						endTime: '',		// 禁行结束 
+						createTime: '',		// 创建时间 
+						createBy: '',		// 创建人 
+						updateTime: '',		// 更新时间 
+						updateBy: '',		// 更新人 
+						pageNo: 1,		// 当前页 
+						pageSize: 10,	// 页大小 
+						sortType: 0		// 排序方式 
+					},
+					dataCount: 0,
+					dataList: [], // 数据集合 
+				},
+				methods: {
+					// 刷新
+					f5: function() {
+						sa.ajax('/TbCarBlack/getList', sa.removeNull(this.p), function(res) {
+							this.dataList = res.data; // 数据
+							this.dataCount = res.dataCount; // 数据总数 
+							sa.f5TableHeight();		// 刷新表格高度 
+						}.bind(this));
+					},
+					// 查看
+					get: function(data) {
+						sa.showIframe('数据详情', 'tb-car-black-info.html?id=' + data.id, '650px', '70%');
+					},
+					// 查看 - 根据选中的
+					getBySelect: function(data) {
+						var selection = this.$refs['data-table'].selection;
+						if(selection.length == 0) {
+							return sa.msg('请选择一条数据')
+						}
+						this.get(selection[0]);
+					},
+					// 修改
+					update: function(data) {
+						sa.showIframe('修改数据', 'tb-car-black-add.html?id=' + data.id, '650px', '70%');
+					},
+					// 新增
+					add: function(data) {
+						sa.showIframe('新增数据', 'tb-car-black-add.html?id=-1', '650px', '70%');
+					},
+					// 删除
+					del: function(data) {
+						sa.confirm('是否删除,此操作不可撤销', function() {
+							sa.ajax('/TbCarBlack/delete?id=' + data.id, function(res) {
+								sa.arrayDelete(this.dataList, data);
+								sa.ok('删除成功');
+								sa.f5TableHeight();		// 刷新表格高度 
+							}.bind(this))
+						}.bind(this));
+					},
+					// 批量删除
+					deleteByIds: function() {
+						// 获取选中元素的id列表 
+						let selection = this.$refs['data-table'].selection;
+						let ids = sa.getArrayField(selection, 'id');
+						if(selection.length == 0) {
+							return sa.msg('请至少选择一条数据')
+						}
+						// 提交删除 
+						sa.confirm('是否批量删除选中数据?此操作不可撤销', function() {
+							sa.ajax('/TbCarBlack/deleteByIds', {ids: ids.join(',')}, function(res) {
+								sa.arrayDelete(this.dataList, selection);
+								sa.ok('删除成功');
+								sa.f5TableHeight();		// 刷新表格高度 
+							}.bind(this))
+						}.bind(this));
+					},
+				},
+				created: function() {
+					this.f5();
+					sa.onInputEnter();
+				}
+			})
+		</script>
+	</body>
+</html>

+ 162 - 0
sa-view/tb-car-filing/tb-car-filing-add.html

@@ -0,0 +1,162 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>车辆备案-添加/修改</title>
+		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+		<meta name="viewport"
+			content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css js 资源 -->
+		<link rel="stylesheet" href="../../static/kj/element-ui/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css">
+		<script src="../../static/kj/vue.min.js"></script>
+		<script src="../../static/kj/element-ui/index.js"></script>
+		<script src="../../static/kj/httpVueLoader.js"></script>
+		<script src="../../static/kj/jquery.min.js"></script>
+		<script src="../../static/kj/layer/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+		<style type="text/css">
+			.c-panel .el-form .c-label {
+				width: 7.5em !important;
+			}
+
+			.c-panel .el-form .el-input,
+			.c-panel .el-form .el-textarea__inner {
+				width: 250px;
+			}
+		</style>
+	</head>
+	<body>
+		<div class="vue-box" :class="{sbot: id}" style="display: none;" :style="'display: block;'">
+			<!-- ------- 内容部分 ------- -->
+			<div class="s-body">
+				<div class="c-panel">
+					<div class="c-title" v-if="id == 0">数据添加</div>
+					<div class="c-title" v-else>数据修改</div>
+					<el-form v-if="m">
+						<div class="c-item">
+							<label class="c-label"><span style="color: red;">*</span>部门:</label>
+							<el-select v-model="m.deptId" :disabled="currentUser.deptId!==9999999">
+								<el-option label="请选择" v-for="(item,index) in deptList" :key="item.id"
+									:label="item.name" :value="item.id"></el-option>
+
+							</el-select>
+						</div>
+						<sa-item type="text" name="车牌号" v-model="m.carNo" br need></sa-item>
+						<sa-item type="enum" name="类型" v-model="m.type" :jv="{1: '临时', 2: '内部'}" jtype="3" br></sa-item>
+						<sa-item type="text" name="联系人" v-model="m.driverName" br need></sa-item>
+						<sa-item type="num" name="联系电话" v-model="m.driverContact" br need></sa-item>
+						<sa-item type="text" name="联系人身份证" v-model="m.driverIdCard" br need></sa-item>
+						<sa-item type="datetime" name="预计来访时间" v-model="m.preComeTime" br :need="m.type==1"></sa-item>
+						<sa-item type="datetime" name="预计结束时间" v-model="m.preLeaveTime" br :need="m.type==1"></sa-item>
+						<sa-item type="textarea" name="备注" v-model="m.remark" br></sa-item>
+						<sa-item name="" class="s-ok" br>
+							<el-button type="primary" icon="el-icon-plus" @click="ok()">保存</el-button>
+						</sa-item>
+					</el-form>
+				</div>
+			</div>
+			<!-- ------- 底部按钮 ------- -->
+			<div class="s-foot">
+				<el-button type="primary" @click="ok()">确定</el-button>
+				<el-button @click="sa.closeCurrIframe()">取消</el-button>
+			</div>
+		</div>
+		<script>
+			var app = new Vue({
+				components: {
+					"sa-item": httpVueLoader('../../sa-frame/com/sa-item.vue')
+				},
+				el: '.vue-box',
+				data: {
+					currentUser: sa.$sys.getCurrUser(),
+					id: sa.p('id', 0), // 获取超链接中的id参数(0=添加,非0=修改) 
+					m: {
+						id: '', //  
+						deptId: '', // 部门ID 
+						carNo: '', // 车牌号 
+						type: 1, // 类型(1=临时,2=内部) 
+						driverName: '', // 联系人 
+						driverContact: '', // 联系电话 
+						driverIdCard: '', // 联系人身份证 
+						preComeTime: '', // 预计来访时间 
+						preLeaveTime: '', // 预计结束时间 
+						visit: '', // 状态 
+						remark: '', // 备注 
+					}, // 实体对象 
+					deptList: []
+				},
+				methods: {
+
+					// 提交数据 
+					ok: function() {
+						// 表单校验 
+						let m = this.m;
+						sa.checkNull(m.deptId, '请选择部门');
+						sa.checkNull(m.carNo, '请输入 [车牌号]');
+						sa.checkNull(m.driverName, '请输入 [联系人]');
+						let contact = m.driverContact;
+						if (!sa.isPhone(contact)) {
+							sa.error('请输入正确的号码');
+							return false;
+						}
+						sa.checkNull(m.driverIdCard, '请输入 [联系人身份证]');
+						let driverIdCard=m.driverIdCard;
+						if(driverIdCard.length<15){
+							sa.error('请输入正确的身份证');
+							return false;
+						}
+						let type = m.type;
+						if (type == 1) {
+							sa.checkNull(m.preComeTime, '请输入 [预计来访时间]');
+							sa.checkNull(m.preLeaveTime, '请输入 [预计结束时间]');
+						}
+						m=sa.removeNull(m);
+						// 开始增加或修改
+						if (this.id <= 0) { // 添加
+							sa.ajax('/TbCarFiling/add', m, function(res) {
+								sa.alert('增加成功', this.clean);
+							}.bind(this));
+						} else { // 修改
+							sa.ajax('/TbCarFiling/update', m, function(res) {
+								sa.alert('修改成功', this.clean);
+							}.bind(this));
+						}
+					},
+					// 添加/修改 完成后的动作
+					clean: function() {
+						parent.app.f5(); // 刷新父页面列表
+						sa.closeCurrIframe(); // 关闭本页 
+					},
+					getDeptList() {
+						sa.ajax('/TbDept/getList', {
+							pageNo: 1,
+							pageSize: 100
+						}, function(resp) {
+							let list = resp.data;
+							let deptId = this.currentUser.deptId;
+							if (deptId != 9999999) {
+								this.m.deptId = deptId;
+							}
+							this.deptList = list;
+						}.bind(this))
+					},
+					init() {
+						// 初始化数据
+						if (this.id > 0) {
+							sa.ajax('/TbCarFiling/getById?id=' + this.id, function(res) {
+								this.m = res.data;
+								if (res.data == null) {
+									sa.alert('未能查找到 id=' + this.id + " 详细数据");
+								}
+							}.bind(this))
+						}
+					}
+				},
+				mounted() {
+					this.init();
+					this.getDeptList();
+				}
+			})
+		</script>
+	</body>
+</html>

+ 77 - 0
sa-view/tb-car-filing/tb-car-filing-info.html

@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>车辆备案-详情</title>
+		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+		<meta name="viewport"
+			content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css js 资源 -->
+		<link rel="stylesheet" href="../../static/kj/element-ui/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css">
+		<script src="../../static/kj/vue.min.js"></script>
+		<script src="../../static/kj/element-ui/index.js"></script>
+		<script src="../../static/kj/httpVueLoader.js"></script>
+		<script src="../../static/kj/jquery.min.js"></script>
+		<script src="../../static/kj/layer/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+		<style type="text/css">
+			.c-panel .c-label {
+				width: 8em;
+			}
+		</style>
+	</head>
+	<body>
+		<div class="vue-box sbot" style="display: none;" :style="'display: block;'">
+			<!-- ------- 内容部分 ------- -->
+			<div class="s-body">
+				<div class="c-panel">
+					<el-form v-if="m">
+						<el-row>
+							<el-col :span="12">
+								<sa-info name="部门" br>{{m.deptName}}</sa-info>
+								<sa-info name="车牌号" br>{{m.carNo}}</sa-info>
+								<sa-info name="联系人" br>{{m.driverName}}</sa-info>
+								<sa-info name="联系电话" br>{{m.driverContact}}</sa-info>
+								<sa-info name="联系人身份证" br>{{m.driverIdCard}}</sa-info>
+							</el-col>
+							<el-col :span="12">
+								<sa-info type="enum" name="类型" :value="m.type" :jv="{1: '临时', 2: '内部'}" br></sa-info>
+								<sa-info name="预计来访时间" br>{{m.preComeTime}}</sa-info>
+								<sa-info name="预计结束时间" br>{{m.preLeaveTime}}</sa-info>
+								<sa-info name="备注" br>{{m.remark}}</sa-info>
+								<sa-info name="创建时间" br>{{m.createTime}}</sa-info>
+							</el-col>
+						</el-row>
+
+					</el-form>
+				</div>
+			</div>
+			<!-- ------- 底部按钮 ------- -->
+			<div class="s-foot">
+				<el-button type="success" @click="sa.closeCurrIframe()">确定</el-button>
+				<el-button @click="sa.closeCurrIframe()">取消</el-button>
+			</div>
+		</div>
+		<script>
+			var app = new Vue({
+				components: {
+					"sa-info": httpVueLoader('../../sa-frame/com/sa-info.vue')
+				},
+				el: '.vue-box',
+				data: {
+					id: sa.p('id', 0), // 获取数据ID 
+					m: null
+				},
+				methods: {},
+				mounted: function() {
+					sa.ajax('/TbCarFiling/getById?id=' + this.id, function(res) {
+						this.m = res.data;
+						if (res.data == null) {
+							sa.alert('未能查找到 id=' + this.id + " 详细数据");
+						}
+					}.bind(this))
+				}
+			})
+		</script>
+	</body>
+</html>

+ 153 - 0
sa-view/tb-car-filing/tb-car-filing-list.html

@@ -0,0 +1,153 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>车辆备案-列表</title>
+		<meta charset="utf-8">
+		<meta name="viewport"
+			content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css & js 资源 -->
+		<link rel="stylesheet" href="../../static/kj/element-ui/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css">
+		<script src="../../static/kj/vue.min.js"></script>
+		<script src="../../static/kj/element-ui/index.js"></script>
+		<script src="../../static/kj/httpVueLoader.js"></script>
+		<script src="../../static/kj/jquery.min.js"></script>
+		<script src="../../static/kj/layer/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+	</head>
+	<body>
+		<div class="vue-box" style="display: none;" :style="'display: block;'">
+			<div class="c-panel">
+				<!-- ------------- 检索参数 ------------- -->
+				<div class="c-title">检索参数</div>
+				<el-form ref="form" :model='p' @submit.native.prevent>
+					<sa-item type="text" name="车牌号" v-model="p.carNo"></sa-item>
+					<sa-item type="enum" name="类型" v-model="p.type" :jv="{1: '临时', 2: '内部'}" jtype="2" def="不限">
+					</sa-item>
+					<sa-item type="text" name="联系人" v-model="p.driverName"></sa-item>
+					<sa-item type="text" name="联系电话" v-model="p.driverContact"></sa-item>
+					<el-button type="primary" icon="el-icon-search" @click="p.pageNo = 1; f5()">查询</el-button>
+					<el-button type="primary" icon="el-icon-plus" @click="add" v-if="sa.isAuth('tb-car-filing-add')">新增</el-button>
+					<el-button type="info"  icon="el-icon-refresh"  @click="p.carNo='',p.type='',p.driverName='',p.driverContact='',f5()">重置</el-button>
+
+				</el-form>
+				
+				<!-- ------------- 数据列表 ------------- -->
+				<el-table class="data-table" ref="data-table" :data="dataList">
+					<sa-td type="selection"></sa-td>
+					<sa-td  name="部门" prop="deptName"></sa-td>
+					<sa-td name="车牌号" prop="carNo"></sa-td>
+					<sa-td name="类型" prop="type" type="enum" :jv="{1: '临时', 2: '内部'}"></sa-td>
+					<sa-td name="联系人" prop="driverName"></sa-td>
+					<sa-td name="联系电话" prop="driverContact"></sa-td>
+					<sa-td name="联系人身份证" prop="driverIdCardStr" width="150"></sa-td>
+					<sa-td name="预计来访时间" prop="preComeTime"></sa-td>
+					<sa-td name="预计结束时间" prop="preLeaveTime"></sa-td>
+					<sa-td name="创建时间" prop="createTime"></sa-td>
+					<sa-td name="更新时间" prop="updateTime"></sa-td>
+					<el-table-column label="操作" fixed="right" width="240px">
+						<template slot-scope="s">
+							<el-button class="c-btn" type="success" icon="el-icon-view" @click="get(s.row)">查看
+							</el-button>
+							<el-button v-if="sa.isAuth('tb-car-filing-edit')" class="c-btn" type="primary" icon="el-icon-edit" @click="update(s.row)">修改
+							</el-button>
+							<el-button v-if="sa.isAuth('tb-car-filing-del')" class="c-btn" type="danger" icon="el-icon-delete" @click="del(s.row)">删除
+							</el-button>
+						</template>
+					</el-table-column>
+				</el-table>
+				<!-- ------------- 分页 ------------- -->
+				<sa-item type="page" :curr.sync="p.pageNo" :size.sync="p.pageSize" :total="dataCount" @change="f5()">
+				</sa-item>
+			</div>
+		</div>
+		<script>
+			var app = new Vue({
+				components: {
+					"sa-item": httpVueLoader('../../sa-frame/com/sa-item.vue'),
+					"sa-td": httpVueLoader('../../sa-frame/com/sa-td.vue'),
+				},
+				el: '.vue-box',
+				data: {
+					p: { // 查询参数  
+						carNo: '', // 车牌号 
+						type: '', // 类型(1=临时,2=内部) 
+						driverName: '', // 联系人 
+						driverContact: '', // 联系电话 
+						driverIdCard: '', // 联系人身份证 
+						visit: '', // 状态 
+						remark: '', // 备注 
+						pageNo: 1, // 当前页 
+						pageSize: 10, // 页大小 
+						sortType: 0 // 排序方式 
+					},
+					dataCount: 0,
+					dataList: [], // 数据集合 
+				},
+				methods: {
+					// 刷新
+					f5: function() {
+						sa.ajax('/TbCarFiling/getList', sa.removeNull(this.p), function(res) {
+							this.dataList = res.data; // 数据
+							this.dataCount = res.dataCount; // 数据总数 
+							sa.f5TableHeight(); // 刷新表格高度 
+						}.bind(this));
+					},
+					// 查看
+					get: function(data) {
+						sa.showIframe('数据详情', 'tb-car-filing-info.html?id=' + data.id, '700px', '90%');
+					},
+					// 查看 - 根据选中的
+					getBySelect: function(data) {
+						var selection = this.$refs['data-table'].selection;
+						if (selection.length == 0) {
+							return sa.msg('请选择一条数据')
+						}
+						this.get(selection[0]);
+					},
+					// 修改
+					update: function(data) {
+						sa.showIframe('修改数据', 'tb-car-filing-add.html?id=' + data.id, '700px', '90%');
+					},
+					// 新增
+					add: function(data) {
+						sa.showIframe('新增数据', 'tb-car-filing-add.html?id=-1', '700px', '80%');
+					},
+					// 删除
+					del: function(data) {
+						sa.confirm('是否删除,此操作不可撤销', function() {
+							sa.ajax('/TbCarFiling/delete?id=' + data.id, function(res) {
+								sa.arrayDelete(this.dataList, data);
+								sa.ok('删除成功');
+								sa.f5TableHeight(); // 刷新表格高度 
+							}.bind(this))
+						}.bind(this));
+					},
+					// 批量删除
+					deleteByIds: function() {
+						// 获取选中元素的id列表 
+						let selection = this.$refs['data-table'].selection;
+						let ids = sa.getArrayField(selection, 'id');
+						if (selection.length == 0) {
+							return sa.msg('请至少选择一条数据')
+						}
+						// 提交删除 
+						sa.confirm('是否批量删除选中数据?此操作不可撤销', function() {
+							sa.ajax('/TbCarFiling/deleteByIds', {
+								ids: ids.join(',')
+							}, function(res) {
+								sa.arrayDelete(this.dataList, selection);
+								sa.ok('删除成功');
+								sa.f5TableHeight(); // 刷新表格高度 
+							}.bind(this))
+						}.bind(this));
+					},
+				},
+				created: function() {
+					this.f5();
+					sa.onInputEnter();
+				}
+			})
+		</script>
+	</body>
+</html>

+ 134 - 0
sa-view/tb-channel/tb-channel-add.html

@@ -0,0 +1,134 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>通道管理-添加/修改</title>
+		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css js 资源 -->
+		<link rel="stylesheet" href="../../static/kj/element-ui/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css">
+		<script src="../../static/kj/vue.min.js"></script>
+		<script src="../../static/kj/element-ui/index.js"></script>
+		<script src="../../static/kj/httpVueLoader.js"></script>
+		<script src="../../static/kj/jquery.min.js"></script>
+		<script src="../../static/kj/layer/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+		<style type="text/css">
+			.c-panel .el-form .c-label{width: 7em !important;}
+			.c-panel .el-form .el-input, .c-panel .el-form .el-textarea__inner{width: 250px;}
+		</style>
+	</head>
+	<body>
+		<div class="vue-box" :class="{sbot: id}" style="display: none;" :style="'display: block;'">
+			<!-- ------- 内容部分 ------- -->
+			<div class="s-body">
+				<div class="c-panel">
+                    <div class="c-title" v-if="id == 0">数据添加</div>
+					<div class="c-title" v-else>数据修改</div>
+					<el-form v-if="m">
+						<sa-item type="text" name="" v-model="m.id" br></sa-item>
+						<sa-item type="text" name="部门ID" v-model="m.deptId" br></sa-item>
+						<sa-item type="text" name="名称" v-model="m.name" br></sa-item>
+						<sa-item type="text" name="场所ID" v-model="m.venuesId" br></sa-item>
+						<sa-item type="text" name="场所名称" v-model="m.venuesName" br></sa-item>
+						<sa-item type="text" name="设备数" v-model="m.terminalNum" br></sa-item>
+						<sa-item type="text" name="备注" v-model="m.remark" br></sa-item>
+						<sa-item type="text" name="创建时间" v-model="m.createTime" br></sa-item>
+						<sa-item type="text" name="创建人" v-model="m.createBy" br></sa-item>
+						<sa-item type="text" name="更新时间" v-model="m.updateTime" br></sa-item>
+						<sa-item type="text" name="更新人" v-model="m.updateBy" br></sa-item>
+						<sa-item name="" class="s-ok" br>
+							<el-button type="primary" icon="el-icon-plus" @click="ok()">保存</el-button>
+						</sa-item>
+					</el-form>
+				</div>
+			</div>
+			<!-- ------- 底部按钮 ------- -->
+			<div class="s-foot">
+				<el-button type="primary" @click="ok()">确定</el-button>
+				<el-button @click="sa.closeCurrIframe()">取消</el-button>
+			</div>
+		</div>
+        <script>
+			
+			var app = new Vue({
+				components: {
+					"sa-item": httpVueLoader('../../sa-frame/com/sa-item.vue')
+				},
+				el: '.vue-box',
+				data: {
+					id: sa.p('id', 0),		// 获取超链接中的id参数(0=添加,非0=修改) 
+					m: null,		// 实体对象 
+				},
+				methods: {
+					// 创建一个 默认Model 
+					createModel: function() {
+						return {
+							id: '',		//  
+							deptId: '',		// 部门ID 
+							name: '',		// 名称 
+							venuesId: '',		// 场所ID 
+							venuesName: '',		// 场所名称 
+							terminalNum: '',		// 设备数 
+							remark: '',		// 备注 
+							createTime: '',		// 创建时间 
+							createBy: '',		// 创建人 
+							updateTime: '',		// 更新时间 
+							updateBy: '',		// 更新人 
+						}
+					},
+					// 提交数据 
+					ok: function(){
+						// 表单校验 
+						let m = this.m;
+						sa.checkNull(m.id, '请输入 []');
+						sa.checkNull(m.deptId, '请输入 [部门ID]');
+						sa.checkNull(m.name, '请输入 [名称]');
+						sa.checkNull(m.venuesId, '请输入 [场所ID]');
+						sa.checkNull(m.venuesName, '请输入 [场所名称]');
+						sa.checkNull(m.terminalNum, '请输入 [设备数]');
+						sa.checkNull(m.remark, '请输入 [备注]');
+						sa.checkNull(m.createTime, '请输入 [创建时间]');
+						sa.checkNull(m.createBy, '请输入 [创建人]');
+						sa.checkNull(m.updateTime, '请输入 [更新时间]');
+						sa.checkNull(m.updateBy, '请输入 [更新人]');
+				
+						// 开始增加或修改
+						if(this.id <= 0) {	// 添加
+							sa.ajax('/TbChannel/add', m, function(res){
+								sa.alert('增加成功', this.clean); 
+							}.bind(this));
+						} else {	// 修改
+							sa.ajax('/TbChannel/update', m, function(res){
+								sa.alert('修改成功', this.clean);
+							}.bind(this));
+						}
+					},
+					// 添加/修改 完成后的动作
+					clean: function() {
+						if(this.id == 0) {
+							this.m = this.createModel();
+						} else {
+							parent.app.f5();		// 刷新父页面列表
+							sa.closeCurrIframe();	// 关闭本页 
+						}
+					}
+				},
+				mounted: function(){
+					// 初始化数据 
+					if(this.id <= 0) {	
+						this.m = this.createModel();
+					} else {	
+						sa.ajax('/TbChannel/getById?id=' + this.id, function(res) {
+							this.m = res.data;
+							if(res.data == null) {
+								sa.alert('未能查找到 id=' + this.id + " 详细数据");
+							}
+						}.bind(this))
+					}
+				}
+			})
+			
+		</script>
+	</body>
+</html>

+ 70 - 0
sa-view/tb-channel/tb-channel-info.html

@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>通道管理-详情</title>
+		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css js 资源 -->
+		<link rel="stylesheet" href="../../static/kj/element-ui/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css">
+		<script src="../../static/kj/vue.min.js"></script>
+		<script src="../../static/kj/element-ui/index.js"></script>
+		<script src="../../static/kj/httpVueLoader.js"></script>
+		<script src="../../static/kj/jquery.min.js"></script>
+		<script src="../../static/kj/layer/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+		<style type="text/css">
+			.c-panel .c-label{width: 8em;}
+		</style>
+	</head>
+	<body>
+		<div class="vue-box sbot" style="display: none;" :style="'display: block;'">
+			<!-- ------- 内容部分 ------- -->
+			<div class="s-body">
+				<div class="c-panel">
+					<el-form v-if="m">
+						<sa-info name="" br>{{m.id}}</sa-info>
+						<sa-info name="部门ID" br>{{m.deptId}}</sa-info>
+						<sa-info name="名称" br>{{m.name}}</sa-info>
+						<sa-info name="场所ID" br>{{m.venuesId}}</sa-info>
+						<sa-info name="场所名称" br>{{m.venuesName}}</sa-info>
+						<sa-info name="设备数" br>{{m.terminalNum}}</sa-info>
+						<sa-info name="备注" br>{{m.remark}}</sa-info>
+						<sa-info name="创建时间" br>{{m.createTime}}</sa-info>
+						<sa-info name="创建人" br>{{m.createBy}}</sa-info>
+						<sa-info name="更新时间" br>{{m.updateTime}}</sa-info>
+						<sa-info name="更新人" br>{{m.updateBy}}</sa-info>
+					</el-form>
+				</div>
+			</div>
+			<!-- ------- 底部按钮 ------- -->
+			<div class="s-foot">
+				<el-button type="success" @click="sa.closeCurrIframe()">确定</el-button>
+				<el-button @click="sa.closeCurrIframe()">取消</el-button>
+			</div>
+		</div>
+		<script>
+			var app = new Vue({
+				components: {
+					"sa-info": httpVueLoader('../../sa-frame/com/sa-info.vue')
+				},
+				el: '.vue-box',
+				data: {
+					id: sa.p('id', 0),	// 获取数据ID 
+					m: null
+				},
+				methods: {
+				},
+				mounted: function() {
+					sa.ajax('/TbChannel/getById?id=' + this.id, function(res) {
+						this.m = res.data;
+						if(res.data == null) {
+							sa.alert('未能查找到 id=' + this.id + " 详细数据");
+						}
+					}.bind(this))
+				}
+			})
+			
+		</script>
+	</body>
+</html>

+ 172 - 0
sa-view/tb-channel/tb-channel-list.html

@@ -0,0 +1,172 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>通道管理-列表</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css & js 资源 -->
+		<link rel="stylesheet" href="../../static/kj/element-ui/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css">
+		<script src="../../static/kj/vue.min.js"></script>
+		<script src="../../static/kj/element-ui/index.js"></script>
+		<script src="../../static/kj/httpVueLoader.js"></script>
+		<script src="../../static/kj/jquery.min.js"></script>
+		<script src="../../static/kj/layer/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+	</head>
+	<body>
+		<div class="vue-box" style="display: none;" :style="'display: block;'">
+			<div class="c-panel">
+				<!-- ------------- 检索参数 ------------- -->
+				<div class="c-title">检索参数</div>
+				<el-form ref="form" :model='p' @submit.native.prevent>
+					<sa-item type="text" name="" v-model="p.id"></sa-item>
+					<sa-item type="text" name="部门ID" v-model="p.deptId"></sa-item>
+					<sa-item type="text" name="名称" v-model="p.name"></sa-item>
+					<sa-item type="text" name="场所ID" v-model="p.venuesId"></sa-item>
+					<sa-item type="text" name="场所名称" v-model="p.venuesName"></sa-item>
+					<sa-item type="text" name="设备数" v-model="p.terminalNum"></sa-item>
+					<sa-item type="text" name="备注" v-model="p.remark"></sa-item>
+					<sa-item type="text" name="创建时间" v-model="p.createTime"></sa-item>
+					<sa-item type="text" name="创建人" v-model="p.createBy"></sa-item>
+					<sa-item type="text" name="更新时间" v-model="p.updateTime"></sa-item>
+					<sa-item type="text" name="更新人" v-model="p.updateBy"></sa-item>
+					<el-button type="primary" icon="el-icon-search" @click="p.pageNo = 1; f5()">查询</el-button>
+					<br />
+					<sa-item name="综合排序">
+						<el-radio-group v-model="p.sortType" class="s-radio-text">
+							<el-radio :label="0">默认</el-radio>
+							<el-radio :label="1"></el-radio>
+							<el-radio :label="2">部门ID</el-radio>
+							<el-radio :label="3">名称</el-radio>
+							<el-radio :label="4">场所ID</el-radio>
+							<el-radio :label="5">场所名称</el-radio>
+							<el-radio :label="6">设备数</el-radio>
+							<el-radio :label="7">备注</el-radio>
+							<el-radio :label="8">创建时间</el-radio>
+							<el-radio :label="9">创建人</el-radio>
+							<el-radio :label="10">更新时间</el-radio>
+							<el-radio :label="11">更新人</el-radio>
+						</el-radio-group>
+					</sa-item>
+				</el-form>
+				<!-- ------------- 快捷按钮 ------------- -->
+				<sa-item type="fast-btn" show="add,get,delete,export,reset"></sa-item>
+				<!-- ------------- 数据列表 ------------- -->
+				<el-table class="data-table" ref="data-table" :data="dataList" >
+					<sa-td type="selection"></sa-td>
+					<sa-td name="" prop="id" ></sa-td>
+					<sa-td name="部门ID" prop="deptId" ></sa-td>
+					<sa-td name="名称" prop="name" ></sa-td>
+					<sa-td name="场所ID" prop="venuesId" ></sa-td>
+					<sa-td name="场所名称" prop="venuesName" ></sa-td>
+					<sa-td name="设备数" prop="terminalNum" ></sa-td>
+					<sa-td name="备注" prop="remark" ></sa-td>
+					<sa-td name="创建时间" prop="createTime" ></sa-td>
+					<sa-td name="创建人" prop="createBy" ></sa-td>
+					<sa-td name="更新时间" prop="updateTime" ></sa-td>
+					<sa-td name="更新人" prop="updateBy" ></sa-td>
+					<el-table-column label="操作" fixed="right"  width="240px">
+						<template slot-scope="s">
+							<el-button class="c-btn" type="success" icon="el-icon-view" @click="get(s.row)">查看</el-button>
+							<el-button class="c-btn" type="primary" icon="el-icon-edit" @click="update(s.row)">修改</el-button>
+							<el-button class="c-btn" type="danger" icon="el-icon-delete" @click="del(s.row)">删除</el-button>
+						</template>
+					</el-table-column>
+				</el-table>
+				<!-- ------------- 分页 ------------- -->
+				<sa-item type="page" :curr.sync="p.pageNo" :size.sync="p.pageSize" :total="dataCount" @change="f5()"></sa-item>
+			</div>
+		</div>
+		<script>
+			var app = new Vue({
+				components: {
+					"sa-item": httpVueLoader('../../sa-frame/com/sa-item.vue'),  
+					"sa-td": httpVueLoader('../../sa-frame/com/sa-td.vue'),		
+				},
+				el: '.vue-box',
+				data: {
+					p: { // 查询参数  
+						id: '',		//  
+						deptId: '',		// 部门ID 
+						name: '',		// 名称 
+						venuesId: '',		// 场所ID 
+						venuesName: '',		// 场所名称 
+						terminalNum: '',		// 设备数 
+						remark: '',		// 备注 
+						createTime: '',		// 创建时间 
+						createBy: '',		// 创建人 
+						updateTime: '',		// 更新时间 
+						updateBy: '',		// 更新人 
+						pageNo: 1,		// 当前页 
+						pageSize: 10,	// 页大小 
+						sortType: 0		// 排序方式 
+					},
+					dataCount: 0,
+					dataList: [], // 数据集合 
+				},
+				methods: {
+					// 刷新
+					f5: function() {
+						sa.ajax('/TbChannel/getList', sa.removeNull(this.p), function(res) {
+							this.dataList = res.data; // 数据
+							this.dataCount = res.dataCount; // 数据总数 
+							sa.f5TableHeight();		// 刷新表格高度 
+						}.bind(this));
+					},
+					// 查看
+					get: function(data) {
+						sa.showIframe('数据详情', 'tb-channel-info.html?id=' + data.id, '1050px', '90%');
+					},
+					// 查看 - 根据选中的
+					getBySelect: function(data) {
+						var selection = this.$refs['data-table'].selection;
+						if(selection.length == 0) {
+							return sa.msg('请选择一条数据')
+						}
+						this.get(selection[0]);
+					},
+					// 修改
+					update: function(data) {
+						sa.showIframe('修改数据', 'tb-channel-add.html?id=' + data.id, '1000px', '90%');
+					},
+					// 新增
+					add: function(data) {
+						sa.showIframe('新增数据', 'tb-channel-add.html?id=-1', '1000px', '90%');
+					},
+					// 删除
+					del: function(data) {
+						sa.confirm('是否删除,此操作不可撤销', function() {
+							sa.ajax('/TbChannel/delete?id=' + data.id, function(res) {
+								sa.arrayDelete(this.dataList, data);
+								sa.ok('删除成功');
+								sa.f5TableHeight();		// 刷新表格高度 
+							}.bind(this))
+						}.bind(this));
+					},
+					// 批量删除
+					deleteByIds: function() {
+						// 获取选中元素的id列表 
+						let selection = this.$refs['data-table'].selection;
+						let ids = sa.getArrayField(selection, 'id');
+						if(selection.length == 0) {
+							return sa.msg('请至少选择一条数据')
+						}
+						// 提交删除 
+						sa.confirm('是否批量删除选中数据?此操作不可撤销', function() {
+							sa.ajax('/TbChannel/deleteByIds', {ids: ids.join(',')}, function(res) {
+								sa.arrayDelete(this.dataList, selection);
+								sa.ok('删除成功');
+								sa.f5TableHeight();		// 刷新表格高度 
+							}.bind(this))
+						}.bind(this));
+					},
+				},
+				created: function() {
+					this.f5();
+					sa.onInputEnter();
+				}
+			})
+		</script>
+	</body>
+</html>

+ 124 - 0
sa-view/tb-dept/tb-dept-add.html

@@ -0,0 +1,124 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>组织管理-添加/修改</title>
+		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css js 资源 -->
+		<link rel="stylesheet" href="../../static/kj/element-ui/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css">
+		<script src="../../static/kj/vue.min.js"></script>
+		<script src="../../static/kj/element-ui/index.js"></script>
+		<script src="../../static/kj/httpVueLoader.js"></script>
+		<script src="../../static/kj/jquery.min.js"></script>
+		<script src="../../static/kj/layer/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+		<style type="text/css">
+			.c-panel .el-form .c-label{width: 7em !important;}
+			.c-panel .el-form .el-input, .c-panel .el-form .el-textarea__inner{width: 250px;}
+		</style>
+	</head>
+	<body>
+		<div class="vue-box" :class="{sbot: id}" style="display: none;" :style="'display: block;'">
+			<!-- ------- 内容部分 ------- -->
+			<div class="s-body">
+				<div class="c-panel">
+                    <div class="c-title" v-if="id == 0">数据添加</div>
+					<div class="c-title" v-else>数据修改</div>
+					<el-form v-if="m">
+						<sa-item type="text" name="名称" v-model="m.name" br need></sa-item>
+						<sa-item type="text" name="负责人" v-model="m.chargePerson" br need></sa-item>
+						<sa-item type="text" name="联系号码" v-model="m.contact" br need></sa-item>
+						<sa-item type="text" name="部门描述" v-model="m.deptDesc" br ></sa-item>
+						<sa-item type="textarea" name="备注" v-model="m.remark" br></sa-item>
+						<sa-item name="" class="s-ok" br>
+							<el-button type="primary" icon="el-icon-plus" @click="ok()">保存</el-button>
+						</sa-item>
+					</el-form>
+				</div>
+			</div>
+			<!-- ------- 底部按钮 ------- -->
+			<div class="s-foot">
+				<el-button type="primary" @click="ok()">确定</el-button>
+				<el-button @click="sa.closeCurrIframe()">取消</el-button>
+			</div>
+		</div>
+        <script>
+			
+			var app = new Vue({
+				components: {
+					"sa-item": httpVueLoader('../../sa-frame/com/sa-item.vue')
+				},
+				el: '.vue-box',
+				data: {
+					id: sa.p('id', 0),		// 获取超链接中的id参数(0=添加,非0=修改) 
+					m: null,		// 实体对象 
+				},
+				methods: {
+					// 创建一个 默认Model 
+					createModel: function() {
+						return {
+							id: '',		// 主键 
+							name: '',		// 名称 
+							peopleNum: '',		// 人数 
+							desc: '',		// 描述 
+							chargePerson: '',		// 负责人 
+							contact: '',		// 联系号码 
+							createTime: '',		// 创建时间 
+							createBy: '',		// 创建人 
+							updateTime: '',		// 更新时间 
+							updateBy: '',		// 更新人 
+							remark: '',		// 备注 
+						}
+					},
+					// 提交数据 
+					ok: function(){
+						// 表单校验 
+						let m = this.m;
+						let contact=m.contact;
+						sa.checkNull(m.name, '请输入 [名称]');
+						sa.checkNull(m.chargePerson, '请输入 [负责人]');
+						sa.checkNull(contact, '请输入 [联系号码]');
+						if(!sa.isPhone(contact)){
+							sa.error('请输入正确的联系号码');
+							return;
+						}
+						// 开始增加或修改
+						if(this.id <= 0) {	// 添加
+							sa.ajax('/TbDept/add', m, function(res){
+								sa.alert('增加成功', this.clean); 
+							}.bind(this));
+						} else {	// 修改
+							sa.ajax('/TbDept/update', m, function(res){
+								sa.alert('修改成功', this.clean);
+							}.bind(this));
+						}
+					},
+					// 添加/修改 完成后的动作
+					clean: function() {
+						if(this.id == 0) {
+							this.m = this.createModel();
+						} else {
+							parent.app.f5();		// 刷新父页面列表
+							sa.closeCurrIframe();	// 关闭本页 
+						}
+					}
+				},
+				mounted: function(){
+					// 初始化数据 
+					if(this.id <= 0) {	
+						this.m = this.createModel();
+					} else {	
+						sa.ajax('/TbDept/getById?id=' + this.id, function(res) {
+							this.m = res.data;
+							if(res.data == null) {
+								sa.alert('未能查找到 id=' + this.id + " 详细数据");
+							}
+						}.bind(this))
+					}
+				}
+			})
+			
+		</script>
+	</body>
+</html>

+ 70 - 0
sa-view/tb-dept/tb-dept-info.html

@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>组织管理-详情</title>
+		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css js 资源 -->
+		<link rel="stylesheet" href="../../static/kj/element-ui/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css">
+		<script src="../../static/kj/vue.min.js"></script>
+		<script src="../../static/kj/element-ui/index.js"></script>
+		<script src="../../static/kj/httpVueLoader.js"></script>
+		<script src="../../static/kj/jquery.min.js"></script>
+		<script src="../../static/kj/layer/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+		<style type="text/css">
+			.c-panel .c-label{width: 8em;}
+		</style>
+	</head>
+	<body>
+		<div class="vue-box sbot" style="display: none;" :style="'display: block;'">
+			<!-- ------- 内容部分 ------- -->
+			<div class="s-body">
+				<div class="c-panel">
+					<el-form v-if="m">
+						<sa-info type="num" name="主键" :value="m.id" br></sa-info>
+						<sa-info name="名称" br>{{m.name}}</sa-info>
+						<sa-info type="num" name="人数" :value="m.peopleNum" br></sa-info>
+						<sa-info name="描述" br>{{m.desc}}</sa-info>
+						<sa-info name="负责人" br>{{m.chargePerson}}</sa-info>
+						<sa-info name="联系号码" br>{{m.contact}}</sa-info>
+						<sa-info name="创建时间" br>{{m.createTime}}</sa-info>
+						<sa-info name="创建人" br>{{m.createBy}}</sa-info>
+						<sa-info name="更新时间" br>{{m.updateTime}}</sa-info>
+						<sa-info name="更新人" br>{{m.updateBy}}</sa-info>
+						<sa-info type="textarea" name="备注" :value="m.remark" br></sa-info>
+					</el-form>
+				</div>
+			</div>
+			<!-- ------- 底部按钮 ------- -->
+			<div class="s-foot">
+				<el-button type="success" @click="sa.closeCurrIframe()">确定</el-button>
+				<el-button @click="sa.closeCurrIframe()">取消</el-button>
+			</div>
+		</div>
+		<script>
+			var app = new Vue({
+				components: {
+					"sa-info": httpVueLoader('../../sa-frame/com/sa-info.vue')
+				},
+				el: '.vue-box',
+				data: {
+					id: sa.p('id', 0),	// 获取数据ID 
+					m: null
+				},
+				methods: {
+				},
+				mounted: function() {
+					sa.ajax('/TbDept/getById?id=' + this.id, function(res) {
+						this.m = res.data;
+						if(res.data == null) {
+							sa.alert('未能查找到 id=' + this.id + " 详细数据");
+						}
+					}.bind(this))
+				}
+			})
+			
+		</script>
+	</body>
+</html>

+ 146 - 0
sa-view/tb-dept/tb-dept-list.html

@@ -0,0 +1,146 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>组织管理-列表</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css & js 资源 -->
+		<link rel="stylesheet" href="../../static/kj/element-ui/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css">
+		<script src="../../static/kj/vue.min.js"></script>
+		<script src="../../static/kj/element-ui/index.js"></script>
+		<script src="../../static/kj/httpVueLoader.js"></script>
+		<script src="../../static/kj/jquery.min.js"></script>
+		<script src="../../static/kj/layer/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+	</head>
+	<body>
+		<div class="vue-box" style="display: none;" :style="'display: block;'">
+			<div class="c-panel">
+				<!-- ------------- 检索参数 ------------- -->
+				<div class="c-title">检索参数</div>
+				<el-form ref="form" :model='p' @submit.native.prevent>
+					<sa-item type="text" name="名称" v-model="p.name"></sa-item>
+					<sa-item type="text" name="负责人" v-model="p.chargePerson"></sa-item>
+					<sa-item type="text" name="联系号码" v-model="p.contact"></sa-item>
+					<el-button type="primary" icon="el-icon-search" @click="p.pageNo = 1; f5()">查询</el-button>
+					<br />
+				</el-form>
+				<!-- ------------- 快捷按钮 ------------- -->
+				<div class="fast-btn">
+					<el-button type="primary" icon="el-icon-plus" @click="add" v-if="sa.isAuth('tb-dept-add')">新增</el-button>
+					<el-button type="info"  icon="el-icon-refresh"  @click="f5()">重置</el-button>
+				</div>
+				<!-- ------------- 数据列表 ------------- -->
+				<el-table class="data-table" ref="data-table" :data="dataList" >
+					<sa-td name="序号" type="index"></sa-td>
+					<sa-td name="名称" prop="name" ></sa-td>
+					<sa-td name="人数" prop="peopleNum" type="num" not='0'></sa-td>
+					<sa-td name="部门描述" prop="deptDesc" ></sa-td>
+					<sa-td name="负责人" prop="chargePerson" ></sa-td>
+					<sa-td name="联系号码" prop="contact" ></sa-td>
+					<sa-td name="创建时间" prop="createTime" ></sa-td>
+					<sa-td name="备注" prop="remark" type="textarea"></sa-td>
+					<el-table-column label="操作" fixed="right"  width="240px">
+						<template slot-scope="s">
+							<el-button class="c-btn" type="success" icon="el-icon-view" @click="get(s.row)">查看</el-button>
+							<el-button v-if="sa.isAuth('tb-dept-edit')" class="c-btn" type="primary" icon="el-icon-edit" @click="update(s.row)">修改</el-button>
+							<el-button v-if="sa.isAuth('tb-dept-del')" class="c-btn" type="danger" icon="el-icon-delete" @click="del(s.row)">删除</el-button>
+						</template>
+					</el-table-column>
+				</el-table>
+				<!-- ------------- 分页 ------------- -->
+				<sa-item type="page" :curr.sync="p.pageNo" :size.sync="p.pageSize" :total="dataCount" @change="f5()"></sa-item>
+			</div>
+		</div>
+		<script>
+			var app = new Vue({
+				components: {
+					"sa-item": httpVueLoader('../../sa-frame/com/sa-item.vue'),  
+					"sa-td": httpVueLoader('../../sa-frame/com/sa-td.vue'),		
+				},
+				el: '.vue-box',
+				data: {
+					p: { // 查询参数  
+						name: '',		// 名称 
+						peopleNum: '',		// 人数 
+						desc: '',		// 描述 
+						chargePerson: '',		// 负责人 
+						contact: '',		// 联系号码 
+						createTime: '',		// 创建时间 
+						createBy: '',		// 创建人 
+						updateTime: '',		// 更新时间 
+						updateBy: '',		// 更新人 
+						remark: '',		// 备注 
+						pageNo: 1,		// 当前页 
+						pageSize: 10,	// 页大小 
+						sortType: 0		// 排序方式 
+					},
+					dataCount: 0,
+					dataList: [], // 数据集合 
+				},
+				methods: {
+					// 刷新
+					f5: function() {
+						sa.ajax('/TbDept/getList', sa.removeNull(this.p), function(res) {
+							this.dataList = res.data; // 数据
+							this.dataCount = res.dataCount; // 数据总数 
+							sa.f5TableHeight();		// 刷新表格高度 
+						}.bind(this));
+					},
+					// 查看
+					get: function(data) {
+						sa.showIframe('数据详情', 'tb-dept-info.html?id=' + data.id, '1050px', '90%');
+					},
+					// 查看 - 根据选中的
+					getBySelect: function(data) {
+						var selection = this.$refs['data-table'].selection;
+						if(selection.length == 0) {
+							return sa.msg('请选择一条数据')
+						}
+						this.get(selection[0]);
+					},
+					// 修改
+					update: function(data) {
+						sa.showIframe('修改数据', 'tb-dept-add.html?id=' + data.id, '650px', '70%');
+					},
+					// 新增
+					add: function(data) {
+						sa.showIframe('新增数据', 'tb-dept-add.html?id=-1', '650px', '75%');
+					},
+					// 删除
+					del: function(data) {
+						sa.confirm('是否删除,此操作不可撤销', function() {
+							sa.ajax('/TbDept/delete?id=' + data.id, function(res) {
+								sa.arrayDelete(this.dataList, data);
+								sa.ok('删除成功');
+								sa.f5TableHeight();		// 刷新表格高度 
+							}.bind(this))
+						}.bind(this));
+					},
+					// 批量删除
+					deleteByIds: function() {
+						// 获取选中元素的id列表 
+						let selection = this.$refs['data-table'].selection;
+						let ids = sa.getArrayField(selection, 'id');
+						if(selection.length == 0) {
+							return sa.msg('请至少选择一条数据')
+						}
+						// 提交删除 
+						sa.confirm('是否批量删除选中数据?此操作不可撤销', function() {
+							sa.ajax('/TbDept/deleteByIds', {ids: ids.join(',')}, function(res) {
+								sa.arrayDelete(this.dataList, selection);
+								sa.ok('删除成功');
+								sa.f5TableHeight();		// 刷新表格高度 
+							}.bind(this))
+						}.bind(this));
+					},
+				},
+				created: function() {
+					this.f5();
+					sa.onInputEnter();
+				}
+			})
+		</script>
+	</body>
+</html>

+ 151 - 0
sa-view/tb-person-black/tb-person-black-add.html

@@ -0,0 +1,151 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>人员黑名单-添加/修改</title>
+		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+		<meta name="viewport"
+			content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css js 资源 -->
+		<link rel="stylesheet" href="../../static/kj/element-ui/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css">
+		<script src="../../static/kj/vue.min.js"></script>
+		<script src="../../static/kj/element-ui/index.js"></script>
+		<script src="../../static/kj/httpVueLoader.js"></script>
+		<script src="../../static/kj/jquery.min.js"></script>
+		<script src="../../static/kj/layer/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+		<style type="text/css">
+			.c-panel .el-form .c-label {
+				width: 7em !important;
+			}
+
+			.c-panel .el-form .el-input,
+			.c-panel .el-form .el-textarea__inner {
+				width: 250px;
+			}
+		</style>
+	</head>
+	<body>
+		<div class="vue-box" :class="{sbot: id}" style="display: none;" :style="'display: block;'">
+			<!-- ------- 内容部分 ------- -->
+			<div class="s-body">
+				<div class="c-panel">
+					<div class="c-title" v-if="id == 0">数据添加</div>
+					<div class="c-title" v-else>数据修改</div>
+					<el-form v-if="m">
+						<div class="c-item">
+							<label class="c-label"><span style="color: red;">*</span>部门:</label>
+							<el-select v-model="m.deptId" :disabled="currentUser.deptId!==9999999">
+								<el-option label="请选择" v-for="(item,index) in deptList" :key="item.id"
+									:label="item.name" :value="item.id"></el-option>
+
+							</el-select>
+						</div>
+						<sa-item type="text" name="名字" v-model="m.name" br need></sa-item>
+						<sa-item type="text" name="身份证" v-model="m.idCard" br need></sa-item>
+						<sa-item type="datetime" name="开始时间" v-model="m.startTime" br need></sa-item>
+						<sa-item type="datetime" name="结束时间" v-model="m.endTime" br need></sa-item>
+						<sa-item type="textarea" name="禁行原因" v-model="m.reason" br></sa-item>
+						<sa-item name="" class="s-ok" br>
+							<el-button type="primary" icon="el-icon-plus" @click="ok()">保存</el-button>
+						</sa-item>
+					</el-form>
+				</div>
+			</div>
+			<!-- ------- 底部按钮 ------- -->
+			<div class="s-foot">
+				<el-button type="primary" @click="ok()">确定</el-button>
+				<el-button @click="sa.closeCurrIframe()">取消</el-button>
+			</div>
+		</div>
+		<script>
+			var app = new Vue({
+				components: {
+					"sa-item": httpVueLoader('../../sa-frame/com/sa-item.vue')
+				},
+				el: '.vue-box',
+				data: {
+					currentUser: sa.$sys.getCurrUser(),
+					deptList:[],
+					id: sa.p('id', 0), // 获取超链接中的id参数(0=添加,非0=修改) 
+					m: null, // 实体对象 
+				},
+				methods: {
+					getDeptList() {
+						sa.ajax('/TbDept/getList', {
+							pageNo: 1,
+							pageSize: 100
+						}, function(resp) {
+							let list = resp.data;
+							let deptId = this.currentUser.deptId;
+							if (deptId != 9999999) {
+								this.m.deptId = deptId;
+							}
+							this.deptList = list;
+						}.bind(this))
+					},
+					// 创建一个 默认Model 
+					createModel: function() {
+						return {
+							deptId: '', // 部门ID 
+							name: '', // 名字 
+							idCard: '', // 身份证 
+							reason: '', // 禁行原因 
+							startTime: '', // 禁行开始 
+							endTime: '', // 禁行结束 
+						}
+					},
+					// 提交数据 
+					ok: function() {
+						// 表单校验 
+						let m = this.m;
+						sa.checkNull(m.deptId, '请选择部门');
+						sa.checkNull(m.name, '请输入 [名字]');
+						sa.checkNull(m.idCard, '请输入 [身份证]');
+						let idCard = m.idCard;
+						if (idCard.length < 15) {
+							sa.error('请输入正确的身份证');
+							return false;
+						}
+						sa.checkNull(m.startTime, '请选择 [禁行开始时间]');
+						sa.checkNull(m.endTime, '请选择 [禁行结束时间]');
+						m = sa.removeNull(m);
+						// 开始增加或修改
+						if (this.id <= 0) { // 添加
+							sa.ajax('/TbPersonBlack/add', m, function(res) {
+								sa.alert('增加成功', this.clean);
+							}.bind(this));
+						} else { // 修改
+							sa.ajax('/TbPersonBlack/update', m, function(res) {
+								sa.alert('修改成功', this.clean);
+							}.bind(this));
+						}
+					},
+					// 添加/修改 完成后的动作
+					clean: function() {
+						if (this.id == 0) {
+							this.m = this.createModel();
+						} else {
+							parent.app.f5(); // 刷新父页面列表
+							sa.closeCurrIframe(); // 关闭本页 
+						}
+					}
+				},
+				mounted: function() {
+					this.getDeptList();
+					// 初始化数据 
+					if (this.id <= 0) {
+						this.m = this.createModel();
+					} else {
+						sa.ajax('/TbPersonBlack/getById?id=' + this.id, function(res) {
+							this.m = res.data;
+							if (res.data == null) {
+								sa.alert('未能查找到 id=' + this.id + " 详细数据");
+							}
+						}.bind(this))
+					}
+				}
+			})
+		</script>
+	</body>
+</html>

+ 73 - 0
sa-view/tb-person-black/tb-person-black-info.html

@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>人员黑名单-详情</title>
+		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css js 资源 -->
+		<link rel="stylesheet" href="../../static/kj/element-ui/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css">
+		<script src="../../static/kj/vue.min.js"></script>
+		<script src="../../static/kj/element-ui/index.js"></script>
+		<script src="../../static/kj/httpVueLoader.js"></script>
+		<script src="../../static/kj/jquery.min.js"></script>
+		<script src="../../static/kj/layer/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+		<style type="text/css">
+			.c-panel .c-label{width: 8em;}
+		</style>
+	</head>
+	<body>
+		<div class="vue-box sbot" style="display: none;" :style="'display: block;'">
+			<!-- ------- 内容部分 ------- -->
+			<div class="s-body">
+				<div class="c-panel">
+					<el-form v-if="m">
+						<el-row>
+							<el-col :span="12">
+								<sa-info name="部门" br>{{m.deptName}}</sa-info>
+								<sa-info name="名字" br>{{m.name}}</sa-info>
+								<sa-info name="身份证" br>{{m.idCard}}</sa-info>
+								<sa-info name="禁行原因" br>{{m.reason}}</sa-info>
+							</el-col>
+							<el-col :span="12">
+								<sa-info name="禁行开始" br>{{m.startTime}}</sa-info>
+								<sa-info name="禁行结束" br>{{m.endTime}}</sa-info>
+								<sa-info name="创建时间" br>{{m.createTime}}</sa-info>
+								<sa-info name="更新时间" br>{{m.updateTime}}</sa-info>
+							</el-col>
+						</el-row>
+					</el-form>
+				</div>
+			</div>
+			<!-- ------- 底部按钮 ------- -->
+			<div class="s-foot">
+				<el-button type="success" @click="sa.closeCurrIframe()">确定</el-button>
+				<el-button @click="sa.closeCurrIframe()">取消</el-button>
+			</div>
+		</div>
+		<script>
+			var app = new Vue({
+				components: {
+					"sa-info": httpVueLoader('../../sa-frame/com/sa-info.vue')
+				},
+				el: '.vue-box',
+				data: {
+					id: sa.p('id', 0),	// 获取数据ID 
+					m: null
+				},
+				methods: {
+				},
+				mounted: function() {
+					sa.ajax('/TbPersonBlack/getById?id=' + this.id, function(res) {
+						this.m = res.data;
+						if(res.data == null) {
+							sa.alert('未能查找到 id=' + this.id + " 详细数据");
+						}
+					}.bind(this))
+				}
+			})
+			
+		</script>
+	</body>
+</html>

+ 135 - 0
sa-view/tb-person-black/tb-person-black-list.html

@@ -0,0 +1,135 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>人员黑名单-列表</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css & js 资源 -->
+		<link rel="stylesheet" href="../../static/kj/element-ui/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css">
+		<script src="../../static/kj/vue.min.js"></script>
+		<script src="../../static/kj/element-ui/index.js"></script>
+		<script src="../../static/kj/httpVueLoader.js"></script>
+		<script src="../../static/kj/jquery.min.js"></script>
+		<script src="../../static/kj/layer/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+	</head>
+	<body>
+		<div class="vue-box" style="display: none;" :style="'display: block;'">
+			<div class="c-panel">
+				<!-- ------------- 检索参数 ------------- -->
+				<div class="c-title">检索参数</div>
+				<el-form ref="form" :model='p' @submit.native.prevent>
+					<sa-item type="text" name="名字" v-model="p.name"></sa-item>
+					<el-button type="primary" icon="el-icon-search" @click="p.pageNo = 1; f5()">查询</el-button>
+					<el-button type="primary" icon="el-icon-plus" @click="add" v-if="sa.isAuth('tb-person-black-add')">新增</el-button>
+					<el-button type="info"  icon="el-icon-refresh"  @click="f5">重置</el-button>
+				</el-form>
+				<!-- ------------- 数据列表 ------------- -->
+				<el-table class="data-table" ref="data-table" :data="dataList" >
+					<sa-td name="序号" type="index"></sa-td>
+					<sa-td name="部门" prop="deptName" ></sa-td>
+					<sa-td name="名字" prop="name" ></sa-td>
+					<sa-td name="身份证" prop="idCadStr" ></sa-td>
+					<sa-td name="禁行原因" prop="reason" ></sa-td>
+					<sa-td name="禁行开始" prop="startTime" ></sa-td>
+					<sa-td name="禁行结束" prop="endTime" ></sa-td>
+					<sa-td name="创建时间" prop="createTime" ></sa-td>
+					<sa-td name="更新时间" prop="updateTime" ></sa-td>
+					<el-table-column label="操作" fixed="right"  width="240px">
+						<template slot-scope="s">
+							<el-button class="c-btn" type="success" icon="el-icon-view" @click="get(s.row)">查看</el-button>
+							<el-button v-if="sa.isAuth('tb-person-black-edit')" class="c-btn" type="primary" icon="el-icon-edit" @click="update(s.row)">修改</el-button>
+							<el-button v-if="sa.isAuth('tb-person-black-del')" class="c-btn" type="danger" icon="el-icon-delete" @click="del(s.row)">删除</el-button>
+						</template>
+					</el-table-column>
+				</el-table>
+				<!-- ------------- 分页 ------------- -->
+				<sa-item type="page" :curr.sync="p.pageNo" :size.sync="p.pageSize" :total="dataCount" @change="f5()"></sa-item>
+			</div>
+		</div>
+		<script>
+			var app = new Vue({
+				components: {
+					"sa-item": httpVueLoader('../../sa-frame/com/sa-item.vue'),  
+					"sa-td": httpVueLoader('../../sa-frame/com/sa-td.vue'),		
+				},
+				el: '.vue-box',
+				data: {
+					p: { // 查询参数  
+						id: '',		// 主键 
+						name: '',		// 名字 
+						idCard: '',		// 身份证 
+						reason: '',		// 禁行原因 
+						pageNo: 1,		// 当前页 
+						pageSize: 10,	// 页大小 
+						sortType: 0		// 排序方式 
+					},
+					dataCount: 0,
+					dataList: [], // 数据集合 
+				},
+				methods: {
+					// 刷新
+					f5: function() {
+						sa.ajax('/TbPersonBlack/getList', sa.removeNull(this.p), function(res) {
+							this.dataList = res.data; // 数据
+							this.dataCount = res.dataCount; // 数据总数 
+							sa.f5TableHeight();		// 刷新表格高度 
+						}.bind(this));
+					},
+					// 查看
+					get: function(data) {
+						sa.showIframe('数据详情', 'tb-person-black-info.html?id=' + data.id, '700px', '70%');
+					},
+					// 查看 - 根据选中的
+					getBySelect: function(data) {
+						var selection = this.$refs['data-table'].selection;
+						if(selection.length == 0) {
+							return sa.msg('请选择一条数据')
+						}
+						this.get(selection[0]);
+					},
+					// 修改
+					update: function(data) {
+						sa.showIframe('修改数据', 'tb-person-black-add.html?id=' + data.id, '700px', '70%');
+					},
+					// 新增
+					add: function(data) {
+						sa.showIframe('新增数据', 'tb-person-black-add.html?id=-1', '700px', '70%');
+					},
+					// 删除
+					del: function(data) {
+						sa.confirm('是否删除,此操作不可撤销', function() {
+							sa.ajax('/TbPersonBlack/delete?id=' + data.id, function(res) {
+								sa.arrayDelete(this.dataList, data);
+								sa.ok('删除成功');
+								sa.f5TableHeight();		// 刷新表格高度 
+							}.bind(this))
+						}.bind(this));
+					},
+					// 批量删除
+					deleteByIds: function() {
+						// 获取选中元素的id列表 
+						let selection = this.$refs['data-table'].selection;
+						let ids = sa.getArrayField(selection, 'id');
+						if(selection.length == 0) {
+							return sa.msg('请至少选择一条数据')
+						}
+						// 提交删除 
+						sa.confirm('是否批量删除选中数据?此操作不可撤销', function() {
+							sa.ajax('/TbPersonBlack/deleteByIds', {ids: ids.join(',')}, function(res) {
+								sa.arrayDelete(this.dataList, selection);
+								sa.ok('删除成功');
+								sa.f5TableHeight();		// 刷新表格高度 
+							}.bind(this))
+						}.bind(this));
+					},
+				},
+				created: function() {
+					this.f5();
+					sa.onInputEnter();
+				}
+			})
+		</script>
+	</body>
+</html>

+ 155 - 0
sa-view/tb-person-filing/tb-person-filing-add.html

@@ -0,0 +1,155 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>人员备案-添加/修改</title>
+		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+		<meta name="viewport"
+			content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css js 资源 -->
+		<link rel="stylesheet" href="../../static/kj/element-ui/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css">
+		<script src="../../static/kj/vue.min.js"></script>
+		<script src="../../static/kj/element-ui/index.js"></script>
+		<script src="../../static/kj/httpVueLoader.js"></script>
+		<script src="../../static/kj/jquery.min.js"></script>
+		<script src="../../static/kj/layer/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+		<style type="text/css">
+			.c-panel .el-form .c-label {
+				width: 7.5em !important;
+			}
+
+			.c-panel .el-form .el-input,
+			.c-panel .el-form .el-textarea__inner {
+				width: 250px;
+			}
+		</style>
+	</head>
+	<body>
+		<div class="vue-box" :class="{sbot: id}" style="display: none;" :style="'display: block;'">
+			<!-- ------- 内容部分 ------- -->
+			<div class="s-body">
+				<div class="c-panel">
+					<div class="c-title" v-if="id == 0">数据添加</div>
+					<div class="c-title" v-else>数据修改</div>
+					<el-form v-if="m">
+						<div class="c-item">
+							<label class="c-label"><span style="color: red;">*</span>部门:</label>
+							<el-select v-model="m.deptId" :disabled="currentUser.deptId!==9999999">
+								<el-option label="请选择" v-for="(item,index) in deptList" :key="item.id"
+									:label="item.name" :value="item.id"></el-option>
+
+							</el-select>
+						</div>
+						<sa-item type="text" name="姓名" v-model="m.name" br need></sa-item>
+						<sa-item type="num" name="联系电话" v-model="m.phone" br need></sa-item>
+						<sa-item type="enum" name="类型" v-model="m.type" :jv="{1: '临时', 2: '内部'}" jtype="3" br></sa-item>
+						<sa-item type="text" name="身份证" v-model="m.idCard" br need></sa-item>
+						<sa-item type="text" name="来访事由" v-model="m.visitReason" br need></sa-item>
+						<sa-item type="datetime" name="预计来访时间" v-model="m.preComeTime" br :need="m.type==1"></sa-item>
+						<sa-item type="datetime" name="预计结束时间" v-model="m.preLeaveTime" br :need="m.type==1"></sa-item>
+						<sa-item type="textarea" name="备注" v-model="m.remark" br></sa-item>
+						</sa-item>
+					</el-form>
+				</div>
+			</div>
+			<!-- ------- 底部按钮 ------- -->
+			<div class="s-foot">
+				<el-button type="primary" @click="ok()">确定</el-button>
+				<el-button @click="sa.closeCurrIframe()">取消</el-button>
+			</div>
+		</div>
+		<script>
+			var app = new Vue({
+				components: {
+					"sa-item": httpVueLoader('../../sa-frame/com/sa-item.vue')
+				},
+				el: '.vue-box',
+				data: {
+					currentUser: sa.$sys.getCurrUser(),
+					id: sa.p('id', 0), // 获取超链接中的id参数(0=添加,非0=修改) 
+					m: {
+						id: '', //  
+						deptId: '', // 部门ID 
+						name: '', // 姓名 
+						phone: '', // 联系电话 
+						type: 1, // 类型(1=临时,2=内部) 
+						idCard: '', // 身份证 
+						remark: '', // 备注 
+					}, // 实体对象 
+					deptList: []
+				},
+				methods: {
+					getDeptList() {
+						sa.ajax('/TbDept/getList', {
+							pageNo: 1,
+							pageSize: 100
+						}, function(resp) {
+							let list = resp.data;
+							let deptId = this.currentUser.deptId;
+							if (deptId != 9999999) {
+								this.m.deptId = deptId;
+							}
+							this.deptList = list;
+						}.bind(this))
+					},
+					// 提交数据 
+					ok: function() {
+						// 表单校验 
+						let m = this.m;
+						let phone=m.phone;
+						sa.checkNull(m.deptId, '请选择部门');
+						sa.checkNull(m.name, '请输入 [姓名]');
+						if (!sa.isPhone(phone)) {
+							sa.error('请输入正确的联系号码');
+							return false;
+						}
+						sa.checkNull(m.idCard, '请输入 [身份证]');
+						let idCard = m.idCard;
+						if (idCard.length < 15) {
+							sa.error('请输入正确的身份证');
+							return false;
+						}
+						sa.checkNull(m.visitReason, '请输入 [来访事由]');
+						let type = m.type;
+						if (type == 1) {
+							sa.checkNull(m.preComeTime, '请输入 [预计来访时间]');
+							sa.checkNull(m.preLeaveTime, '请输入 [预计结束时间]');
+						}
+						m=sa.removeNull(m);
+						// 开始增加或修改
+						if (this.id <= 0) { // 添加
+							sa.ajax('/TbPersonFiling/add', m, function(res) {
+								sa.alert('增加成功', this.clean);
+							}.bind(this));
+						} else { // 修改
+							sa.ajax('/TbPersonFiling/update', m, function(res) {
+								sa.alert('修改成功', this.clean);
+							}.bind(this));
+						}
+					},
+					// 添加/修改 完成后的动作
+					clean: function() {
+						parent.app.f5(); // 刷新父页面列表
+						sa.closeCurrIframe(); // 关闭本页 
+					},
+					init() {
+						// 初始化数据 
+						if (this.id > 0) {
+							sa.ajax('/TbPersonFiling/getById?id=' + this.id, function(res) {
+								this.m = res.data;
+								if (res.data == null) {
+									sa.alert('未能查找到 id=' + this.id + " 详细数据");
+								}
+							}.bind(this))
+						}
+					}
+				},
+				mounted: function() {
+					this.init();
+					this.getDeptList();
+				}
+			})
+		</script>
+	</body>
+</html>

+ 77 - 0
sa-view/tb-person-filing/tb-person-filing-info.html

@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>人员备案-详情</title>
+		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+		<meta name="viewport"
+			content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css js 资源 -->
+		<link rel="stylesheet" href="../../static/kj/element-ui/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css">
+		<script src="../../static/kj/vue.min.js"></script>
+		<script src="../../static/kj/element-ui/index.js"></script>
+		<script src="../../static/kj/httpVueLoader.js"></script>
+		<script src="../../static/kj/jquery.min.js"></script>
+		<script src="../../static/kj/layer/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+		<style type="text/css">
+			.c-panel .c-label {
+				width: 8em;
+			}
+		</style>
+	</head>
+	<body>
+		<div class="vue-box sbot" style="display: none;" :style="'display: block;'">
+			<!-- ------- 内容部分 ------- -->
+			<div class="s-body">
+				<div class="c-panel">
+					<el-form v-if="m">
+						<el-row>
+							<el-col :span="12">
+								<sa-info name="姓名" br>{{m.name}}</sa-info>
+								<sa-info name="部门" br>{{m.deptName}}</sa-info>
+								<sa-info type="enum" name="类型" :value="m.type" :jv="{1: '临时', 2: '内部'}" br></sa-info>
+								<sa-info name="身份证" br>{{m.idCadStr}}</sa-info>
+								<sa-info name="来访事由" br>{{m.visitReason}}</sa-info>
+							</el-col>
+							<el-col :span="12">
+								<sa-info name="预计来访时间" br>{{m.preComeTime}}</sa-info>
+								<sa-info name="预计结束时间" br>{{m.preLeaveTime}}</sa-info>
+								<sa-info name="备注" br>{{m.remark}}</sa-info>
+								<sa-info name="创建时间" br>{{m.createTime}}</sa-info>
+								<sa-info name="更新时间" br>{{m.updateTime}}</sa-info>
+							</el-col>
+						</el-row>
+
+					</el-form>
+				</div>
+			</div>
+			<!-- ------- 底部按钮 ------- -->
+			<div class="s-foot">
+				<el-button type="success" @click="sa.closeCurrIframe()">确定</el-button>
+				<el-button @click="sa.closeCurrIframe()">取消</el-button>
+			</div>
+		</div>
+		<script>
+			var app = new Vue({
+				components: {
+					"sa-info": httpVueLoader('../../sa-frame/com/sa-info.vue')
+				},
+				el: '.vue-box',
+				data: {
+					id: sa.p('id', 0), // 获取数据ID 
+					m: null
+				},
+				methods: {},
+				mounted: function() {
+					sa.ajax('/TbPersonFiling/getById?id=' + this.id, function(res) {
+						this.m = res.data;
+						if (res.data == null) {
+							sa.alert('未能查找到 id=' + this.id + " 详细数据");
+						}
+					}.bind(this))
+				}
+			})
+		</script>
+	</body>
+</html>

+ 159 - 0
sa-view/tb-person-filing/tb-person-filing-list.html

@@ -0,0 +1,159 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>人员备案-列表</title>
+		<meta charset="utf-8">
+		<meta name="viewport"
+			content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css & js 资源 -->
+		<link rel="stylesheet" href="../../static/kj/element-ui/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css">
+		<script src="../../static/kj/vue.min.js"></script>
+		<script src="../../static/kj/element-ui/index.js"></script>
+		<script src="../../static/kj/httpVueLoader.js"></script>
+		<script src="../../static/kj/jquery.min.js"></script>
+		<script src="../../static/kj/layer/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+	</head>
+	<body>
+		<div class="vue-box" style="display: none;" :style="'display: block;'">
+			<div class="c-panel">
+				<!-- ------------- 检索参数 ------------- -->
+				<div class="c-title">检索参数</div>
+				<el-form ref="form" :model='p' @submit.native.prevent>
+					<sa-item type="text" name="姓名" v-model="p.name"></sa-item>
+					<sa-item type="enum" name="类型" v-model="p.type" :jv="{1: '临时', 2: '内部'}" jtype="2" def="不限">
+					</sa-item>
+					<sa-item type="text" name="身份证" v-model="p.idCard"></sa-item>
+					<el-button type="primary" icon="el-icon-search" @click="p.pageNo = 1; f5()">查询</el-button>
+					<el-button type="primary" icon="el-icon-plus" @click="add" v-if="sa.isAuth('tb-person-filing-add')">新增</el-button>
+					<el-button type="info"  icon="el-icon-refresh"  @click="f5">重置</el-button>
+
+				</el-form>
+				
+				<!-- ------------- 数据列表 ------------- -->
+				<el-table class="data-table" ref="data-table" :data="dataList">
+					<sa-td name="序号" type="index"></sa-td>
+					<sa-td name="姓名" prop="name"></sa-td>
+					<sa-td name="联系号码" prop="phone"></sa-td>
+					<sa-td name="部门" prop="deptName"></sa-td>
+					<sa-td name="类型" prop="type" type="enum" :jv="{1: '临时', 2: '内部'}"></sa-td>
+					<sa-td name="身份证" prop="idCadStr" width="150"></sa-td>
+					<sa-td name="来访事由" prop="visitReason"></sa-td>
+					<sa-td name="预计来访时间" prop="preComeTime"></sa-td>
+					<sa-td name="预计结束时间" prop="preLeaveTime"></sa-td>
+					<sa-td name="创建时间" prop="createTime"></sa-td>
+					<sa-td name="更新时间" prop="updateTime"></sa-td>
+					<el-table-column label="操作" fixed="right" width="240px">
+						<template slot-scope="s">
+							<el-button class="c-btn" type="success" icon="el-icon-view" @click="get(s.row)">查看
+							</el-button>
+							<el-button v-if="sa.isAuth('tb-person-filing-edit')" class="c-btn" type="primary" icon="el-icon-edit" @click="update(s.row)">修改
+							</el-button>
+							<el-button v-if="sa.isAuth('tb-person-filing-del')" class="c-btn" type="danger" icon="el-icon-delete" @click="del(s.row)">删除
+							</el-button>
+						</template>
+					</el-table-column>
+				</el-table>
+				<!-- ------------- 分页 ------------- -->
+				<sa-item type="page" :curr.sync="p.pageNo" :size.sync="p.pageSize" :total="dataCount" @change="f5()">
+				</sa-item>
+			</div>
+		</div>
+		<script>
+			var app = new Vue({
+				components: {
+					"sa-item": httpVueLoader('../../sa-frame/com/sa-item.vue'),
+					"sa-td": httpVueLoader('../../sa-frame/com/sa-td.vue'),
+				},
+				el: '.vue-box',
+				data: {
+					p: { // 查询参数  
+						id: '', //  
+						deptId: '', // 部门ID 
+						name: '', // 姓名 
+						type: '', // 类型(1=临时,2=内部) 
+						idCard: '', // 身份证 
+						visitReason: '', // 来访事由 
+						preComeTime: '', // 预计来访时间 
+						preLeaveTime: '', // 预计结束时间 
+						visit: '', // 状态 
+						remark: '', // 备注 
+						createTime: '', // 创建时间 
+						createBy: '', // 创建人 
+						updateTime: '', // 更新时间 
+						updateBy: '', // 更新人 
+						pageNo: 1, // 当前页 
+						pageSize: 10, // 页大小 
+						sortType: 0 // 排序方式 
+					},
+					dataCount: 0,
+					dataList: [], // 数据集合 
+				},
+				methods: {
+					// 刷新
+					f5: function() {
+						sa.ajax('/TbPersonFiling/getList', sa.removeNull(this.p), function(res) {
+							this.dataList = res.data; // 数据
+							this.dataCount = res.dataCount; // 数据总数 
+							sa.f5TableHeight(); // 刷新表格高度 
+						}.bind(this));
+					},
+					// 查看
+					get: function(data) {
+						sa.showIframe('数据详情', 'tb-person-filing-info.html?id=' + data.id, '700px', '70%');
+					},
+					// 查看 - 根据选中的
+					getBySelect: function(data) {
+						var selection = this.$refs['data-table'].selection;
+						if (selection.length == 0) {
+							return sa.msg('请选择一条数据')
+						}
+						this.get(selection[0]);
+					},
+					// 修改
+					update: function(data) {
+						sa.showIframe('修改数据', 'tb-person-filing-add.html?id=' + data.id, '700px', '80%');
+					},
+					// 新增
+					add: function(data) {
+						sa.showIframe('新增数据', 'tb-person-filing-add.html?id=-1', '700px', '80%');
+					},
+					// 删除
+					del: function(data) {
+						sa.confirm('是否删除,此操作不可撤销', function() {
+							sa.ajax('/TbPersonFiling/delete?id=' + data.id, function(res) {
+								sa.arrayDelete(this.dataList, data);
+								sa.ok('删除成功');
+								sa.f5TableHeight(); // 刷新表格高度 
+							}.bind(this))
+						}.bind(this));
+					},
+					// 批量删除
+					deleteByIds: function() {
+						// 获取选中元素的id列表 
+						let selection = this.$refs['data-table'].selection;
+						let ids = sa.getArrayField(selection, 'id');
+						if (selection.length == 0) {
+							return sa.msg('请至少选择一条数据')
+						}
+						// 提交删除 
+						sa.confirm('是否批量删除选中数据?此操作不可撤销', function() {
+							sa.ajax('/TbPersonFiling/deleteByIds', {
+								ids: ids.join(',')
+							}, function(res) {
+								sa.arrayDelete(this.dataList, selection);
+								sa.ok('删除成功');
+								sa.f5TableHeight(); // 刷新表格高度 
+							}.bind(this))
+						}.bind(this));
+					},
+				},
+				created: function() {
+					this.f5();
+					sa.onInputEnter();
+				}
+			})
+		</script>
+	</body>
+</html>

+ 149 - 0
sa-view/tb-terminal/tb-terminal-add.html

@@ -0,0 +1,149 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>设备管理-添加/修改</title>
+		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css js 资源 -->
+		<link rel="stylesheet" href="../../static/kj/element-ui/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css">
+		<script src="../../static/kj/vue.min.js"></script>
+		<script src="../../static/kj/element-ui/index.js"></script>
+		<script src="../../static/kj/httpVueLoader.js"></script>
+		<script src="../../static/kj/jquery.min.js"></script>
+		<script src="../../static/kj/layer/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+		<style type="text/css">
+			.c-panel .el-form .c-label{width: 7em !important;}
+			.c-panel .el-form .el-input, .c-panel .el-form .el-textarea__inner{width: 250px;}
+		</style>
+	</head>
+	<body>
+		<div class="vue-box" :class="{sbot: id}" style="display: none;" :style="'display: block;'">
+			<!-- ------- 内容部分 ------- -->
+			<div class="s-body">
+				<div class="c-panel">
+                    <div class="c-title" v-if="id == 0">数据添加</div>
+					<div class="c-title" v-else>数据修改</div>
+					<el-form v-if="m">
+						<sa-item type="text" name="" v-model="m.id" br></sa-item>
+						<sa-item type="text" name="部门ID" v-model="m.deptId" br></sa-item>
+						<sa-item type="text" name="名称" v-model="m.name" br></sa-item>
+						<sa-item type="text" name="IP地址" v-model="m.ipAddress" br></sa-item>
+						<sa-item type="enum" name="状态" v-model="m.state" :jv="{0: '离线', 1: '正常 no-add no-s'}" jtype="3" br></sa-item>
+						<sa-item type="text" name="序列号" v-model="m.sn" br></sa-item>
+						<sa-item type="text" name="场所ID" v-model="m.venuesId" br></sa-item>
+						<sa-item type="text" name="场所名称" v-model="m.venuesName" br></sa-item>
+						<sa-item type="num" name="通道ID" v-model="m.channelId" br></sa-item>
+						<sa-item type="text" name="通道名称" v-model="m.channelName" br></sa-item>
+						<sa-item type="text" name="最近一次上线" v-model="m.lastOnLine" br></sa-item>
+						<sa-item type="text" name="备注" v-model="m.remark" br></sa-item>
+						<sa-item type="text" name="创建时间" v-model="m.createTime" br></sa-item>
+						<sa-item type="text" name="创建人" v-model="m.createBy" br></sa-item>
+						<sa-item type="text" name="更新时间" v-model="m.updateTime" br></sa-item>
+						<sa-item type="text" name="更新人" v-model="m.updateBy" br></sa-item>
+						<sa-item name="" class="s-ok" br>
+							<el-button type="primary" icon="el-icon-plus" @click="ok()">保存</el-button>
+						</sa-item>
+					</el-form>
+				</div>
+			</div>
+			<!-- ------- 底部按钮 ------- -->
+			<div class="s-foot">
+				<el-button type="primary" @click="ok()">确定</el-button>
+				<el-button @click="sa.closeCurrIframe()">取消</el-button>
+			</div>
+		</div>
+        <script>
+			
+			var app = new Vue({
+				components: {
+					"sa-item": httpVueLoader('../../sa-frame/com/sa-item.vue')
+				},
+				el: '.vue-box',
+				data: {
+					id: sa.p('id', 0),		// 获取超链接中的id参数(0=添加,非0=修改) 
+					m: null,		// 实体对象 
+				},
+				methods: {
+					// 创建一个 默认Model 
+					createModel: function() {
+						return {
+							id: '',		//  
+							deptId: '',		// 部门ID 
+							name: '',		// 名称 
+							ipAddress: '',		// IP地址 
+							state: '',		// 状态(0=离线,1=正常 no-add no-s) 
+							sn: '',		// 序列号 
+							venuesId: '',		// 场所ID 
+							venuesName: '',		// 场所名称 
+							channelId: '',		// 通道ID 
+							channelName: '',		// 通道名称 
+							lastOnLine: '',		// 最近一次上线 
+							remark: '',		// 备注 
+							createTime: '',		// 创建时间 
+							createBy: '',		// 创建人 
+							updateTime: '',		// 更新时间 
+							updateBy: '',		// 更新人 
+						}
+					},
+					// 提交数据 
+					ok: function(){
+						// 表单校验 
+						let m = this.m;
+						sa.checkNull(m.id, '请输入 []');
+						sa.checkNull(m.deptId, '请输入 [部门ID]');
+						sa.checkNull(m.name, '请输入 [名称]');
+						sa.checkNull(m.ipAddress, '请输入 [IP地址]');
+						sa.checkNull(m.state, '请输入 [状态]');
+						sa.checkNull(m.sn, '请输入 [序列号]');
+						sa.checkNull(m.venuesId, '请输入 [场所ID]');
+						sa.checkNull(m.venuesName, '请输入 [场所名称]');
+						sa.checkNull(m.channelId, '请输入 [通道ID]');
+						sa.checkNull(m.channelName, '请输入 [通道名称]');
+						sa.checkNull(m.lastOnLine, '请输入 [最近一次上线]');
+						sa.checkNull(m.remark, '请输入 [备注]');
+						sa.checkNull(m.createTime, '请输入 [创建时间]');
+						sa.checkNull(m.createBy, '请输入 [创建人]');
+						sa.checkNull(m.updateTime, '请输入 [更新时间]');
+						sa.checkNull(m.updateBy, '请输入 [更新人]');
+				
+						// 开始增加或修改
+						if(this.id <= 0) {	// 添加
+							sa.ajax('/TbTerminal/add', m, function(res){
+								sa.alert('增加成功', this.clean); 
+							}.bind(this));
+						} else {	// 修改
+							sa.ajax('/TbTerminal/update', m, function(res){
+								sa.alert('修改成功', this.clean);
+							}.bind(this));
+						}
+					},
+					// 添加/修改 完成后的动作
+					clean: function() {
+						if(this.id == 0) {
+							this.m = this.createModel();
+						} else {
+							parent.app.f5();		// 刷新父页面列表
+							sa.closeCurrIframe();	// 关闭本页 
+						}
+					}
+				},
+				mounted: function(){
+					// 初始化数据 
+					if(this.id <= 0) {	
+						this.m = this.createModel();
+					} else {	
+						sa.ajax('/TbTerminal/getById?id=' + this.id, function(res) {
+							this.m = res.data;
+							if(res.data == null) {
+								sa.alert('未能查找到 id=' + this.id + " 详细数据");
+							}
+						}.bind(this))
+					}
+				}
+			})
+			
+		</script>
+	</body>
+</html>

+ 74 - 0
sa-view/tb-terminal/tb-terminal-info.html

@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>设备管理-详情</title>
+		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css js 资源 -->
+		<link rel="stylesheet" href="../../static/kj/element-ui/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css">
+		<script src="../../static/kj/vue.min.js"></script>
+		<script src="../../static/kj/element-ui/index.js"></script>
+		<script src="../../static/kj/httpVueLoader.js"></script>
+		<script src="../../static/kj/jquery.min.js"></script>
+		<script src="../../static/kj/layer/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+		<style type="text/css">
+			.c-panel .c-label{width: 8em;}
+		</style>
+	</head>
+	<body>
+		<div class="vue-box sbot" style="display: none;" :style="'display: block;'">
+			<!-- ------- 内容部分 ------- -->
+			<div class="s-body">
+				<div class="c-panel">
+					<el-form v-if="m">
+						<sa-info name="" br>{{m.id}}</sa-info>
+						<sa-info name="部门ID" br>{{m.deptId}}</sa-info>
+						<sa-info name="名称" br>{{m.name}}</sa-info>
+						<sa-info name="IP地址" br>{{m.ipAddress}}</sa-info>
+						<sa-info type="enum" name="状态" :value="m.state" :jv="{0: '离线', 1: '正常 no-add no-s'}" br></sa-info>
+						<sa-info name="序列号" br>{{m.sn}}</sa-info>
+						<sa-info name="场所ID" br>{{m.venuesId}}</sa-info>
+						<sa-info name="场所名称" br>{{m.venuesName}}</sa-info>
+						<sa-info name="通道名称" br>{{m.channelName}}</sa-info>
+						<sa-info name="最近一次上线" br>{{m.lastOnLine}}</sa-info>
+						<sa-info name="备注" br>{{m.remark}}</sa-info>
+						<sa-info name="创建时间" br>{{m.createTime}}</sa-info>
+						<sa-info name="创建人" br>{{m.createBy}}</sa-info>
+						<sa-info name="更新时间" br>{{m.updateTime}}</sa-info>
+						<sa-info name="更新人" br>{{m.updateBy}}</sa-info>
+					</el-form>
+				</div>
+			</div>
+			<!-- ------- 底部按钮 ------- -->
+			<div class="s-foot">
+				<el-button type="success" @click="sa.closeCurrIframe()">确定</el-button>
+				<el-button @click="sa.closeCurrIframe()">取消</el-button>
+			</div>
+		</div>
+		<script>
+			var app = new Vue({
+				components: {
+					"sa-info": httpVueLoader('../../sa-frame/com/sa-info.vue')
+				},
+				el: '.vue-box',
+				data: {
+					id: sa.p('id', 0),	// 获取数据ID 
+					m: null
+				},
+				methods: {
+				},
+				mounted: function() {
+					sa.ajax('/TbTerminal/getById?id=' + this.id, function(res) {
+						this.m = res.data;
+						if(res.data == null) {
+							sa.alert('未能查找到 id=' + this.id + " 详细数据");
+						}
+					}.bind(this))
+				}
+			})
+			
+		</script>
+	</body>
+</html>

+ 192 - 0
sa-view/tb-terminal/tb-terminal-list.html

@@ -0,0 +1,192 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>设备管理-列表</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css & js 资源 -->
+		<link rel="stylesheet" href="../../static/kj/element-ui/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css">
+		<script src="../../static/kj/vue.min.js"></script>
+		<script src="../../static/kj/element-ui/index.js"></script>
+		<script src="../../static/kj/httpVueLoader.js"></script>
+		<script src="../../static/kj/jquery.min.js"></script>
+		<script src="../../static/kj/layer/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+	</head>
+	<body>
+		<div class="vue-box" style="display: none;" :style="'display: block;'">
+			<div class="c-panel">
+				<!-- ------------- 检索参数 ------------- -->
+				<div class="c-title">检索参数</div>
+				<el-form ref="form" :model='p' @submit.native.prevent>
+					<sa-item type="text" name="" v-model="p.id"></sa-item>
+					<sa-item type="text" name="部门ID" v-model="p.deptId"></sa-item>
+					<sa-item type="text" name="名称" v-model="p.name"></sa-item>
+					<sa-item type="text" name="IP地址" v-model="p.ipAddress"></sa-item>
+					<sa-item type="enum" name="状态" v-model="p.state" 
+						:jv="{0: '离线', 1: '正常 no-add no-s'}" jtype="2" def="不限"></sa-item>
+					<sa-item type="text" name="序列号" v-model="p.sn"></sa-item>
+					<sa-item type="text" name="场所ID" v-model="p.venuesId"></sa-item>
+					<sa-item type="text" name="场所名称" v-model="p.venuesName"></sa-item>
+					<sa-item type="num" name="通道ID" v-model="p.channelId"></sa-item>
+					<sa-item type="text" name="通道名称" v-model="p.channelName"></sa-item>
+					<sa-item type="text" name="最近一次上线" v-model="p.lastOnLine"></sa-item>
+					<sa-item type="text" name="备注" v-model="p.remark"></sa-item>
+					<sa-item type="text" name="创建时间" v-model="p.createTime"></sa-item>
+					<sa-item type="text" name="创建人" v-model="p.createBy"></sa-item>
+					<sa-item type="text" name="更新时间" v-model="p.updateTime"></sa-item>
+					<sa-item type="text" name="更新人" v-model="p.updateBy"></sa-item>
+					<el-button type="primary" icon="el-icon-search" @click="p.pageNo = 1; f5()">查询</el-button>
+					<br />
+					<sa-item name="综合排序">
+						<el-radio-group v-model="p.sortType" class="s-radio-text">
+							<el-radio :label="0">默认</el-radio>
+							<el-radio :label="1"></el-radio>
+							<el-radio :label="2">部门ID</el-radio>
+							<el-radio :label="3">名称</el-radio>
+							<el-radio :label="4">IP地址</el-radio>
+							<el-radio :label="5">状态</el-radio>
+							<el-radio :label="6">序列号</el-radio>
+							<el-radio :label="7">场所ID</el-radio>
+							<el-radio :label="8">场所名称</el-radio>
+							<el-radio :label="9">通道ID</el-radio>
+							<el-radio :label="10">通道名称</el-radio>
+							<el-radio :label="11">最近一次上线</el-radio>
+							<el-radio :label="12">备注</el-radio>
+							<el-radio :label="13">创建时间</el-radio>
+							<el-radio :label="14">创建人</el-radio>
+							<el-radio :label="15">更新时间</el-radio>
+							<el-radio :label="16">更新人</el-radio>
+						</el-radio-group>
+					</sa-item>
+				</el-form>
+				<!-- ------------- 快捷按钮 ------------- -->
+				<sa-item type="fast-btn" show="add,get,delete,export,reset"></sa-item>
+				<!-- ------------- 数据列表 ------------- -->
+				<el-table class="data-table" ref="data-table" :data="dataList" >
+					<sa-td type="selection"></sa-td>
+					<sa-td name="" prop="id" ></sa-td>
+					<sa-td name="部门ID" prop="deptId" ></sa-td>
+					<sa-td name="名称" prop="name" ></sa-td>
+					<sa-td name="IP地址" prop="ipAddress" ></sa-td>
+					<sa-td name="状态" prop="state" type="enum" :jv="{0: '离线', 1: '正常 no-add no-s'}"></sa-td>
+					<sa-td name="序列号" prop="sn" ></sa-td>
+					<sa-td name="场所ID" prop="venuesId" ></sa-td>
+					<sa-td name="场所名称" prop="venuesName" ></sa-td>
+					<sa-td name="通道名称" prop="channelName" ></sa-td>
+					<sa-td name="最近一次上线" prop="lastOnLine" ></sa-td>
+					<sa-td name="备注" prop="remark" ></sa-td>
+					<sa-td name="创建时间" prop="createTime" ></sa-td>
+					<sa-td name="创建人" prop="createBy" ></sa-td>
+					<sa-td name="更新时间" prop="updateTime" ></sa-td>
+					<sa-td name="更新人" prop="updateBy" ></sa-td>
+					<el-table-column label="操作" fixed="right"  width="240px">
+						<template slot-scope="s">
+							<el-button class="c-btn" type="success" icon="el-icon-view" @click="get(s.row)">查看</el-button>
+							<el-button class="c-btn" type="primary" icon="el-icon-edit" @click="update(s.row)">修改</el-button>
+							<el-button class="c-btn" type="danger" icon="el-icon-delete" @click="del(s.row)">删除</el-button>
+						</template>
+					</el-table-column>
+				</el-table>
+				<!-- ------------- 分页 ------------- -->
+				<sa-item type="page" :curr.sync="p.pageNo" :size.sync="p.pageSize" :total="dataCount" @change="f5()"></sa-item>
+			</div>
+		</div>
+		<script>
+			var app = new Vue({
+				components: {
+					"sa-item": httpVueLoader('../../sa-frame/com/sa-item.vue'),  
+					"sa-td": httpVueLoader('../../sa-frame/com/sa-td.vue'),		
+				},
+				el: '.vue-box',
+				data: {
+					p: { // 查询参数  
+						id: '',		//  
+						deptId: '',		// 部门ID 
+						name: '',		// 名称 
+						ipAddress: '',		// IP地址 
+						state: '',		// 状态(0=离线,1=正常 no-add no-s) 
+						sn: '',		// 序列号 
+						venuesId: '',		// 场所ID 
+						venuesName: '',		// 场所名称 
+						channelId: '',		// 通道ID 
+						channelName: '',		// 通道名称 
+						lastOnLine: '',		// 最近一次上线 
+						remark: '',		// 备注 
+						createTime: '',		// 创建时间 
+						createBy: '',		// 创建人 
+						updateTime: '',		// 更新时间 
+						updateBy: '',		// 更新人 
+						pageNo: 1,		// 当前页 
+						pageSize: 10,	// 页大小 
+						sortType: 0		// 排序方式 
+					},
+					dataCount: 0,
+					dataList: [], // 数据集合 
+				},
+				methods: {
+					// 刷新
+					f5: function() {
+						sa.ajax('/TbTerminal/getList', sa.removeNull(this.p), function(res) {
+							this.dataList = res.data; // 数据
+							this.dataCount = res.dataCount; // 数据总数 
+							sa.f5TableHeight();		// 刷新表格高度 
+						}.bind(this));
+					},
+					// 查看
+					get: function(data) {
+						sa.showIframe('数据详情', 'tb-terminal-info.html?id=' + data.id, '1050px', '90%');
+					},
+					// 查看 - 根据选中的
+					getBySelect: function(data) {
+						var selection = this.$refs['data-table'].selection;
+						if(selection.length == 0) {
+							return sa.msg('请选择一条数据')
+						}
+						this.get(selection[0]);
+					},
+					// 修改
+					update: function(data) {
+						sa.showIframe('修改数据', 'tb-terminal-add.html?id=' + data.id, '1000px', '90%');
+					},
+					// 新增
+					add: function(data) {
+						sa.showIframe('新增数据', 'tb-terminal-add.html?id=-1', '1000px', '90%');
+					},
+					// 删除
+					del: function(data) {
+						sa.confirm('是否删除,此操作不可撤销', function() {
+							sa.ajax('/TbTerminal/delete?id=' + data.id, function(res) {
+								sa.arrayDelete(this.dataList, data);
+								sa.ok('删除成功');
+								sa.f5TableHeight();		// 刷新表格高度 
+							}.bind(this))
+						}.bind(this));
+					},
+					// 批量删除
+					deleteByIds: function() {
+						// 获取选中元素的id列表 
+						let selection = this.$refs['data-table'].selection;
+						let ids = sa.getArrayField(selection, 'id');
+						if(selection.length == 0) {
+							return sa.msg('请至少选择一条数据')
+						}
+						// 提交删除 
+						sa.confirm('是否批量删除选中数据?此操作不可撤销', function() {
+							sa.ajax('/TbTerminal/deleteByIds', {ids: ids.join(',')}, function(res) {
+								sa.arrayDelete(this.dataList, selection);
+								sa.ok('删除成功');
+								sa.f5TableHeight();		// 刷新表格高度 
+							}.bind(this))
+						}.bind(this));
+					},
+				},
+				created: function() {
+					this.f5();
+					sa.onInputEnter();
+				}
+			})
+		</script>
+	</body>
+</html>

+ 122 - 0
sa-view/tb-venues/tb-venues-add.html

@@ -0,0 +1,122 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>场所管理-添加/修改</title>
+		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css js 资源 -->
+		<link rel="stylesheet" href="../../static/kj/element-ui/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css">
+		<script src="../../static/kj/vue.min.js"></script>
+		<script src="../../static/kj/element-ui/index.js"></script>
+		<script src="../../static/kj/httpVueLoader.js"></script>
+		<script src="../../static/kj/jquery.min.js"></script>
+		<script src="../../static/kj/layer/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+		<style type="text/css">
+			.c-panel .el-form .c-label{width: 7em !important;}
+			.c-panel .el-form .el-input, .c-panel .el-form .el-textarea__inner{width: 250px;}
+		</style>
+	</head>
+	<body>
+		<div class="vue-box" :class="{sbot: id}" style="display: none;" :style="'display: block;'">
+			<!-- ------- 内容部分 ------- -->
+			<div class="s-body">
+				<div class="c-panel">
+                    <div class="c-title" v-if="id == 0">数据添加</div>
+					<div class="c-title" v-else>数据修改</div>
+					<el-form v-if="m">
+						<sa-item type="text" name="" v-model="m.id" br></sa-item>
+						<sa-item type="text" name="部门ID" v-model="m.deptId" br></sa-item>
+						<sa-item type="text" name="名称" v-model="m.name" br></sa-item>
+						<sa-item type="text" name="创建时间" v-model="m.createTime" br></sa-item>
+						<sa-item type="text" name="创建人" v-model="m.createBy" br></sa-item>
+						<sa-item type="text" name="更新时间" v-model="m.updateTime" br></sa-item>
+						<sa-item type="text" name="更新人" v-model="m.updateBy" br></sa-item>
+						<sa-item name="" class="s-ok" br>
+							<el-button type="primary" icon="el-icon-plus" @click="ok()">保存</el-button>
+						</sa-item>
+					</el-form>
+				</div>
+			</div>
+			<!-- ------- 底部按钮 ------- -->
+			<div class="s-foot">
+				<el-button type="primary" @click="ok()">确定</el-button>
+				<el-button @click="sa.closeCurrIframe()">取消</el-button>
+			</div>
+		</div>
+        <script>
+			
+			var app = new Vue({
+				components: {
+					"sa-item": httpVueLoader('../../sa-frame/com/sa-item.vue')
+				},
+				el: '.vue-box',
+				data: {
+					id: sa.p('id', 0),		// 获取超链接中的id参数(0=添加,非0=修改) 
+					m: null,		// 实体对象 
+				},
+				methods: {
+					// 创建一个 默认Model 
+					createModel: function() {
+						return {
+							id: '',		//  
+							deptId: '',		// 部门ID 
+							name: '',		// 名称 
+							createTime: '',		// 创建时间 
+							createBy: '',		// 创建人 
+							updateTime: '',		// 更新时间 
+							updateBy: '',		// 更新人 
+						}
+					},
+					// 提交数据 
+					ok: function(){
+						// 表单校验 
+						let m = this.m;
+						sa.checkNull(m.id, '请输入 []');
+						sa.checkNull(m.deptId, '请输入 [部门ID]');
+						sa.checkNull(m.name, '请输入 [名称]');
+						sa.checkNull(m.createTime, '请输入 [创建时间]');
+						sa.checkNull(m.createBy, '请输入 [创建人]');
+						sa.checkNull(m.updateTime, '请输入 [更新时间]');
+						sa.checkNull(m.updateBy, '请输入 [更新人]');
+				
+						// 开始增加或修改
+						if(this.id <= 0) {	// 添加
+							sa.ajax('/TbVenues/add', m, function(res){
+								sa.alert('增加成功', this.clean); 
+							}.bind(this));
+						} else {	// 修改
+							sa.ajax('/TbVenues/update', m, function(res){
+								sa.alert('修改成功', this.clean);
+							}.bind(this));
+						}
+					},
+					// 添加/修改 完成后的动作
+					clean: function() {
+						if(this.id == 0) {
+							this.m = this.createModel();
+						} else {
+							parent.app.f5();		// 刷新父页面列表
+							sa.closeCurrIframe();	// 关闭本页 
+						}
+					}
+				},
+				mounted: function(){
+					// 初始化数据 
+					if(this.id <= 0) {	
+						this.m = this.createModel();
+					} else {	
+						sa.ajax('/TbVenues/getById?id=' + this.id, function(res) {
+							this.m = res.data;
+							if(res.data == null) {
+								sa.alert('未能查找到 id=' + this.id + " 详细数据");
+							}
+						}.bind(this))
+					}
+				}
+			})
+			
+		</script>
+	</body>
+</html>

+ 66 - 0
sa-view/tb-venues/tb-venues-info.html

@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>场所管理-详情</title>
+		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css js 资源 -->
+		<link rel="stylesheet" href="../../static/kj/element-ui/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css">
+		<script src="../../static/kj/vue.min.js"></script>
+		<script src="../../static/kj/element-ui/index.js"></script>
+		<script src="../../static/kj/httpVueLoader.js"></script>
+		<script src="../../static/kj/jquery.min.js"></script>
+		<script src="../../static/kj/layer/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+		<style type="text/css">
+			.c-panel .c-label{width: 8em;}
+		</style>
+	</head>
+	<body>
+		<div class="vue-box sbot" style="display: none;" :style="'display: block;'">
+			<!-- ------- 内容部分 ------- -->
+			<div class="s-body">
+				<div class="c-panel">
+					<el-form v-if="m">
+						<sa-info name="" br>{{m.id}}</sa-info>
+						<sa-info name="部门ID" br>{{m.deptId}}</sa-info>
+						<sa-info name="名称" br>{{m.name}}</sa-info>
+						<sa-info name="创建时间" br>{{m.createTime}}</sa-info>
+						<sa-info name="创建人" br>{{m.createBy}}</sa-info>
+						<sa-info name="更新时间" br>{{m.updateTime}}</sa-info>
+						<sa-info name="更新人" br>{{m.updateBy}}</sa-info>
+					</el-form>
+				</div>
+			</div>
+			<!-- ------- 底部按钮 ------- -->
+			<div class="s-foot">
+				<el-button type="success" @click="sa.closeCurrIframe()">确定</el-button>
+				<el-button @click="sa.closeCurrIframe()">取消</el-button>
+			</div>
+		</div>
+		<script>
+			var app = new Vue({
+				components: {
+					"sa-info": httpVueLoader('../../sa-frame/com/sa-info.vue')
+				},
+				el: '.vue-box',
+				data: {
+					id: sa.p('id', 0),	// 获取数据ID 
+					m: null
+				},
+				methods: {
+				},
+				mounted: function() {
+					sa.ajax('/TbVenues/getById?id=' + this.id, function(res) {
+						this.m = res.data;
+						if(res.data == null) {
+							sa.alert('未能查找到 id=' + this.id + " 详细数据");
+						}
+					}.bind(this))
+				}
+			})
+			
+		</script>
+	</body>
+</html>

+ 156 - 0
sa-view/tb-venues/tb-venues-list.html

@@ -0,0 +1,156 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<title>场所管理-列表</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<!-- 所有的 css & js 资源 -->
+		<link rel="stylesheet" href="../../static/kj/element-ui/theme-chalk/index.css">
+		<link rel="stylesheet" href="../../static/sa.css">
+		<script src="../../static/kj/vue.min.js"></script>
+		<script src="../../static/kj/element-ui/index.js"></script>
+		<script src="../../static/kj/httpVueLoader.js"></script>
+		<script src="../../static/kj/jquery.min.js"></script>
+		<script src="../../static/kj/layer/layer.js"></script>
+		<script src="../../static/sa.js"></script>
+	</head>
+	<body>
+		<div class="vue-box" style="display: none;" :style="'display: block;'">
+			<div class="c-panel">
+				<!-- ------------- 检索参数 ------------- -->
+				<div class="c-title">检索参数</div>
+				<el-form ref="form" :model='p' @submit.native.prevent>
+					<sa-item type="text" name="" v-model="p.id"></sa-item>
+					<sa-item type="text" name="部门ID" v-model="p.deptId"></sa-item>
+					<sa-item type="text" name="名称" v-model="p.name"></sa-item>
+					<sa-item type="text" name="创建时间" v-model="p.createTime"></sa-item>
+					<sa-item type="text" name="创建人" v-model="p.createBy"></sa-item>
+					<sa-item type="text" name="更新时间" v-model="p.updateTime"></sa-item>
+					<sa-item type="text" name="更新人" v-model="p.updateBy"></sa-item>
+					<el-button type="primary" icon="el-icon-search" @click="p.pageNo = 1; f5()">查询</el-button>
+					<br />
+					<sa-item name="综合排序">
+						<el-radio-group v-model="p.sortType" class="s-radio-text">
+							<el-radio :label="0">默认</el-radio>
+							<el-radio :label="1"></el-radio>
+							<el-radio :label="2">部门ID</el-radio>
+							<el-radio :label="3">名称</el-radio>
+							<el-radio :label="4">创建时间</el-radio>
+							<el-radio :label="5">创建人</el-radio>
+							<el-radio :label="6">更新时间</el-radio>
+							<el-radio :label="7">更新人</el-radio>
+						</el-radio-group>
+					</sa-item>
+				</el-form>
+				<!-- ------------- 快捷按钮 ------------- -->
+				<sa-item type="fast-btn" show="add,get,delete,export,reset"></sa-item>
+				<!-- ------------- 数据列表 ------------- -->
+				<el-table class="data-table" ref="data-table" :data="dataList" >
+					<sa-td type="selection"></sa-td>
+					<sa-td name="" prop="id" ></sa-td>
+					<sa-td name="部门ID" prop="deptId" ></sa-td>
+					<sa-td name="名称" prop="name" ></sa-td>
+					<sa-td name="创建时间" prop="createTime" ></sa-td>
+					<sa-td name="创建人" prop="createBy" ></sa-td>
+					<sa-td name="更新时间" prop="updateTime" ></sa-td>
+					<sa-td name="更新人" prop="updateBy" ></sa-td>
+					<el-table-column label="操作" fixed="right"  width="240px">
+						<template slot-scope="s">
+							<el-button class="c-btn" type="success" icon="el-icon-view" @click="get(s.row)">查看</el-button>
+							<el-button class="c-btn" type="primary" icon="el-icon-edit" @click="update(s.row)">修改</el-button>
+							<el-button class="c-btn" type="danger" icon="el-icon-delete" @click="del(s.row)">删除</el-button>
+						</template>
+					</el-table-column>
+				</el-table>
+				<!-- ------------- 分页 ------------- -->
+				<sa-item type="page" :curr.sync="p.pageNo" :size.sync="p.pageSize" :total="dataCount" @change="f5()"></sa-item>
+			</div>
+		</div>
+		<script>
+			var app = new Vue({
+				components: {
+					"sa-item": httpVueLoader('../../sa-frame/com/sa-item.vue'),  
+					"sa-td": httpVueLoader('../../sa-frame/com/sa-td.vue'),		
+				},
+				el: '.vue-box',
+				data: {
+					p: { // 查询参数  
+						id: '',		//  
+						deptId: '',		// 部门ID 
+						name: '',		// 名称 
+						createTime: '',		// 创建时间 
+						createBy: '',		// 创建人 
+						updateTime: '',		// 更新时间 
+						updateBy: '',		// 更新人 
+						pageNo: 1,		// 当前页 
+						pageSize: 10,	// 页大小 
+						sortType: 0		// 排序方式 
+					},
+					dataCount: 0,
+					dataList: [], // 数据集合 
+				},
+				methods: {
+					// 刷新
+					f5: function() {
+						sa.ajax('/TbVenues/getList', sa.removeNull(this.p), function(res) {
+							this.dataList = res.data; // 数据
+							this.dataCount = res.dataCount; // 数据总数 
+							sa.f5TableHeight();		// 刷新表格高度 
+						}.bind(this));
+					},
+					// 查看
+					get: function(data) {
+						sa.showIframe('数据详情', 'tb-venues-info.html?id=' + data.id, '1050px', '90%');
+					},
+					// 查看 - 根据选中的
+					getBySelect: function(data) {
+						var selection = this.$refs['data-table'].selection;
+						if(selection.length == 0) {
+							return sa.msg('请选择一条数据')
+						}
+						this.get(selection[0]);
+					},
+					// 修改
+					update: function(data) {
+						sa.showIframe('修改数据', 'tb-venues-add.html?id=' + data.id, '1000px', '90%');
+					},
+					// 新增
+					add: function(data) {
+						sa.showIframe('新增数据', 'tb-venues-add.html?id=-1', '1000px', '90%');
+					},
+					// 删除
+					del: function(data) {
+						sa.confirm('是否删除,此操作不可撤销', function() {
+							sa.ajax('/TbVenues/delete?id=' + data.id, function(res) {
+								sa.arrayDelete(this.dataList, data);
+								sa.ok('删除成功');
+								sa.f5TableHeight();		// 刷新表格高度 
+							}.bind(this))
+						}.bind(this));
+					},
+					// 批量删除
+					deleteByIds: function() {
+						// 获取选中元素的id列表 
+						let selection = this.$refs['data-table'].selection;
+						let ids = sa.getArrayField(selection, 'id');
+						if(selection.length == 0) {
+							return sa.msg('请至少选择一条数据')
+						}
+						// 提交删除 
+						sa.confirm('是否批量删除选中数据?此操作不可撤销', function() {
+							sa.ajax('/TbVenues/deleteByIds', {ids: ids.join(',')}, function(res) {
+								sa.arrayDelete(this.dataList, selection);
+								sa.ok('删除成功');
+								sa.f5TableHeight();		// 刷新表格高度 
+							}.bind(this))
+						}.bind(this));
+					},
+				},
+				created: function() {
+					this.f5();
+					sa.onInputEnter();
+				}
+			})
+		</script>
+	</body>
+</html>

BIN
static/icon/icon-article.png


BIN
static/icon/icon-comment.png


BIN
static/icon/icon-goods.png


BIN
static/icon/icon-money.png


BIN
static/icon/icon-order.png


BIN
static/icon/icon-user.png


BIN
static/img/kulian.png


BIN
static/img/up-icon.png


Разница между файлами не показана из-за своего большого размера
+ 1 - 0
static/kj/Sortable.min.js


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
static/kj/element-ui/index.js


BIN
static/kj/element-ui/theme-chalk/fonts/element-icons.ttf


BIN
static/kj/element-ui/theme-chalk/fonts/element-icons.woff


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
static/kj/element-ui/theme-chalk/index.css


+ 478 - 0
static/kj/httpVueLoader.js

@@ -0,0 +1,478 @@
+(function umd(root,factory){
+    if(typeof module==='object' && typeof exports === 'object' )
+        module.exports=factory()
+    else if(typeof define==='function' && define.amd)
+        define([],factory)
+    else
+        root.httpVueLoader=factory()
+})(this,function factory() {
+    'use strict';
+
+    var scopeIndex = 0;
+
+    StyleContext.prototype = {
+
+        withBase: function(callback) {
+
+            var tmpBaseElt;
+            if ( this.component.baseURI ) {
+
+                // firefox and chrome need the <base> to be set while inserting or modifying <style> in a document.
+                tmpBaseElt = document.createElement('base');
+                tmpBaseElt.href = this.component.baseURI;
+
+                var headElt = this.component.getHead();
+                headElt.insertBefore(tmpBaseElt, headElt.firstChild);
+            }
+
+            callback.call(this);
+
+            if ( tmpBaseElt )
+                this.component.getHead().removeChild(tmpBaseElt);
+        },
+
+        scopeStyles: function(styleElt, scopeName) {
+
+            function process() {
+
+                var sheet = styleElt.sheet;
+                var rules = sheet.cssRules;
+
+                for ( var i = 0; i < rules.length; ++i ) {
+
+                    var rule = rules[i];
+                    if ( rule.type !== 1 )
+                        continue;
+
+                    var scopedSelectors = [];
+
+                    rule.selectorText.split(/\s*,\s*/).forEach(function(sel) {
+
+                        scopedSelectors.push(scopeName+' '+sel);
+                        var segments = sel.match(/([^ :]+)(.+)?/);
+                        scopedSelectors.push(segments[1] + scopeName + (segments[2]||''));
+                    });
+
+                    var scopedRule = scopedSelectors.join(',') + rule.cssText.substr(rule.selectorText.length);
+                    sheet.deleteRule(i);
+                    sheet.insertRule(scopedRule, i);
+                }
+            }
+
+            try {
+                // firefox may fail sheet.cssRules with InvalidAccessError
+                process();
+            } catch (ex) {
+
+                if ( ex instanceof DOMException && ex.code === DOMException.INVALID_ACCESS_ERR ) {
+
+                    styleElt.sheet.disabled = true;
+                    styleElt.addEventListener('load', function onStyleLoaded() {
+
+                        styleElt.removeEventListener('load', onStyleLoaded);
+
+                        // firefox need this timeout otherwise we have to use document.importNode(style, true)
+                        setTimeout(function() {
+
+                            process();
+                            styleElt.sheet.disabled = false;
+                        });
+                    });
+                    return;
+                }
+
+                throw ex;
+            }
+        },
+
+        compile: function() {
+
+            var hasTemplate = this.template !== null;
+
+            var scoped = this.elt.hasAttribute('scoped');
+
+            if ( scoped ) {
+
+                // no template, no scopable style needed
+                if ( !hasTemplate )
+                    return;
+
+                // firefox does not tolerate this attribute
+                this.elt.removeAttribute('scoped');
+            }
+
+            this.withBase(function() {
+
+                this.component.getHead().appendChild(this.elt);
+            });
+
+            if ( scoped )
+                this.scopeStyles(this.elt, '['+this.component.getScopeId()+']');
+
+            return Promise.resolve();
+        },
+
+        getContent: function() {
+
+            return this.elt.textContent;
+        },
+
+        setContent: function(content) {
+
+            this.withBase(function() {
+
+                this.elt.textContent = content;
+            });
+        }
+    };
+
+    function StyleContext(component, elt) {
+
+        this.component = component;
+        this.elt = elt;
+    }
+
+
+    ScriptContext.prototype = {
+
+        getContent: function() {
+
+            return this.elt.textContent;
+        },
+
+        setContent: function(content) {
+
+            this.elt.textContent = content;
+        },
+
+        compile: function(module) {
+
+            var childModuleRequire = function(childURL) {
+
+                return httpVueLoader.require(resolveURL(this.component.baseURI, childURL));
+            }.bind(this);
+
+            var childLoader = function(childURL, childName) {
+
+                return httpVueLoader(resolveURL(this.component.baseURI, childURL), childName);
+            }.bind(this);
+
+            try {
+                Function('exports', 'require', 'httpVueLoader', 'module', this.getContent()).call(this.module.exports, this.module.exports, childModuleRequire, childLoader, this.module);
+            } catch(ex) {
+
+                if ( !('lineNumber' in ex) ) {
+
+                    return Promise.reject(ex);
+                }
+                var vueFileData = responseText.replace(/\r?\n/g, '\n');
+                var lineNumber = vueFileData.substr(0, vueFileData.indexOf(script)).split('\n').length + ex.lineNumber - 1;
+                throw new (ex.constructor)(ex.message, url, lineNumber);
+            }
+
+            return Promise.resolve(this.module.exports)
+                .then(httpVueLoader.scriptExportsHandler.bind(this))
+                .then(function(exports) {
+
+                    this.module.exports = exports;
+                }.bind(this));
+        }
+    };
+
+    function ScriptContext(component, elt) {
+
+        this.component = component;
+        this.elt = elt;
+        this.module = { exports:{} };
+    }
+
+
+    TemplateContext.prototype = {
+
+        getContent: function() {
+
+            return this.elt.innerHTML;
+        },
+
+        setContent: function(content) {
+
+            this.elt.innerHTML = content;
+        },
+
+        getRootElt: function() {
+
+            var tplElt = this.elt.content || this.elt;
+
+            if ( 'firstElementChild' in tplElt )
+                return tplElt.firstElementChild;
+
+            for ( tplElt = tplElt.firstChild; tplElt !== null; tplElt = tplElt.nextSibling )
+                if ( tplElt.nodeType === Node.ELEMENT_NODE )
+                    return tplElt;
+
+            return null;
+        },
+
+        compile: function() {
+
+            return Promise.resolve();
+        }
+    };
+
+    function TemplateContext(component, elt) {
+
+        this.component = component;
+        this.elt = elt;
+    }
+
+
+
+    Component.prototype = {
+
+        getHead: function() {
+
+            return document.head || document.getElementsByTagName('head')[0];
+        },
+
+        getScopeId: function() {
+
+            if ( this._scopeId === '' ) {
+
+                this._scopeId = 'data-s-' + (scopeIndex++).toString(36);
+                this.template.getRootElt().setAttribute(this._scopeId, '');
+            }
+            return this._scopeId;
+        },
+
+        load: function(componentURL) {
+
+            return httpVueLoader.httpRequest(componentURL)
+                .then(function(responseText) {
+
+                    this.baseURI = componentURL.substr(0, componentURL.lastIndexOf('/')+1);
+                    var doc = document.implementation.createHTMLDocument('');
+
+                    // IE requires the <base> to come with <style>
+                    doc.body.innerHTML = (this.baseURI ? '<base href="'+this.baseURI+'">' : '') + responseText;
+
+                    for ( var it = doc.body.firstChild; it; it = it.nextSibling ) {
+
+                        switch ( it.nodeName ) {
+                            case 'TEMPLATE':
+                                this.template = new TemplateContext(this, it);
+                                break;
+                            case 'SCRIPT':
+                                this.script = new ScriptContext(this, it);
+                                break;
+                            case 'STYLE':
+                                this.styles.push(new StyleContext(this, it));
+                                break;
+                        }
+                    }
+
+                    return this;
+                }.bind(this));
+        },
+
+        _normalizeSection: function(eltCx) {
+
+            var p;
+
+            if ( eltCx === null || !eltCx.elt.hasAttribute('src') ) {
+
+                p = Promise.resolve(null);
+            } else {
+
+                p = httpVueLoader.httpRequest(eltCx.elt.getAttribute('src'))
+                    .then(function(content) {
+
+                        eltCx.elt.removeAttribute('src');
+                        return content;
+                    });
+            }
+
+            return p
+                .then(function(content) {
+
+                    if ( eltCx !== null && eltCx.elt.hasAttribute('lang') ) {
+
+                        var lang = eltCx.elt.getAttribute('lang');
+                        eltCx.elt.removeAttribute('lang');
+                        return httpVueLoader.langProcessor[lang.toLowerCase()].call(this, content === null ? eltCx.getContent() : content);
+                    }
+                    return content;
+                }.bind(this))
+                .then(function(content) {
+
+                    if ( content !== null )
+                        eltCx.setContent(content);
+                });
+        },
+
+        normalize: function() {
+
+            return Promise.all(Array.prototype.concat(
+                this._normalizeSection(this.template),
+                this._normalizeSection(this.script),
+                this.styles.map(this._normalizeSection)
+            ))
+                .then(function() {
+
+                    return this;
+                }.bind(this));
+        },
+
+        compile: function() {
+
+            return Promise.all(Array.prototype.concat(
+                this.template && this.template.compile(),
+                this.script && this.script.compile(),
+                this.styles.map(function(style) { return style.compile(); })
+            ))
+                .then(function() {
+
+                    return this;
+                }.bind(this));
+        }
+    };
+
+    function Component(name) {
+
+        this.name = name;
+        this.template = null;
+        this.script = null;
+        this.styles = [];
+        this._scopeId = '';
+    }
+
+    function identity(value) {
+
+        return value;
+    }
+
+    function parseComponentURL(url) {
+
+        var comp = url.match(/(.*?)([^/]+?)\/?(\.vue)?(\?.*|#.*|$)/);
+        return {
+            name: comp[2],
+            url: comp[1] + comp[2] + (comp[3] === undefined ? '/index.vue' : comp[3]) + comp[4]
+        };
+    }
+
+    function resolveURL(baseURL, url) {
+
+        if (url.substr(0, 2) === './' || url.substr(0, 3) === '../') {
+            return baseURL + url;
+        }
+        return url;
+    }
+
+
+    httpVueLoader.load = function(url, name) {
+
+        return function() {
+
+            return new Component(name).load(url)
+                .then(function(component) {
+
+                    return component.normalize();
+                })
+                .then(function(component) {
+
+                    return component.compile();
+                })
+                .then(function(component) {
+
+                    var exports = component.script !== null ? component.script.module.exports : {};
+
+                    if ( component.template !== null )
+                        exports.template = component.template.getContent();
+
+                    if ( exports.name === undefined )
+                        if ( component.name !== undefined )
+                            exports.name = component.name;
+
+                    exports._baseURI = component.baseURI;
+
+                    return exports;
+                });
+        };
+    };
+
+
+    httpVueLoader.register = function(Vue, url) {
+
+        var comp = parseComponentURL(url);
+        Vue.component(comp.name, httpVueLoader.load(comp.url));
+    };
+
+    httpVueLoader.install = function(Vue) {
+
+        Vue.mixin({
+
+            beforeCreate: function () {
+
+                var components = this.$options.components;
+
+                for ( var componentName in components ) {
+
+                    if ( typeof(components[componentName]) === 'string' && components[componentName].substr(0, 4) === 'url:' ) {
+
+                        var comp = parseComponentURL(components[componentName].substr(4));
+
+                        var componentURL = ('_baseURI' in this.$options) ? resolveURL(this.$options._baseURI, comp.url) : comp.url;
+
+                        if ( isNaN(componentName) )
+                            components[componentName] = httpVueLoader.load(componentURL, componentName);
+                        else
+                            components[componentName] = Vue.component(comp.name, httpVueLoader.load(componentURL, comp.name));
+                    }
+                }
+            }
+        });
+    };
+
+    httpVueLoader.require = function(moduleName) {
+
+        return window[moduleName];
+    };
+
+    httpVueLoader.httpRequest = function(url) {
+
+        return new Promise(function(resolve, reject) {
+
+            var xhr = new XMLHttpRequest();
+            xhr.open('GET', url);
+            xhr.responseType = 'text';
+
+            xhr.onreadystatechange = function() {
+
+                if ( xhr.readyState === 4 ) {
+
+                    if ( xhr.status >= 200 && xhr.status < 300 )
+                        resolve(xhr.responseText);
+                    else
+                        reject(xhr.status);
+                }
+            };
+
+            xhr.send(null);
+        });
+    };
+
+    httpVueLoader.langProcessor = {
+        html: identity,
+        js: identity,
+        css: identity
+    };
+
+    httpVueLoader.scriptExportsHandler = identity;
+
+    function httpVueLoader(url, name) {
+
+        var comp = parseComponentURL(url);
+        return httpVueLoader.load(comp.url, name);
+    }
+
+    return httpVueLoader;
+});

Некоторые файлы не были показаны из-за большого количества измененных файлов