Full Code of shengzhang666/sa-admin for AI

master 218e4f0b61c2 cached
77 files
1.1 MB
378.6k tokens
110 symbols
1 requests
Download .txt
Showing preview only (1,259K chars total). Download the full file or copy to clipboard to get everything.
Repository: shengzhang666/sa-admin
Branch: master
Commit: 218e4f0b61c2
Files: 77
Total size: 1.1 MB

Directory structure:
gitextract_n2f8j4kf/

├── LICENSE
├── README.md
├── index.html
├── login.html
├── main.html
├── sa-frame/
│   ├── com/
│   │   ├── sa-info.vue
│   │   ├── sa-item.vue
│   │   └── sa-td.vue
│   ├── index/
│   │   ├── admin-util.js
│   │   ├── index.css
│   │   ├── index.js
│   │   └── theme.css
│   ├── login/
│   │   ├── app.js
│   │   ├── reset.css
│   │   └── style.css
│   ├── menu-list.js
│   ├── nav/
│   │   ├── com-add-tab.vue
│   │   ├── com-right-menu.vue
│   │   ├── nav-logo.vue
│   │   ├── nav-menu-bar.vue
│   │   ├── nav-tab-bar.vue
│   │   ├── nav-tool-bar.vue
│   │   └── nav-view-vessel.vue
│   └── sa-code.js
├── sa-view/
│   ├── article/
│   │   ├── art-add.html
│   │   ├── art-list.html
│   │   └── data-list.js
│   ├── case/
│   │   ├── mock-data-list.js
│   │   ├── mock-joptions.js
│   │   ├── query-p-case.html
│   │   ├── query-table-case-add.html
│   │   ├── query-table-case.html
│   │   └── submit-form.html
│   ├── cfg/
│   │   ├── system-cfg.html
│   │   └── xxx.vue
│   ├── console/
│   │   ├── com-chart-1.vue
│   │   ├── com-chart-2.vue
│   │   ├── com-chart-3.vue
│   │   ├── com-intro.vue
│   │   ├── com-origin.vue
│   │   ├── com-sta-data.vue
│   │   ├── com-stack.vue
│   │   ├── com-update-log.vue
│   │   └── console-main.html
│   ├── error-page/
│   │   ├── 403.html
│   │   ├── 404.html
│   │   └── 500.html
│   ├── home/
│   │   ├── mock-data-list.js
│   │   ├── swiper-add.html
│   │   └── swiper-list.html
│   ├── role/
│   │   ├── menu-list.html
│   │   ├── menu-setup.html
│   │   ├── mock-data.js
│   │   ├── role-add.html
│   │   └── role-list.html
│   ├── sa-doc/
│   │   ├── check-per.md
│   │   ├── sa-doc.html
│   │   └── start-up.md
│   ├── ser-goods/
│   │   └── ser-goods-add.html
│   ├── sys-type/
│   │   ├── moke-data.js
│   │   ├── sys-type-add.html
│   │   ├── sys-type-info.html
│   │   └── sys-type-list.html
│   └── user/
│       ├── data-list.js
│       ├── user-add.html
│       ├── user-chart.html
│       └── user-list.html
└── static/
    ├── kj/
    │   ├── element-ui/
    │   │   ├── index.js
    │   │   └── theme-chalk/
    │   │       └── index.css
    │   ├── httpVueLoader.js
    │   ├── layer/
    │   │   ├── layer.js
    │   │   ├── mobile/
    │   │   │   ├── layer.js
    │   │   │   └── need/
    │   │   │       └── layer.css
    │   │   └── theme/
    │   │       └── default/
    │   │           └── layer.css
    │   └── upload-util.js
    ├── sa.css
    └── sa.js

================================================
FILE CONTENTS
================================================

================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2020 省长

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.



================================================
FILE: README.md
================================================
<p align="center">
    <img alt="logo" src="https://oss.dev33.cn/sa-admin/admin-logo.png" width="150" height="150" style="margin-bottom: 10px;">
</p>
<h2 align="center" style="margin: 30px 0 30px;font-weight: bold;font-size:40px;">Sa-Admin v1.41.0</h2>
<h4 align="center">一个多窗口后台模板,流畅、易上手、提高生产力</h4>
<p align="center">
	<a href="https://gitee.com/click33/sa-admin/stargazers"><img src="https://gitee.com/click33/sa-admin/badge/star.svg"></a>
	<a href="https://gitee.com/click33/sa-admin/members"><img src="https://gitee.com/click33/sa-admin/badge/fork.svg"></a>
	<a href="https://github.com/click33/sa-admin/stargazers"><img src="https://img.shields.io/github/stars/click33/sa-admin?style=flat-square&logo=GitHub"></a>
	<a href="https://github.com/click33/sa-admin/network/members"><img src="https://img.shields.io/github/forks/click33/sa-admin?style=flat-square&logo=GitHub"></a>
	<a href="https://github.com/click33/sa-admin/watchers"><img src="https://img.shields.io/github/watchers/click33/sa-admin?style=flat-square&logo=GitHub"></a>
	<a href="https://github.com/click33/sa-admin/issues"><img src="https://img.shields.io/github/issues/click33/sa-admin.svg?style=flat-square&logo=GitHub"></a>
	<a href="https://github.com/click33/sa-admin/blob/master/LICENSE"><img src="https://img.shields.io/github/license/click33/sa-admin.svg?style=flat-square"></a>			
</p>


## ⚡ 介绍
Sa-Admin 是一个多窗口后台模板,纯 html 无后端代码,无需脚手架即可直接运行,流畅、易上手、提高生产力。核心技术栈:Vue + Element-UI + jquery + layer。

废话不多说,具体效果可参见在线预览:[http://sa-admin.dev33.cn/](http://sa-admin.dev33.cn/)

## ⭐ 特点 
Sa-Admin 最大的特点是无需搭建 vue-cli 脚手架,随便一个 html 预览工具(比如 [HBuilderX](https://www.dcloud.io/hbuilderx.html))即可直接运行(采用 http-vue-loader 技术实现)。

目前最新版包括以下功能:

- 视图:支持 iframe 和 .vue 两种视图方式,支持一、二、三、四级菜单。
- 操作:工具栏提供常见操作按钮:折叠、搜索、刷新、账号、便签、主题切换、全屏切换。
- 主题:内置十种主题,也可方便的扩展主题。
- 切换:支持拖拽排序、切换视图自动记录hash,刷新页面自动打开上次的视图。
- 右键:tabbar栏支持右键菜单:悬浮打开、新窗口打开、视图复制、快捷关闭等操作。
- 接口:开放一系列api,可方便的用js新建、打开、切换视图等动作。
- 示例:提供大量常见示例,以及各种表单组件的封装,助你快速CRUD。

## 🖥 截图  

<table>
    <tr>
        <td><img src="https://oss.dev33.cn/sa-admin/pre/sa-admin-pre-1.png"/></td>
        <td><img src="https://oss.dev33.cn/sa-admin/pre/sa-admin-pre-2.png"/></td>
    </tr>
    <tr>
        <td><img src="https://oss.dev33.cn/sa-admin/pre/sa-admin-pre-3.png"/></td>
        <td><img src="https://oss.dev33.cn/sa-admin/pre/sa-admin-pre-4.png"/></td>
    </tr>
</table>


## 🔍 开始使用

### 1、下载项目
直接在 Gitee 或 GitHub 下载代码

### 2、初始化模板
在使用时不建议你直接魔改模板的代码,以免在运行时出现意外bug。在文件夹 `\sa-frame` 下有个 `sa-code.js` 文件,
这是为了方便你对接后端专门预留的文件,你可以在此文件中调用 Sa-Admin 提供的 API 来操作模板。

``` js
// 设置基本信息 
sa_admin.title="xxx";         // 设置模板标题 
sa_admin.logo='xxx.png';      // 设置 logo 图标地址  
sa_admin.icon = 'xxx.ico';    // 设置 icon 图标地址  

// 初始化菜单
var myMenuList = window.menuList;    // window.menuList 在 menu-list.js 中定义(内有格式详细说明)
sa_admin.setMenuList(myMenuList);    // 方式一:写入菜单,这些菜单会全部显示 
sa_admin.setMenuList(myMenuList, ['1', '1-1', '1-2']);    // 方式二:写入菜单,并在第二个参数决定哪些菜单会显示出来
sa_admin.initMenu(['1', '1-1', '1-2']);    // 方式三:相当于方式二省略了第一个参数(框架会自动寻找 window.menuList)
/*
 * MenuList 菜单的格式在 menu-list.js 文件中有详细的示例和格式说明 
 * 一般情况下我们的菜单最终都是一个 Tree 形格式,但我们从数据库查询出来时都是平面一维数组,
 * 这时候你不需要手动做格式转换,调用 sa_admin.setMenuList 模板会自动为你进行格式转换,
 * (前提是你的数组里每个 menu 对象指定了 parentId 属性)
 */

// 设置右上角的 user 信息
sa_admin.user = { 
    username: 'root', 	// 昵称    
    avatar: 'sa-frame/admin-logo.png' // 头像地址 
}

// 设置头像点击处可操作的选项    
sa_admin.dropList = [    
    {    
        name: '我的资料',    
        click: function() { /* balabala... */ }    
    },    
    {    
        name: '退出登录',    
        click: function() { /* balabala... */ }    
    }    
]    

// 初始化模板(必须调用)
sa_admin.init();
```

### 3、js操作模板
你可以使用以下 API 来操作模板 
``` js
sa_admin.showHome();            // 显示主页选项卡 
sa_admin.showTabById('1-1');    // 显示一个选项卡, 根据id
sa_admin.closeTabById('1-1');    // 关闭一个选项卡,根据 id ( 第二个参数可填关闭后的回调函数 )
sa_admin.showMenuById('1-1');    // 打开一个 菜单,根据 id

// 新增一个选项卡
sa_admin.addTab({name: '新页面', url: 'http://sa-token.dev33.cn/'});    // id不要和已有的菜单id冲突,其它属性均可参照菜单项 

// 新增一个选项卡、并立即显示  
sa_admin.showTab({name: '新页面', url: 'http://sa-plus.dev33.cn/'});    // 参数同上 
```

### 4、多视图通信
怎么在一个选项卡页面调用另一个页面的代码
``` js
// 根据id获取其页面的window对象   (如果此页面未打开,则返回空)(跨域模式下无法获取其window对象)
var win = sa_admin.getTabWindow('2-1');      
if(win) {
	win.app.f5();     // 然后调用这个对象上的方法 
}

// 根据iframe的子父通信原则,在子页面中调用父页面的方法,需要加上parent前缀,例如:
parent.sa_admin.msg('啦啦啦');        // 调用父页面的弹窗方法 
```

### 5、鉴权相关
``` js
// 首先在登录时,写入当前会话所具有的权限码集合
var arr = ['1', '2', '3', 'a', 'b', 'c'];        // 一般由后端提供接口返回当前会话所具有的权限码集合 
sa.setAuth(arr);            // 写入本地缓存中 

// 然后:我们就可以愉快的使用鉴权了
// 如果一个页面需要某个权限码才能打开,在这个页面的 <script> 代码块第一句写上:
sa.checkAuth('a');      // 必须具有权限码 `a` 才能打开这个页面,否则会被强制跳转到 403-无权限 页面 

// 某段代码需要某个权限码才能继续往下执行,在需要鉴权的地方加上这段代码 
sa.checkAuthTs('a');    // 含义同上,只不过如果鉴权失败,不是强制跳转,而是弹窗显示 403-无权限 页面 

// 如果需要精细的根据权限来控制页面上某个按钮是否显示
<!-- 可以利用vue的v-if指令来渲染 -->
<button v-if="sa.isAuth('a')">删除这条记录(只有具有权限码a,才能看到这个按钮)</button>

// 注销登录时,可以清除掉所有权限
sa.clearAuth();        // 清除当前会话所有权限码 

/*
 * `sa.checkAuth` 与 `sa.checkAuthTs` 方法为了调用方便,默认在无权限时打开的页面地址为:`../../sa-view/error-page/403.html`
 * 此url只有在当前页面为二级子目录时才能打开成功,其它级别目录则会无法打开显示404,这时候你需要指定403无权限页面地址 
 * 例如在首页index.html调用时,原调用方式:`sa.checkAuth('a')` ,改为:`sa.checkAuth('a', 'sa-view/error-page/403.html')`
 */
```

注:最后请知晓一点:**最终的鉴权操作一定要在后端完成,前端只能是起到一个辅助作用**

## 📐️️️️ 使用封装组件 
你可以使用 Element-UI 原生写法构建表单,也可以使用 Sa-Admin 封装的组件,例如:
``` html
<!-- 原生写法: -->
<div class="c-item">
	<label class="c-label">商品名称:</label>
	<el-input v-model="m.name"></el-input>
</div>

<!-- Sa-Admin 封装写法 (type="text"时可省略不写) -->
<sa-item type="text" name="商品名称" v-model="m.name"></sa-item>
```
两者完全等价 


#### 1、首先引入这些组件 
``` js
var app = new Vue({
	components: {
		"sa-item": httpVueLoader('../../sa-frame/com/sa-item.vue'),		
		"sa-info": httpVueLoader('../../sa-frame/com/sa-info.vue'),		
		"sa-td": httpVueLoader('../../sa-frame/com/sa-td.vue'),			
	},
	// ... 其它代码 
});
```

#### 2、使用 sa-item 组件 
sa-item 封装了各种输入框 
``` html
<!-- 普通输入框 -->
<sa-item type="text" name="商品名称" v-model="m.name"></sa-item>
<!-- 数字输入框 -->
<sa-item type="num" name="商品数量" v-model="m.count"></sa-item>
<!-- 其它 -->
<sa-item type="textarea" name="多行输入" v-model="m.name"></sa-item>
<sa-item type="date" name="日期选择" v-model="m.create_time" br></sa-item>
<sa-item type="datetime" name="日期时间选择" v-model="m.create_time" br></sa-item>
<sa-item type="slider" name="滑块参数" v-model="m.hValue" br></sa-item>
<sa-item type="money" name="钱(单位 元)" v-model="m.value" br></sa-item>
<sa-item type="money-f" name="钱(单位 分)" v-model="m.value" br></sa-item>
<sa-item type="img" name="图片上传" v-model="m.value" br></sa-item>
<sa-item type="audio" name="音频上传" v-model="m.value" br></sa-item>
<sa-item type="video" name="视频上传" v-model="m.value" br></sa-item>
<sa-item type="file" name="文件上传" v-model="m.value" br></sa-item>
<sa-item type="img-list" name="多图上传" v-model="m.value" br></sa-item>
<sa-item type="audio-list" name="多音频上传" v-model="m.value" br></sa-item>
<sa-item type="video-list" name="多视频上传" v-model="m.value" br></sa-item>
<sa-item type="file-list" name="多文件上传" v-model="m.value" br></sa-item>
<sa-item type="img-video-list" name="图片视屏结合" v-model="m.value" br></sa-item>
<sa-item type="richtext" name="富文本编辑器" v-model="m.value" br></sa-item>
<!-- 枚举类型,jtype为枚举表单类型(1=单选框,2=单选文字,3=单选按钮,4=单选下拉框)  -->
<sa-item type="enum" name="枚举参数" v-model="m.value" :jv="{1: '正常', 2: '禁用'}" jtype="1" br></sa-item>
<sa-item type="color" name="颜色选择" v-model="m.value" br></sa-item>
<sa-item type="rate" name="评分组件" v-model="m.value" br></sa-item>

<!-- ------- 复杂组件 ----- -->
<!-- 表格上面的快捷‘增、删、改、查’按钮(可自定义slot) -->
<sa-item type="fast-btn" show="add,get,delete,export,reset"></sa-item>
<!-- 表格下面的分页组件 -->
<sa-item type="page" :curr.sync="p.pageNo" :size.sync="p.pageSize" :total="dataCount" @change="f5()"></sa-item>

<!-- 完全自定义 slot 内容 -->
<sa-item name="自定义slot" br>xxx</sa-item>
```


#### 3、使用 sa-info 组件 
sa-info 封装了各种展示框
``` html
<!-- 普通展示 -->
<sa-info name="商品名称">{{m.name}}</sa-info>
<!-- 数字展示 -->
<sa-info name="商品名称" :value="m.count" type="num"></sa-info>
<!-- 其它 -->
<sa-info name="多行文本" :value="m.value" type="textarea" br></sa-info>
<sa-info name="图片展示" :value="m.url" type="img" br></sa-info>
<sa-info name="音频展示" :value="m.url" type="audio" br></sa-info>
<sa-info name="视频展示" :value="m.url" type="video" br></sa-info>
<sa-info name="文件展示" :value="m.url" type="file" br></sa-info>
<!-- img-list -  value形如:url1,url2,url3 -->
<sa-info name="多图展示" :value="m.url" type="img-list" br></sa-info>
<sa-info name="多音频展示" :value="m.url" type="audio-list" br></sa-info>
<sa-info name="多视频展示" :value="m.url" type="video-list" br></sa-info>
<sa-info name="多文件展示" :value="m.url" type="file-list" br></sa-info>
<sa-info name="图片视频结合" :value="m.url" type="img-video-list" br></sa-info>
<sa-info name="钱(单位元)" :value="m.value" type="money" br></sa-info>
<sa-info name="钱(单位分)" :value="m.value" type="money-f" br></sa-info>
<!-- 枚举 jv={key值: 'value展示文字[颜色]'} -->
<sa-info name="显示枚举" :value="m.value" type="enum" :jv="{1: '正常[green]', 2: '禁用[red]'}" br></sa-info>
<sa-info name="展示链接" :value="m.value" type="link" br></sa-info>
<sa-info name="展示日期" :value="m.value" type="date" br></sa-info>
<sa-info name="展示日期时间" :value="m.value" type="datetime" br></sa-info>
<sa-info name="展示日期时间2">{{sa.forDate(m.create_time, 2)}}</sa-info>
<sa-info name="展示评分组件" :value="m.value" type="rate" br></sa-info>
```

#### 4、使用 sa-td 组件 
sa-td 封装了各种表格单元格 
``` html
<!-- ------------- 数据列表 ------------- -->
<el-table :data="dataList" size="small">
	<!-- 复选框 -->
	<sa-td type="selection"></sa-td>
	<!-- 普通文字单元格 -->
	<sa-td name="商品名称" prop="name"></sa-td>
	<!-- 其它单元格 -->
	<sa-td name="展示数字" prop="value" type="num"></sa-td>
	<sa-td name="展示多行文本域" prop="value" type="textarea"></sa-td>
	<sa-td name="展示富文本" prop="value" type="richtext"></sa-td>
	<sa-td name="展示钱(单位元)" prop="value" type="money"></sa-td>
	<sa-td name="展示钱(单位分)" prop="value" type="money-f"></sa-td>
	<!-- 枚举类型,jtype为枚举表单类型(1=单选框,2=单选文字,3=单选按钮,4=单选下拉框)  -->
	<sa-td name="展示枚举" prop="value" type="enum" :jv="{1: '正常[green]', 2: '禁用[red]'}" jtype="1"></sa-td>
	<sa-td name="展示开关" prop="value" type="switch" :jv="{1: '正常', 2: '禁用'}"></sa-td>
	<sa-td name="展示icon" prop="value" type="icon"></sa-td>
	<sa-td name="展示图片" prop="value" type="img"></sa-td>
	<sa-td name="展示音频" prop="value" type="audio"></sa-td>
	<sa-td name="展示视频" prop="value" type="video"></sa-td>
	<sa-td name="展示文件" prop="value" type="file"></sa-td>
	<!-- img-list -  value形如:url1,url2,url3 -->
	<sa-td name="展示多图" prop="value" type="img-list"></sa-td>
	<sa-td name="展示多音频" prop="value" type="audio-list"></sa-td>
	<sa-td name="展示多视频" prop="value" type="video-list"></sa-td>
	<sa-td name="展示多文件" prop="value" type="file-list"></sa-td>
	<sa-td name="展示图片视频结合" prop="value" type="img-video-list"></sa-td>
	<sa-td name="展示链接" prop="value" type="link"></sa-td>
	<sa-td name="展示链接按钮" prop="value" type="link-btn" @click="s => sa.msg('点击事件')"></sa-td>
	<sa-td name="展示日期" prop="value" type="date"></sa-td>
	<sa-td name="展示日期时间" prop="value" type="datetime"></sa-td>
	<sa-td name="展示评分" prop="value" type="rate"></sa-td>
	<el-table-column label="操作" width="240px">
		<template slot-scope="s"> 自定义slot </template>
	</el-table-column>
</el-table>
```




## 🔨 贡献代码
1. 在gitee上fork一份到自己的仓库
2. clone自己的仓库到本地电脑
3. 在本地电脑修改、commit、push
4. 提交pr
5. 等待合并


## 🌱 建议贡献的地方
- 更多登录模板
- 修复源码现有bug,或增加新的实用功能
- 更多demo示例:比如针对element-ui一些复杂组件的示例,或者其它一些常见js库的集成使用


## 🪒 访问旧版本
Sa-Admin v1.40.0 之前的版本通过纯 html + iframe 方式构建,如果您需要访问旧版本:
- 源码:[https://gitee.com/click33/sa-admin/tree/html/](https://gitee.com/click33/sa-admin/tree/html/)
- 演示站:[http://sa-admin-html.dev33.cn/](http://sa-admin-html.dev33.cn/)


## 😎️ QQ群
QQ交流群:[782974737 点击加入](https://jq.qq.com/?_wv=1027&k=5DHN5Ib)





================================================
FILE: index.html
================================================
<!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="static/sa.js"></script>
		<script src="sa-frame/sa-code.js"></script>

	</body>
</html>


================================================
FILE: login.html
================================================
<!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: 270px; border-radius: 0px; transition: all 0.2s;}
			.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;}
			
		</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-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 = 'Sa-Admin 登录';		// 页面标题 
			var key = 'admin';						// 默认的账号
			var password = 'admin';					// 默认的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()
				}
				// 2、判断
				if(p.key == '' || p.password == ''){
					return layer.msg('请输入任意内容即可');
				}
				// 3、请求后台
				sa.ajax2('/acc/doLogin', p, function(res) {
					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>


================================================
FILE: main.html
================================================
<!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/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>


================================================
FILE: sa-frame/com/sa-info.vue
================================================
<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>


================================================
FILE: sa-frame/com/sa-item.vue
================================================
<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">{{name}}:</label> 
		<el-input type="text" :value="value" @input="onInput" :placeholder="placeholder" :disabled="disabled"></el-input>
	</div>
	<!-- 数字input -->
	<div class="c-item" :class="{br: br}" v-else-if="type == 'num'">
		<label class="c-label">{{name}}:</label> 
		<el-input type="number" :value="value" @input="onInput" :placeholder="placeholder" :disabled="disabled"></el-input>
	</div>
	<!-- 密码input -->
	<div class="c-item" :class="{br: br}" v-else-if="type == 'password'">
		<label class="c-label">{{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">{{name}}:</label> 
		<el-date-picker type="datetime" value-format="yyyy-MM-dd HH:mm:ss" :value="value" @input="onInput" :placeholder="placeholder" :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'
			},
			// 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>


================================================
FILE: sa-frame/com/sa-td.vue
================================================
<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>


================================================
FILE: sa-frame/index/admin-util.js
================================================
// ======================== 一些工具方法 ======================== 

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 菜单 (根据其指定的parentId添加到其父菜单的childList)
	arrayToTree: function(menu_list) {
		for (var i = 0; i < menu_list.length; i++) {
			var menu = menu_list[i];
			// 添加到其指定的父菜单的childList
			if(menu.parentId) {
				var parent_menu = this.findMenuById(menu_list, menu.parentId);
				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;
	},
	
	
}









================================================
FILE: sa-frame/index/index.css
================================================
*{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;}



================================================
FILE: sa-frame/index/index.js
================================================
// 首页 
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.41.0',					// 当前版本号
		updateTime: '2021-10-24',			// 更新日期 
		githubUrl: 'https://github.com/click33/sa-admin',	// github地址 
		isRemeOpen: true,		// 是否记住上一次最后打开的窗口 
		printInfo: true,		// 是否在控制台打印信息 
		homeTab: homeTab,	// 主页首屏 Tab 
		menuList: [],		// 全部菜单集合 
		showList: [],		// 显示的菜单集合(id集合) 
		
		// ------------------------------- 状态 -------------------------------
		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 数组,也可以是一个尚未渲染的一维数组(你只要指定好 parentId,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.isBlank) {
				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 菜单 (根据其指定的 parentId 添加到其父菜单的childList)
		arrayToTree: function(menuList) {
			for (var i = 0; i < menuList.length; i++) {
				var menu = menuList[i];
				// 如果这个 Menu 指定了 parentId 属性,则将其转移到其指定的父 Menu 的 childList 属性上 
				if(menu.parentId) {
					var parent_menu = this.findMenuById(menuList, menu.parentId);
					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');
		},
		
	},
	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();
}




================================================
FILE: sa-frame/index/theme.css
================================================
/* 样式调整为继承父级 */
.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;
}


================================================
FILE: sa-frame/login/app.js
================================================
/* -----------------------------------------------
/* 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
	}

);

================================================
FILE: sa-frame/login/reset.css
================================================
@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;}


================================================
FILE: sa-frame/login/style.css
================================================
@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;}



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

// 定义菜单列表 
var menuList =	[
	{
		id: '2',
		name: '各种示例',
		icon: 'el-icon-document-remove',
		info: '增删改查各种常用组件示例',
		childList: [
			{id: '2-1', name: '查询参数示例', url: 'sa-view/case/query-p-case.html'},
			{id: '2-2', name: '表格显示示例', url: 'sa-view/case/query-table-case.html'},
			{id: '2-3', name: '表单提交示例', url: 'sa-view/case/submit-form.html'},
			{id: '2-11', name: '在线表单构建', url: 'https://mrhj.gitee.io/form-generator/#/'},
		]
	},
	{
		id: '3',
		name: '首页设置',
		icon: 'el-icon-search',
		info: '首页的一些设置',
		childList: [
			{id: '3-1-1', name: '分类列表', icon: 'el-icon-eleme', url: 'sa-view/sys-type/sys-type-list.html'},
			{id: '3-1-2', name: '分类添加', icon: 'el-icon-plus', url: 'sa-view/sys-type/sys-type-add.html'},
			{id: '3-2-1', name: '轮播图列表', icon: 'el-icon-collection-tag', url: 'sa-view/home/swiper-list.html'},
			{id: '3-2-2', name: '轮播图添加', icon: 'el-icon-plus', url: 'sa-view/home/swiper-add.html'},
			{id: '3-3-1', name: '商品规格示例', icon: 'el-icon-plus', url: 'sa-view/ser-goods/ser-goods-add.html'},
		]
	},
	{
		id: '4',
		name: '权限控制',
		icon: 'el-icon-unlock',
		info: '对系统角色权限的分配等设计,敏感度较高,请谨慎授权',
		childList: [
			{id: '4-1', name: '角色列表', icon: 'el-icon-key', url: 'sa-view/role/role-list.html'},
			{id: '4-2', name: '菜单列表', icon: 'el-icon-magic-stick', url: 'sa-view/role/menu-list.html'}
		]
	},
	{
		id: '5',
		name: '用户管理',
		icon: 'el-icon-user',
		info: '对用户列表、添加、统计等等...',
		childList: [
			{id: '5-1', name: '用户列表', icon: 'el-icon-document-remove', url: 'sa-view/user/user-list.html'},
			{id: '5-2', name: '用户添加', icon: 'el-icon-plus', url: 'sa-view/user/user-add.html'},
			{
				id: '5-3',
				name: '用户统计',
				icon: 'el-icon-data-line',
				childList: [
					{id: '1-3-1', name: '注册量统计', icon: 'el-icon-pie-chart', url: 'sa-view/user/user-chart.html'},
				]
			},
		]
	},
	{
		id: '6',
		name: '文章管理',
		icon: 'el-icon-document-copy',
		info: '对文章的增删改查、维护',
		childList: [
			{id: '6-1', name: '文章列表', url: 'sa-view/article/art-list.html'},
			{id: '6-2', name: '文章发布', url: 'sa-view/article/art-add.html'},
		]
	},
	{
		id: '7',
		name: '系统设置',
		icon: 'el-icon-setting',
		info: '有关系统的一些设置',
		childList: [
			{id: '7-1', name: '登录页', url: 'login.html'},
			{id: '7-8', name: '403无权限', url: 'sa-view/error-page/403.html'},
			{id: '7-9', name: '404未找到', url: 'sa-view/error-page/404.html'},
			{id: '7-10', name: '500有错误', url: 'sa-view/error-page/500.html'},
			{id: '7-11', name: '服务器设置', url: 'sa-view/cfg/system-cfg.html', info: '对服务器参数的设置'},
			{id: '7-12', name: '函数菜单', click: function(){sa.alert('点击菜单执行一个函数,你可以自定义任意代码')}},
			{id: '6-3', name: 'vue组件', url: 'sa-view/cfg/xxx.vue'}
		]
	},
	//  ========= 示例 外部链接 点击从新窗口打开 ================
	{
		id: '8',
		name: '友情链接',
		icon: 'el-icon-link',
		info: '示例:外部链接',
		childList: [
			{id: '8-21', name: '极品蓝图', url: 'http://un.jipinlantu.com/', isBlank: true},
			{id: '8-11', name: '河浪前端笔记', url: 'https://mydarling.gitee.io/resource/', isBlank: true},
			
			// {id: '8-1', name: '百度一下', url: 'https://www.baidu.com/', isBlank: true},
			{id: '8-41', name: 'sa-admin单页版', url: 'http://sa-vue-admin.dev33.cn/', isBlank: true},
			{id: '8-3', name: 'sa-token', url: 'http://sa-token.dev33.cn/', isBlank: true},
			{id: '8-12', name: 'sa-doc', url: 'http://sa-doc.dev33.cn/', isBlank: true},
			{id: '8-13', name: 'sa-plus', url: 'http://sa-plus.dev33.cn/', isBlank: true},
			{id: '8-4', name: 'SqlFly', url: 'https://sqlfly.dev33.cn/', isBlank: true},
			{id: '8-6', name: '颜值排行榜', url: 'http://yanzhi21.com/', isBlank: true},
			{id: '8-7', name: 'jq22插件库', url: 'http://www.jq22.com/', isBlank: true},
			{id: '8-2', name: 'uni-app', url: 'https://uniapp.dcloud.io/', isBlank: true},
			{id: '8-31', name: '秀恩爱专用', url: 'http://click33.gitee.io/xixi/'},
		]
	},
	//  ========= 示例 隐藏的菜单,最终将不会显示在菜单栏里 ================
	{
		id: '9',
		name: '一个隐藏菜单',
		url: 'https://www.baidu.com/',
		isBlank: true,
		isShow: false// 隐藏
	},
	//  ========= jq22搜集 ================
	{
		id: '111',
		name: 'jq22搜集',
		icon: 'el-icon-link',
		info: '示例:外部链接',
		childList: [
			{
				id: '110',
				name: '大屏展示',
				icon: 'el-icon-link',
				info: '大屏展示页',
				childList: [
					{id: '110-1', name: '大屏1', url: 'http://www.jq22.com/demo/estszjcmoban202008030007/'},	// 原作者:http://www.jq22.com/jquery-info23260
					{id: '110-2', name: '大屏2', url: 'http://www.jq22.com/demo/estjkdsj202007301414/'},	// 原作者:http://www.jq22.com/jquery-info23247
					{id: '110-3', name: '大屏3', url: 'http://www.jq22.com/demo/jquerygndsjmoban202007212350/'},	// 原作者:http://www.jq22.com/jquery-info23239
					{id: '110-4', name: '大屏4', url: 'http://www.jq22.com/demo/jqueryEchartsny202006151033/'},	// 原作者:http://www.jq22.com/jquery-info23114
					{id: '110-5', name: '大屏5', url: 'http://www.jq22.com/demo/echartsdindanmoban202007302202/'},	// 原作者:http://www.jq22.com/jquery-info23202
					{id: '110-6', name: '大屏6', url: 'http://www.jq22.com/demo/echartssjmoban202005210009/'},	// 原作者:http://www.jq22.com/jquery-info23047
					{id: '110-7', name: '大屏7', url: 'http://www.jq22.com/demo/echartsdsj202002251026/'},	// 原作者:http://www.jq22.com/jquery-info22826
					{id: '110-8', name: '大屏8', url: 'http://www.jq22.com/demo/echartswldsj201912112223/'},	// 原作者:http://www.jq22.com/jquery-info22636
				],
			},
			{id: '111-1', name: '图片切换', url: 'http://www.jq22.com/demo/jQueryTpqh201804012309/'},	// 原作者:https://www.jq22.com/jquery-info18534
			{id: '111-2', name: '3D旋转特效', url: 'http://www.jq22.com/demo/jQueryCss3D201710241004/'},	// 原作者:https://www.jq22.com/jquery-info16495
			{id: '111-3', name: 'canvas炫酷星空', url: 'http://www.jq22.com/demo/warpDrive201712211120/index.html'},	// 原作者:https://www.jq22.com/jquery-info17456
			{id: '111-4', name: 'H5碰撞小球', url: 'http://www.jq22.com/demo/html5Pzxq201712242209/'},	// 原作者:https://www.jq22.com/jquery-info17482
			{id: '111-5', name: '网页画板', url: 'http://www.jq22.com/demo/Mapping201802252341/'},	// 原作者:https://www.jq22.com/jquery-info18172
			{id: '111-6', name: '简约富文本编辑器', url: 'http://www.jq22.com/demo/jquery-notebook-master/'},	// 原作者:https://www.jq22.com/jquery-info345
			{id: '111-7', name: '水滴特效', url: 'http://www.jq22.com/demo/jquery-shuidi20151123/'},	// 原作者:https://www.jq22.com/jquery-info4835
			{id: '111-8', name: '图片放大', url: 'http://www.jq22.com/demo/jQueryJpg201708110048/'},	// 原作者:http://www.jq22.com/jquery-info15264
			{id: '111-9', name: '3D云', url: 'http://www.jq22.com/demo/jquery-cloud-141217202931/'},	// 原作者:http://www.jq22.com/jquery-info1325
			{id: '111-10', name: '3D选择图片', url: 'http://www.jq22.com/demo/jquery-3d20150831/'},	// 原作者:http://www.jq22.com/jquery-info4000
			{id: '111-11', name: '蜘蛛纸牌', url: 'http://www.jq22.com/demo/jqueryspider201809140137/'},	// 原作者:http://www.jq22.com/jquery-info20047
			{id: '111-12', name: '大转盘', url: 'http://www.jq22.com/demo/jquerylocal201912122316/'},	// 原作者:http://www.jq22.com/jquery-info22646
			{id: '111-13', name: '旋转地球', url: 'http://www.jq22.com/demo/earth201810300101/'},	// 原作者:http://www.jq22.com/jquery-info20328
			{id: '111-14', name: '下雨动画', url: 'http://www.jq22.com/demo/html5-canvas-rain201710252014/'},	// 原作者:http://www.jq22.com/jquery-info16518
			{id: '111-15', name: '绚丽星空', url: 'http://www.jq22.com/demo/jQuery3dxk201710142249/'},	// 原作者:http://www.jq22.com/jquery-info16294
			{id: '111-16', name: '3d波浪墙', url: 'http://www.jq22.com/demo/voxels-liquid201704112355/'},	// 原作者:http://www.jq22.com/jquery-info13400
			{id: '111-17', name: '元素周期表', url: 'http://www.jq22.com/demo/jquery-3D20151113/'},	// 原作者:http://www.jq22.com/jquery-info4710
			
			{id: '111-18', name: '旋转相册', url: 'http://www.jq22.com/demo/tikm202006072243/'},	// 原作者:http://www.jq22.com/jquery-info23116
			{id: '111-19', name: '装逼专用', url: 'http://www.jq22.com/demo/canvaslxy202003192234/'},	// 原作者:http://www.jq22.com/jquery-info22793
			{id: '111-20', name: '3D粒子文字', url: 'http://www.jq22.com/demo/3dwz201912102124/'},	// 原作者:http://www.jq22.com/jquery-info22631
			{id: '111-21', name: '多面立方体', url: 'http://www.jq22.com/demo/threelft201905080117/'},	// 原作者:http://www.jq22.com/demo/threelft201905080117/
			
			{id: '111-22', name: '常见配色', url: 'http://www.jq22.com/demo/jQueryColour202008050020/'},	// 原作者:http://www.jq22.com/jquery-info23262
			{id: '111-23', name: '音量调节', url: 'http://www.jq22.com/demo/AdjustVolume202005122241/'},	// 原作者:http://www.jq22.com/jquery-info23045
			{id: '111-24', name: '重力下落', url: 'http://www.jq22.com/demo/gamecaisse202005220040/'},	// 原作者:http://www.jq22.com/jquery-info23074
			{id: '111-25', name: '表情匹配', url: 'http://www.jq22.com/demo/emojimatchgame201907170050/dist/'},	// 原作者:http://www.jq22.com/jquery-info21952
			
			
		]
	},
]

================================================
FILE: sa-frame/nav/com-add-tab.vue
================================================
<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>


================================================
FILE: sa-frame/nav/com-right-menu.vue
================================================
<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>


================================================
FILE: sa-frame/nav/nav-logo.vue
================================================
<!-- 左上: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>


================================================
FILE: sa-frame/nav/nav-menu-bar.vue
================================================
<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" 
				:unique-opened="true" 	
				:default-active="$root.activeMenuId" 
				:collapse="!$root.isOpen"
				@select="selectMenu" 
				>
				<div v-for="(menu, index) in $root.menuList" :key="index">
					<!-- 1 如果是子菜单 -->
					<el-menu-item v-if="!menu.childList && 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.childList && 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.childList && 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.childList && 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.childList && 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.childList && 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>


================================================
FILE: sa-frame/nav/nav-tab-bar.vue
================================================
<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>


================================================
FILE: sa-frame/nav/nav-tool-bar.vue
================================================
<!-- 右边第一行,工具栏 -->
<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="点击登录" 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,		// 刷新当前时间的定时器 
				
				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>


================================================
FILE: sa-frame/nav/nav-view-vessel.vue
================================================
<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>


================================================
FILE: sa-frame/sa-code.js
================================================
// 在使用时,不建议你直接魔改模板的代码,以免在运行时出现意外bug,而是在本文件中根据模板的提供的API,来适应你的业务逻辑 
// ....



// ================================= 示例:一些基本信息 ================================= 

// 设置模板标题 
sa_admin.title = "Sa-Admin";
sa_admin.logo = 'sa-frame/admin-logo.png';    // 设置logo图标地址   
sa_admin.icon = 'sa-frame/admin-logo.png';    // 设置icon图标地址 


// ================================= 示例:自定义菜单 =================================

var myMenuList = window.menuList;		// window.menuList 在 menu-list.js 中定义 
sa_admin.setMenuList(myMenuList);	// 写入菜单  
// sa_admin.setMenuList(myMenuList, [11, 1, '1-1']);	// 写入菜单,并设置应该显示哪些id的菜单(第二个参数为空时,代表默认显示所有) 
// sa_admin.initMenu();	// 更简单的写法,相当于setMenuList省略第一个参数 

// 如果需要获得更多操作能力,如:动态添加菜单、删除菜单等
// 可直接 sa_admin.menuList 获得菜单引用,直接操作对象 


// ================================= 示例: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');	

// ================================= 示例:设置user信息 =================================
// 用户登录后,右上角可直接显示用户的头像和昵称
sa_admin.user = {
	username: 'root',	// 昵称 
	avatar: 'sa-frame/admin-logo.png'	// 头像地址  
}



// ================================= 示例:设置登录后的头像处,下拉可以出现的选项  =================================
sa_admin.dropList = [		// 头像点击处可操作的选项
	{
		name: '我的资料',
		click: function() {
			sa_admin.$message('点击了我的资料,你可以参照文档重写此函数');
		}
	},
	{
		name: '切换账号',
		click: function() {
			sa.$page.openLogin('login.html');
			// layer.open({
			// 	type: 2,
			// 	title: false,
			// 	closeBtn: false,
			// 	shadeClose: true,
			// 	shade: 0.8,
			// 	area: ['70%', '80%'],
			// 	resize: false,
			// 	content: 'login.html'
			// });
		}
	},
	{
		name: '退出登录',
		click: function() {
			// sa_admin.$message('点击了退出登录,你可以参照文档重写此函数');
			// location="login.html";
			sa.confirm('退出登录?', function(res) {
				sa.ajax2('/acc/exit', function(res) {
					sa.alert('注销成功', function() {
						location.href = "login.html";
					})
				}, {msg: '正在注销'})
			});
		}
	}
]


// ================================= 示例:调用另一个页面的代码 =================================
// var win = sa_admin.getTabWindow('2-1');		// 根据id获取其页面的window对象   (如果此页面未打开,则返回空)(跨域模式下无法获取其window对象)
// win.app.f5();

// 注意:
// 根据`iframe`的子父通信原则,在子页面中调用父页面的方法,需要加上parent前缀,例如:
// parent.sa_admin.msg('啦啦啦');		// 调用父页面的弹窗方法 



// ================================= 示例:初始化模板(必须调用)  =================================
// 初始化模板 
sa_admin.init();




================================================
FILE: sa-view/article/art-add.html
================================================
<!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.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/wangeditor@4.7.8/dist/wangEditor.min.js"></script>
		<script src="../../static/sa.js"></script>
		<style type="text/css">
			/* wang富文本编辑器 */
			.editor-item{width: 100%; height: auto;}
			.editor-item .c-label{float: left;}
			.editor-item .editor-box{float: left; width: 80%; margin-top: 0px; margin-left: 0px;} 
            .editor-item #editor{min-height: 300px; background-color: #FFF;}
            .editor-item .w-e-toolbar{padding-top: 5px !important;}
			/* 尽量小点 */
			.editor-item .editor-box{float: left; width: 800px;} 
			.editor-item .editor-box img{max-width: 200px !important;}
		</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">
						<div class="c-item br">
							<label class="c-label">文章标题:
							</label><el-input v-model="m.title" style="width: 800px;" placeholder="例如:2002年的第一场雪"></el-input>
						</div>
						<div class="c-item br editor-item">
							<label class="c-label">文章内容:</label>
							<div class="editor-box">
								<div id="editor"></div>
							</div>
							<div style="clear: both;"></div>
						</div>
						<div class="c-item br">
							<label class="c-label">是否公开:</label>
							<el-switch v-model="m.is_public" :active-value="1" :inactive-value="2"></el-switch>
							<span class="c-remark">关闭后,别人将不能看到你的发表</span>
						</div>
						<div class="c-item br s-ok">
							<label class="c-label"></label>
							<el-button type="primary" icon="el-icon-plus" @click="ok()">保存</el-button>
						</div>
						<div style="margin-top: 10px;"></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>
			<!-- 给wangEditor打一波广告 -->
			<!-- <div class="c-panel" style="background-color: rgba(0,0,0,0);">
				wangEditor:<el-link type="primary" href="http://www.wangeditor.com/" target="_blank">
					基于javascript和css开发的 Web富文本编辑器, 轻量、简洁、易用、开源免费
				</el-link>
			</div> -->
		</div>
		<script src="./data-list.js"></script>
		<script type="text/javascript">
			// 创建编辑器
			function create_editor(content) {
				var E = window.wangEditor
				window.editor = new E('#editor');

				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.uploadImgShowBase64 = true   	// 使用 base64 保存图片
				editor.create(); // 创建
				editor.txt.html(content);	// 为编辑器赋值
			}
		</script>
        <script>
			
			var app = new Vue({
				el: '.vue-box',
				data: {
					id: sa.p('id', 0),		// 获取超链接中的id参数(0=添加,非0=修改) 
					m: null,		// 实体对象 
				},
				methods: {
					// 创建一个 默认Model 
					createModel: function() {
						return {
							id: '101',
							title: '江雪',
							content: '<p>千山鸟飞绝,万径人踪灭。</p><p>孤舟蓑笠翁,独钓寒江雪。</p>',
							is_public: 1,
							see_count: 356,
							like_count: 55,
							share_count: 13,
							create_username: '省长',
							is_public: 1,
							create_time: "2019-05-12"
						}
					},
					// 提交数据 
					ok: function(){
						// 验证 
						var m = this.m;		// 获取 m对象 
						sa.checkNull(m.title, '请输入文章标题');
						
						// 开始增加或修改
						// this.m.create_time = undefined;		// 不提交属性:创建日期
						if(this.id <= 0) {	// 添加
							sa.ajax2('/SysType/add', this.m, function(res){
								sa.alert('增加成功', function(res) {
									if(parent.app) {
										var m2 = sa.copyJSON(this.m);
										parent.app.dataList.push(m2);
										parent.sa.f5TableHeight();		// 刷新表格高度 
										sa.closeCurrIframe();	// 关闭本页 
									} else {
										this.m = this.createModel();
										editor.txt.html("");	// 清除图文介绍  
									}
								}.bind(this)); 
							}.bind(this));
						} else {	// 修改
							sa.ajax2('/SysType/update', this.m, function(res){
								sa.alert('修改成功', this.clean);
							}.bind(this));
						}
					},
					// 添加/修改 完成后的动作
					clean: function() {
						if(this.id == 0) {
							this.m = this.createModel();
							editor.txt.html("");	// 清除图文介绍  
						} else {
							parent.app.f5();		// 刷新父页面列表
							sa.closeCurrIframe();	// 关闭本页 
						}
					}
				},
				mounted: function(){
					// 初始化数据 
					if(this.id <= 0) {	
						this.m = this.createModel();
						this.$nextTick(function() {
							create_editor(this.m.content);
						})
					} else {	
						sa.ajax2('/SysType/getById?id=' + this.id, function(res) {
							this.m = res.data;
							this.$nextTick(function() {
								create_editor(this.m.content);
							})
						}.bind(this), {res: getById(this.id)})
					}
				}
			})
			
			// 获取对应的data 
			function getById(id) {
				var res = {
					code: 200,
					msg: 'ok',
					data: null
				};
				mockData.data.forEach(function(item) {
					if(item.id == id) {
						res.data = item;
					}
				})
				return res;
			}
		</script>
	</body>
</html>

================================================
FILE: sa-view/article/art-list.html
================================================
<!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.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">
			/* .data-table .el-table__body-wrapper{min-height: 0px; max-height: 0px;}
			.data-table .el-table__body-wrapper{transition: all 0.3s;} */
		</style>
	</head>
	<body>
		<div class="vue-box" style="display: none;" :style="'display: block;'">
			<div class="c-panel">
				<!-- ------------- 检索参数 ------------- -->
				<div class="c-title">检索参数</div>
				<el-form>
					<div class="c-item">
						<label class="c-label">文章编号:</label>
						<el-input v-model="p.id" type="number"></el-input>
					</div>
					<div class="c-item">
						<label class="c-label">标题:</label>
						<el-input v-model="p.title" placeholder="模糊查询"></el-input>
					</div>
					<div class="c-item" style="min-width: 10px;">
						<el-button type="primary" icon="el-icon-search" @click="f5">查询</el-button>
					</div>
					<br>
					<div class="c-item" style="min-width: 10px;">
						<label class="c-label">综合排序:</label>
						<el-radio-group v-model="p.sortType">
							<el-radio :label="0">发表时间</el-radio>
							<el-radio :label="1">喜欢数</el-radio>
							<el-radio :label="2">点击数</el-radio>
							<el-radio :label="3">分享数</el-radio>
						</el-radio-group>
					</div>
				</el-form>
				<!-- ------------- 快捷按钮 ------------- -->
				<div class="fast-btn">
					<el-button type="primary" icon="el-icon-plus" @click="add()">新增</el-button>
					<!-- <el-button type="success" icon="el-icon-view" @click="getBySelect()">查看</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>
				</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="编号" prop="id" width="70px"> </el-table-column>
					<el-table-column label="文章标题" prop="title"></el-table-column>
					<el-table-column label="文章内容" prop="content" width="400px"></el-table-column>
					<el-table-column label="发表人" prop="create_username"></el-table-column>
					<el-table-column label="发表于" prop="create_time"></el-table-column>
					<el-table-column label="点击量" prop="see_count"></el-table-column>
					<el-table-column label="喜欢" prop="like_count"></el-table-column>
					<el-table-column label="分享" prop="share_count"></el-table-column>
					<el-table-column prop="address" label="操作" width="180px">
						<template slot-scope="s">
							<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>
				<!-- ------------- 分页 ------------- -->
				<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, 30, 40, 50, 100]" 
						@current-change="f5(true)" 
						@size-change="f5(true)">
					</el-pagination>
				</div>
			</div>
		</div>
		<script src="data-list.js"></script>
		<script>
			var app = new Vue({
				el: '.vue-box',
				data: {
					sa: sa,
					p: { // 查询参数
						id: '',
						title: '',
						sortType: 0,
						pageNo: 1,
						pageSize: 10,
					},
					dataCount: 0,	// 数据总数 
					dataList: [] // 数据集合
				},
				methods: {
					// 数据刷新
					f5: function() {
						sa.ajax2('/VocArticle/getList', this.p, function(res){
							this.dataList = res.data;	// 数据
							this.dataCount = res.dataCount;		// 分页 
							sa.f5TableHeight();		// 刷新表格高度 
						}.bind(this), {res: mockData});
					},
					// 修改
					update: function(data) {
						sa.showIframe('文章修改', 'art-add.html?id=' + data.id, '970px', '600px');
					},
					// 新增
					add: function(data) {
						sa.showIframe('文章添加', 'art-add.html?id=-1', '970px', '600px');
					},
					// 删除
					del: function(data) {
						sa.confirm('是否删除,此操作不可撤销', function() {
							sa.ajax2('/acticle/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.ajax2('/SysType/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>


================================================
FILE: sa-view/article/data-list.js
================================================
var mockData = {
	code: 200,
	msg: 'ok',
	data: [{
			"id": 20,
			"title": "那个清晨",
			"content": "那个清晨一大早,便被母亲叫起。我有些不满,平常我是总要在床上多赖一会儿的。可当我迷迷糊糊的看到母亲紧绷的脸庞时...",
			"see_count": 356,
			"like_count": 55,
			"share_count": 13,
			"create_username": '省长',
			"is_public": 1,
			"create_time": "2019-05-12"
		},
		{
			"id": 17,
			"title": "人生,就是一场抵达",
			"content": "庄子说,人生天地之间,若白驹过隙,忽然而已。人生就是一场抵达,我们总以为来日方长...",
			"see_count": 200,
			"like_count": 12,
			"share_count": 6,
			"create_username": '小言',
			"is_public": 1,
			"create_time": "2019-05-12"
		},
		{
			"id": 11,
			"title": "气质女生与世界先生",
			"content": "我把衣柜翻了个底朝天,花花绿绿地堆了满床。谢雨帆盘腿坐在电脑前打游戏,嗑着瓜子眼皮也不抬一下,半晌才悠悠地吐出一句...",
			"see_count": 240,
			"like_count": 22,
			"share_count": 15,
			"create_username": 'fan哈',
			"is_public": 2,
			"create_time": "2019-05-10"
		},
		{
			"id": 9,
			"title": "善待朋友,珍惜拥有",
			"content": "不要弄丢,一个对你好的人;不要漠视,一份待你深的情。不是谁都能包容你的臭脾气,更不是谁都能一直等下去...",
			"see_count": 2420,
			"like_count": 122,
			"share_count": 95,
			"create_username": '小丸子',
			"is_public": 2,
			"create_time": "2019-05-5"
		},
		{
			"id": 7,
			"title": "爱是淡淡的忧伤",
			"content": "回味初识的那一段深情爱恋,清晰的面容,熟悉的身影,悦耳的声音,在某个深夜恍然想起。那是一个深秋的夜晚,我们相拥而坐...",
			"see_count": 320,
			"like_count": 12,
			"share_count": 5,
			"create_username": '不值一提',
			"is_public": 1,
			"create_time": "2019-05-1"
		},
		{
			"id": 6,
			"title": "男人看了沉默,女人看了流泪",
			"content": "男人看了沉默,女人看了流泪男人看了沉默,女人看了流泪男人看了沉默,女人看了流泪男人看了沉默,女人看了流泪...",
			"see_count": 3220,
			"like_count": 11,
			"share_count": 1,
			"create_username": 'UC震惊部',
			"is_public": 1,
			"create_time": "2019-05-1"
		},
		{
			"id": 5,
			"title": "爱是淡淡的忧伤",
			"content": "回味初识的那一段深情爱恋,清晰的面容,熟悉的身影,悦耳的声音,在某个深夜恍然想起。那是一个深秋的夜晚,我们相拥而坐...",
			"see_count": 320,
			"like_count": 12,
			"share_count": 5,
			"create_username": '不值一提',
			"is_public": 1,
			"create_time": "2019-05-1"
		}
	],
	dataCount: 6379
}


================================================
FILE: sa-view/case/mock-data-list.js
================================================
var mockDataList = {
	code: 200,
	msg: 'ok',
	data: [
		{
			id: 101, 
			name: '苹果', 
			icon: 'el-icon-apple',
			avatar: 'http://color-test.oss-cn-qingdao.aliyuncs.com/dyc/img/2020/01/19/15794196587511189314194.png', 
			money: 1999,
			status: 1,
			day: '135',
			create_time: new Date()
		},
		{
			id: 102, 
			name: '香蕉', 
			icon: 'el-icon-apple',
			avatar: 'http://color-test.oss-cn-qingdao.aliyuncs.com/dyc/img/2020/01/19/15794196587511189314194.png', 
			money: 2555,
			status: 1,
			day: '135',
			create_time: new Date()
		},
		{
			id: 103, 
			name: '茄子', 
			icon: 'el-icon-cherry',
			avatar: 'http://color-test.oss-cn-qingdao.aliyuncs.com/dyc/img/2020/01/19/15794196587511189314194.png', 
			money: 4999,
			status: 1,
			day: '135',
			create_time: new Date()
		},
		{
			id: 104, 
			name: '芸豆', 
			icon: 'el-icon-cherry',
			avatar: 'http://color-test.oss-cn-qingdao.aliyuncs.com/dyc/img/2020/01/19/15794196587511189314194.png', 
			money: 5555,
			status: 1,
			day: '135',
			create_time: new Date()
		},
		{
			id: 105, 
			name: '木须肉', 
			icon: 'el-icon-umbrella',
			avatar: 'http://color-test.oss-cn-qingdao.aliyuncs.com/dyc/img/2020/01/19/15794196587511189314194.png', 
			money: 10000,
			status: 1,
			day: '246',
			create_time: new Date()
		},
		{
			id: 106, 
			name: '回锅肉', 
			icon: 'el-icon-umbrella',
			avatar: 'http://color-test.oss-cn-qingdao.aliyuncs.com/dyc/img/2020/01/19/15794196587511189314194.png', 
			money: 10000,
			status: 1,
			day: '246',
			create_time: new Date()
		},
		{
			id: 107, 
			name: '辣子鸡', 
			icon: 'el-icon-user',
			avatar: 'http://color-test.oss-cn-qingdao.aliyuncs.com/dyc/img/2020/01/19/15794196587511189314194.png', 
			money: 99999,
			status: 2,
			day: '246',
			create_time: new Date()
		},
		{
			id: 108, 
			name: '大萝贝', 
			icon: 'el-icon-user',
			avatar: 'http://color-test.oss-cn-qingdao.aliyuncs.com/dyc/img/2020/01/19/15794196587511189314194.png', 
			money: 23333,
			status: 2,
			day: '567',
			create_time: new Date()
		},
	],
	dataCount: 1244
}

================================================
FILE: sa-view/case/mock-joptions.js
================================================
var mockJoptions = [		// 级联选项  
	{
		value: 'v1', 
		label: '选项1',
		children: [
			{
				value: 'v1-1', 
				label: '选项1-1',
				children: [
					{value: 'v1-1-1', label: '选项1-1-1'},
					{value: 'v1-1-2', label: '选项1-1-2'},
					{value: 'v1-1-3', label: '选项1-1-3'},
				]
			},
			{value: 'v1-2', label: '选项1-2'},
			{value: 'v1-3', label: '选项1-3'},
		]
	},
	{
		value: 'v2', 
		label: '选项2',
		children: [
			{value: 'v2-1', label: '选项2-1'},
			{value: 'v2-2', label: '选项2-2'}
		]
	},
	{
		value: 'v3', 
		label: '选项3',
		children: [
			{value: 'v3-1', label: '选项3-1'},
			{value: 'v3-2', label: '选项3-2'}
		]
	}
];

================================================
FILE: sa-view/case/query-p-case.html
================================================
<!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.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>
	</head>
	<body>
		<div class="vue-box" style="display: none;" :style="'display: block;'">
			<div class="c-panel">
				<!-- ------------- 参数栏 ------------- -->
				<div class="c-title">检索参数</div>
				<el-form>
					<sa-item type="text" name="普通参数" v-model="p.name"></sa-item>
					<sa-item type="num" name="数值参数" v-model="p.count"></sa-item>
					<sa-item name="下拉参数">
						<el-select v-model="p.type_id">
							<el-option label="不限" :value="0"></el-option>
							<el-option v-for="type in typeList" :label="type.name" :value="type.id" :key="type.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 type="date" name="日期参数" v-model="p.create_time"></sa-item>
					<sa-item name="时间区间">
						<el-date-picker v-model="p.start_time" type="date" value-format="yyyy-MM-dd" placeholder="开始日期"></el-date-picker> - 
						<el-date-picker v-model="p.end_time" type="date" value-format="yyyy-MM-dd" placeholder="结束日期"></el-date-picker>
					</sa-item>
					<br>
					<sa-item name="单选参数">
						<el-radio-group v-model="p.single">
							<el-radio :label="1">参数1</el-radio>
							<el-radio :label="2">参数2</el-radio>
						</el-radio-group>
					</sa-item>
					<sa-item name="单选按钮">
						<el-radio-group v-model="p.single2">
							<el-radio-button :label="1">参数1</el-radio-button>
							<el-radio-button :label="2">参数2</el-radio-button>
							<el-radio-button :label="3">参数3</el-radio-button>
							<el-radio-button :label="4">参数4</el-radio-button>
						</el-radio-group>
					</sa-item>
					<br>
					<sa-item name="单选文字">
						<el-radio-group v-model="p.status" class="s-radio-text">
							<el-radio :label="0">全部</el-radio>
							<el-radio :label="1">新下单</el-radio>
							<el-radio :label="2">已支付</el-radio>
							<el-radio :label="3">已发货</el-radio>
							<el-radio :label="4">已收货</el-radio>
							<el-radio :label="5">已评价</el-radio>
							<el-radio :label="11">取消中</el-radio>
							<el-radio :label="21">退款中</el-radio>
						</el-radio-group>
					</sa-item>
					<br>
					<sa-item name="多选参数">
						<el-checkbox v-model="p.checkbox_1">已上架</el-checkbox>
						<el-checkbox v-model="p.checkbox_2">已推荐</el-checkbox>
						<el-checkbox v-model="p.checkbox_3">热卖中</el-checkbox>
						<el-checkbox v-model="p.checkbox_4">高点击</el-checkbox>
						<el-checkbox v-model="p.checkbox_5">高转化</el-checkbox>
						<el-checkbox v-model="p.checkbox_6">高复买</el-checkbox>
					</sa-item>
				</el-form>
				<!-- ------------- 数据列表 ------------- -->
				<el-table :data="dataList">
					<el-table-column label="编号" prop="id"></el-table-column>
					<el-table-column label="名称" prop="name"></el-table-column>
					<el-table-column label="库存" prop="count"></el-table-column>
					<el-table-column label="操作" width="220px">
						<template slot-scope="s">
							<el-button type="text" @click="get(s.row)">查看</el-button>
							<el-button type="text" @click="update(s.row)">修改</el-button>
							<el-button type="text" @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="dataList.length" @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: '',
						count: '',
						type_id: 0, 
						create_time: '',
						start_time: new Date().getFullYear() + '-' + (new Date().getMonth() + 1) + '-1',	// 本月一号 
						end_time: new Date().getFullYear() + '-' + (new Date().getMonth() + 1) + '-' + new Date().getDate(),	// 本月当日 
						single: 1,
						single2: 1,
						status: 0,
						checkbox_1: true,
						checkbox_2: true,
						checkbox_3: false,
						checkbox_4: false,
						checkbox_5: false,
						checkbox_6: false,
						pageNo: 1,
						pageSize: 100,
					},
					typeList: [		// 类型数组  
						{id: 1, name: '类型1'},
						{id: 2, name: '类型2'},
						{id: 3, name: '类型3'},
						{id: 4, name: '类型4'},
					],
					dataList: [		// 数据集合
						{id: 101, name: '苹果', count: 3223},
						{id: 102, name: '鸭梨', count: 3231},
						{id: 103, name: '香蕉', count: 4321},
						{id: 104, name: '西瓜', count: 34223},
						{id: 105, name: '菠萝', count: 53272},
						{id: 106, name: '辣椒', count: 20000},
						{id: 107, name: '冬瓜', count: 50000},
						{id: 108, name: '西红柿', count: 99999},
						{id: 109, name: '火龙果', count: 58989},
						{id: 110, name: '红烧鸡脖子', count: 22345},
					],	
				},
				methods: {
					// 刷新
					f5: function(){
						//this.$message(JSON.stringify(this.p))
						sa.ajax2('/goods/getList', this.p, function(res){
							// this.dataList = res.data;	// 数据
						}.bind(this));
					},
					// 查看
					get: function(data) {
						var str = '<div>';
						str += '<p>编号:' + data.id + '</p>';
						str += '<p>名称:' + data.name + '</p>';
						str += '<p>库存:' + data.count + '</p>';
						str += '</div>';
						sa.alert(str);
					},
					// 修改
					update: function (data) {
						// sa.msg('你点击了id=' + data.id + '的修改按钮');
						sa.prompt('修改库存', function(pass, index){
							if(isNaN(pass)) {
								return sa.error('请输入一个数字')
							}
							sa.ajax2('/data/update', {id: data.id, count: data.count}, function(res){
								data.count = pass;
								sa.ok('修改成功');
							})
						}, 0, data.count);
					},
					// 删除
					del: function (data) {
						sa.confirm('是否删除,此操作不可撤销', function() {
							sa.ajax2('/goods/delete?id=' + data.id, function(res) {
								sa.arrayDelete(this.dataList, data);
								sa.ok('删除成功');
							}.bind(this))
						}.bind(this));
					},
				},
				created: function(){
					this.f5();
					sa.onInputEnter();		// 监听输入框的回车事件,执行查询  
					this.$notify.info({
						title: '消息',
						message: '本页面展示各种检索参数,以便完成强大的列表查询'
					});
				}
			})
			
		</script>
	</body>
</html>

================================================
FILE: sa-view/case/query-table-case-add.html
================================================
<!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.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">
			/* 流体input */
			.c-panel .el-form .el-input,.el-select{width: calc(100% - 120px);}
			.c-panel .el-form .el-select .el-input{width: 100%;}
		</style>
	</head>
	<body>
		<div class="vue-box" :class="{sbot: id}" style="display: none;" :style="'display: block;'">
			<!-- ------- 内容部分 ------- -->
			<div class="s-body">
				<div class="c-panel">
					<el-form v-if="m">
						<sa-item name="编号" v-model="m.id" br disabled></sa-item>
						<sa-item name="名称" v-model="m.name" br></sa-item>
						<sa-item name="图标" v-model="m.icon" br></sa-item>
						<sa-item name="状态" v-model="m.status" type="j" :jv="{1: '正常', 2: '禁用'}" jtype="3" 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 src="mock-data-list.js"></script>
		<script type="text/javascript">
			var app = new Vue({
				components: {
					"sa-item": httpVueLoader('../../sa-frame/com/sa-item.vue'),				// logo 
				},
				el: '.vue-box',
				data: {
					id: sa.p('id', 0),	// 获取传递的参数 
					m: null
				},
				methods: {
					// 创建一个 默认Model 
					createModel: function() {
						return {
							id: 0, 
							name: '', 
							icon: '',
							avatar: 'http://sa-admin.dev33.cn/sa-resources/admin-logo.png', 
							money: 99.99,
							status: 1,
							create_time: new Date()
						}
					},
					// 提交 
					ok: function() {
						// 表单验证 
						sa.checkNull(this.m.name, '请输入名称');
						sa.checkNull(this.m.icon, '请输入图标');
						// 开始增加或修改
						if(this.id <= 0) {
							// id == 0 为增加        
							sa.ajax2('/data/add', this.m, function(res) {
								sa.alert('增加成功', app.clean);
							}.bind(this));
						} else {
							// id != 0 为修改  
							sa.ajax2('/data/update', this.m, function(res){
								sa.alert('修改成功', app.clean);
							}.bind(this));
						}
					},
					// 添加/修改 完成后的动作
					clean: function() {
						if(this.id == 0) {
							this.m = this.createModel();	// 初始化model 
						} else {
							parent.app.f5();		// 刷新父页面列表
							sa.closeCurrIframe();	// 关闭本页 
						}
					}
				},
				created: function(){
					// 如果是添加
					if(this.id <= 0){
						this.m = this.createModel();
					} else {
						// id 非 0 代表 是要修改 先查询一下,把旧值显示在input中  
						sa.ajax2('/data/getById', {id: this.id}, function(res) {
							this.m = res.data;
						}.bind(this), {res: getMockData()});
					}
				}
			})
			
			// 遍历判断, 获取模拟数据  
			function getMockData() {
				var id = sa.p('id', 0);	
				var data = null;
				// 遍历判断 
				mockDataList.data.forEach(function(item) {
					if(item.id == id) {
						data = item;
					}
				})
				// 创建模拟数据 
				var mockData = {
					code: 200,
					msg: 'ok',
					data: data
				}
				return mockData;
			}
		</script>
		
	</body>
</html>


================================================
FILE: sa-view/case/query-table-case.html
================================================
<!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.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>
	</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 name="名称查询" v-model="p.name" placeholder="模糊查询"></sa-item>
					<el-button type="primary" icon="el-icon-search" @click="p.pageNo = 1; f5()">查询</el-button>
				</el-form>
				<!-- ------------- 快捷curd按钮 ------------- -->
				<sa-item type="fast-btn" show="add,get,delete,export,reset"></sa-item>
				<!-- ------------- 数据列表 ------------- -->
				<el-table class="data-table" ref="data-table" :data="dataList" size="small">
					<sa-td type="selection"></sa-td>
					<sa-td name="id" prop="id" width="70px"></sa-td>
					<sa-td name="显示文本" prop="name"></sa-td>
					<sa-td type="img" name="显示头像" prop="avatar"></sa-td>
					<sa-td type="icon" name="显示图标" prop="icon"></sa-td>
					<sa-td type="money-f" name="显示价格" prop="money"></sa-td>
					<el-table-column label="显示标签" width="190px" >
						<template slot-scope="s">
							<el-tag size="small" effect="plain" v-if="s.row.day.indexOf('1') > -1">周一</el-tag>
							<el-tag size="small" effect="plain" v-if="s.row.day.indexOf('2') > -1">周二</el-tag>
							<el-tag size="small" effect="plain" v-if="s.row.day.indexOf('3') > -1">周三</el-tag>
							<el-tag size="small" effect="plain" v-if="s.row.day.indexOf('4') > -1">周四</el-tag>
							<el-tag size="small" effect="plain" v-if="s.row.day.indexOf('5') > -1">周五</el-tag>
							<el-tag size="small" effect="plain" v-if="s.row.day.indexOf('6') > -1">周六</el-tag>
							<el-tag size="small" effect="plain" v-if="s.row.day.indexOf('7') > -1">周日</el-tag>
						</template>
					</el-table-column>
					<sa-td type="j" name="显示状态" prop="status" :jv="{1: '正常[green]', 2: '禁用[red]'}"></sa-td>
					<sa-td type="switch" name="显示开关" prop="status" :jv="{1: '正常', 2: '禁用'}"></sa-td>
					<sa-td type="datetime" name="显示日期" prop="create_time" width="150px"></sa-td>
					<el-table-column label="文字按钮" width="160px">
						<template slot-scope="s">
							<el-button type="text" @click="get(s.row)">查看</el-button>
							<el-button type="text" @click="update(s.row)">修改</el-button>
							<el-button type="text" @click="del(s.row)">删除</el-button>
						</template>
					</el-table-column>
					<el-table-column label="正常按钮" 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 src="mock-data-list.js"></script>
        <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: '',
						pageNo: 1,
						pageSize: 10,
					},
					dataCount: 0,		// 数据总数 
					dataList: [],		// 数据集合
				},
				methods: {
					// 刷新
					f5: function(){
						sa.ajax2('/goods/getList', this.p, function(res){
							this.dataList = res.data;	// 数据
							this.dataCount = res.dataCount;	// 分页
							sa.f5TableHeight();		// 刷新表格高度 
						}.bind(this), {res: mockDataList});
					},
					// 查看
					get: function(data) {
						var str = '<div>';
						str += '<p>编号:' + data.id + '</p>';
						str += '<p>名称:' + data.name + '</p>';
						str += '<p>状态:' + (data.status == 1 ? '正常' : '禁用') + '</p>';
						str += '<p>价格:' + (data.money / 100) + ' 元</p>';
						str += '<p>日期:' + (sa.forDate(data.create_time, 2)) + '</p>';
						str += '</div>';
						sa.alert(str);
					},
					// 查看 - 根据选中的
					getBySelect: function(data) {
						var selection = this.$refs['data-table'].selection;
						if(selection.length == 0) {
							return sa.msg('请选择一条数据')
						}
						this.get(selection[0]);
					},
					// 添加 - 底部按钮式
					add: function(data) {
						sa.showIframe('添加一个', 'query-table-case-add.html?id=-1', '450px', '320px');
					},
					// 修改
					update: function (data) {
						sa.showIframe('修改数据', 'query-table-case-add.html?id=' + data.id, '450px', '320px');
					},
					// 删除
					del: function (data) {
						sa.confirm('是否删除,此操作不可撤销', function() {
							sa.ajax2('/goods/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.ajax2('/SysType/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();		// 监听输入框的回车事件,执行查询  
					this.$notify.info({
						title: '消息',
						message: '本页面展示表格的常见显示方式'
					});
				}
			})
			
		</script>
	</body>
</html>

================================================
FILE: sa-view/case/submit-form.html
================================================
<!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.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 .el-form .el-input,
			.c-panel .el-form .el-textarea__inner{width: 250px;}
		</style>
	</head>
	<body>
		<div class="vue-box" style="display: none;" :style="'display: block;'">
			<div class="c-panel">
				<!-- 参数栏 -->
				<div class="c-title">表单提交</div>
				<el-form v-if="m">
					<sa-item type="text" name="普通参数" v-model="m.name" br></sa-item>
					<sa-item type="num" name="数值输入" v-model="m.count" br></sa-item>
					<sa-item type="textarea" name="多行输入" v-model="m.textareaValue" br></sa-item>
					<sa-item type="cascader" name="级联输入" v-model="m.jvalue" :options="joptions" br></sa-item>
					<sa-item type="date" name="日期选择" v-model="m.create_time" br></sa-item>
					<sa-item type="date-range" name="日期范围" :start.sync="m.startDate" :end.sync="m.endDate" br></sa-item>
					<sa-item type="slider" name="滑块参数" v-model="m.hValue" br></sa-item>
					
					<sa-item name="下拉输入" br>
						<el-select v-model="m.type_id">
							<el-option label="请选择" :value="0" disabled></el-option>
							<el-option v-for="type in typeList" :label="type.name" :value="type.id" :key="type.id"></el-option>
						</el-select>
					</sa-item>
					<sa-item name="单选参数" br>
						<el-radio-group v-model="m.single">
							<el-radio :label="1">参数1</el-radio>
							<el-radio :label="2">参数2</el-radio>
							<el-radio :label="3">参数3</el-radio>
						</el-radio-group>
					</sa-item>
					<sa-item name="多选参数" br>
						<el-checkbox v-model="m.checkbox_1">苹果</el-checkbox>
						<el-checkbox v-model="m.checkbox_2">鸭梨</el-checkbox>
						<el-checkbox v-model="m.checkbox_3">香蕉</el-checkbox>
						<el-checkbox v-model="m.checkbox_4">葡萄</el-checkbox>
					</sa-item>
					<sa-item name="单选按钮" br>
						<el-radio-group v-model="m.single2">
							<el-radio-button :label="1">参数1</el-radio-button>
							<el-radio-button :label="2">参数2</el-radio-button>
							<el-radio-button :label="3">参数3</el-radio-button>
							<el-radio-button :label="4">参数4</el-radio-button>
						</el-radio-group>
					</sa-item>
					<sa-item name="开关参数" br>
						<el-switch v-model="m.status" :active-value="1" :inactive-value="2" inactive-color="#ff4949"></el-switch>
						<span style="color: #999;" v-if="m.status==1">正常</span>
						<span style="color: #999;" v-else>禁用</span>
					</sa-item>
					
					<sa-item type="color" name="颜色选择" v-model="m.color" br></sa-item>
					<sa-item type="rate" name="评分组件" v-model="m.rateValue" br></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 src="mock-joptions.js"></script>
		<script type="text/javascript">
			var app = new Vue({
				components: {
					"sa-item": httpVueLoader('../../sa-frame/com/sa-item.vue'),				// logo 
				},
				el: '.vue-box',
				data: {
					m: {	// 查询参数 
						name: '',
						count: '',
						create_time: '',
						create_time2: '',
						startDate: '',
						endDate: '',
						textareaValue: '',
						type_id: 0,
						jvalue: [],
						single: 1,
						single2: 1,
						checkbox_1: true,
						checkbox_2: true,
						checkbox_3: false,
						checkbox_4: false,
						status: 1,
						hValue: 20,
						rateValue: 0,
						color: '#409EFF'
					}, 
					typeList: [		// 类型数组  
						{id: 1, name: '类型1'}, 
						{id: 2, name: '类型2'}, 
						{id: 3, name: '类型3'}, 
						{id: 4, name: '类型4'}, 
					],
					joptions: mockJoptions
				},
				methods: {
					// 提交 
					ok: function() {
						// 表单校验 
						sa.checkNull(this.m.name, '请输入普通参数');
						sa.checkNull(this.m.count, '请输入数值参数');
						sa.check(this.m.type_id == 0, '请选择下拉框');
						// 提交 
						sa.alert('提交参数为:' + JSON.stringify(this.m));
					},
				},
				created: function(){
					this.$notify.info({
						title: '消息',
						message: '本页面展示提交表单时各种常见的各种姿势'
					});
				}
			})
			
		</script>
	</body>
</html>


================================================
FILE: sa-view/cfg/system-cfg.html
================================================
<!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.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;}
		</style>
	</head>
	<body>
		<div class="vue-box" style="display: none;" :style="'display: block;'">
			<!-- 参数栏 -->
			<div class="c-panel">
				<div class="c-title">服务器所有设置</div>
				<el-form v-if="m">
					<div class="c-item br">
						<label class="c-label">系统名称:</label>
						<el-input v-model="m.app_name"></el-input>
					</div>
					<div class="c-item br">
						<label class="c-label">当前版本:</label>
						<el-input v-model="m.app_version_no"></el-input>
					</div>
					<div class="c-item br">
						<label class="c-label">联系Q群:</label>
						<el-input v-model="m.qq_group"></el-input>
					</div>
					<div class="c-item br">
						<label class="c-label">开放注册:</label>
						<el-switch v-model="m.xcx_share_btn" :active-value="1" :inactive-value="2"></el-switch>
						<span class="c-remark" v-if="m.open_reg==1">开启</span>
						<span class="c-remark" v-else>关闭</span>
					</div>
					<div class="c-item br">
						<label class="c-label">显示分享按钮:</label>
						<el-switch v-model="m.gzhxc" :active-value="1" :inactive-value="2"></el-switch>
						<span class="ps" v-if="m.show_share==1">开启</span>
						<span class="ps" v-else>关闭</span>
					</div>
					<div class="c-item br">
						<label class="c-label"></label>
						<el-button type="primary" icon="el-icon-check" @click="ok">保存修改</el-button>
						<el-button type="primary" icon="el-icon-close" @click="f5">重置</el-button>
					</div>
				</el-form>
			</div>
		</div>
		<script type="text/javascript">
			// 创建一个初始化的m 
			function create_m() {
				return {	
					app_name: '',
					app_version_no: '',
					qq_group: '',
					open_reg: 1,
					show_share: 1,
				}
			}
		</script>
		<script type="text/javascript">
			var app = new Vue({
				el: '.vue-box',
				data: {
					m: null
				},
				methods: {
					// f5
					f5: function() {
						var value = localStorage.getItem('system-cfg');
						if(value != null) {
							value = JSON.parse(value);
						} else {
							value = create_m();
						}
						this.m = value;
					},
					// ok
					ok: function() {
						var value = JSON.stringify(this.m);
						localStorage.setItem('system-cfg', value);
						sa.ok('保存成功');
					},
				},
				created: function() {
					this.f5();
				}
			})
			
		</script>
		
		
		
		
		
		
	</body>
</html>


================================================
FILE: sa-view/cfg/xxx.vue
================================================
<template>
	<div>
		<div style="padding: 60px; text-align: center;">
			<h1 style="height: 100px;">.vue组件 (此页面由 .vue 文件提供,而非 .html 文件)</h1>
			<div style="height: 100px;">充数</div> 
			<div style="height: 100px;">充数</div> 
			<div style="height: 100px;">充数</div> 
			<div style="height: 100px;">充数</div> 
			<div style="height: 100px;">充数</div> 
			<div style="height: 100px;">充数</div> 
			<div style="height: 100px;">充数</div> 
			<div style="height: 100px;">充数</div> 
			<div style="height: 100px;">充数</div> 
			<div style="height: 100px;">充数</div> 
			<div style="height: 100px;">充数</div> 
			<div style="height: 100px;">充数</div> 
			<div style="height: 100px;">充数</div> 
			<div style="height: 100px;">充数</div> 
			<div style="height: 100px;">充数</div> 
			<div style="height: 100px;">充数</div> 
		</div>
	</div>
</template>

<script>
	module.exports = {
		data() {
			return {
				
			}
		},
		methods: {
			
		},
		created() {
			
		}
	}
</script>

<style scoped>
</style>


================================================
FILE: sa-view/console/com-chart-1.vue
================================================
<!-- 统计图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>


================================================
FILE: sa-view/console/com-chart-2.vue
================================================
<!-- 统计图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>


================================================
FILE: sa-view/console/com-chart-3.vue
================================================
<!-- 统计图 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>


================================================
FILE: sa-view/console/com-intro.vue
================================================
<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>


================================================
FILE: sa-view/console/com-origin.vue
================================================
<template>
	<div>
		<el-alert type="success" :closable="false" title="缘起" style="border-width: 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>


================================================
FILE: sa-view/console/com-sta-data.vue
================================================
<!-- 第一行统计数据 -->
<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>


================================================
FILE: sa-view/console/com-stack.vue
================================================
<!-- 第一行统计数据 -->
<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=5DHN5Ib')">QQ群(782974737)</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="primary" size="small" @click="sa.open('https://jq.qq.com/?_wv=1027&k=5DHN5Ib')">QQ群(782974737)</el-button> -->
			<el-button type="success" size="small" @click="sa.open('https://github.com/click33/sa-admin')">GitHub 地址 (求star)</el-button>
			<el-button type="danger" size="small" @click="sa.open('https://gitee.com/click33/sa-admin')">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-admin')">需求征集</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>


================================================
FILE: sa-view/console/com-update-log.vue
================================================
<!-- 第一行统计数据 -->
<template>
	<el-timeline>
		<!-- ---------- 一个版本 第41个----------- -->
		<el-timeline-item timestamp="v1.41.0 &emsp; 2021-10-24" placement="top" type="primary">
			<li>新增:表单封装新增部分type</li>
			<li>重构:删除掉不必要的size</li>
		</el-timeline-item>
		<!-- ---------- 一个版本 第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>
		<!-
Download .txt
gitextract_n2f8j4kf/

├── LICENSE
├── README.md
├── index.html
├── login.html
├── main.html
├── sa-frame/
│   ├── com/
│   │   ├── sa-info.vue
│   │   ├── sa-item.vue
│   │   └── sa-td.vue
│   ├── index/
│   │   ├── admin-util.js
│   │   ├── index.css
│   │   ├── index.js
│   │   └── theme.css
│   ├── login/
│   │   ├── app.js
│   │   ├── reset.css
│   │   └── style.css
│   ├── menu-list.js
│   ├── nav/
│   │   ├── com-add-tab.vue
│   │   ├── com-right-menu.vue
│   │   ├── nav-logo.vue
│   │   ├── nav-menu-bar.vue
│   │   ├── nav-tab-bar.vue
│   │   ├── nav-tool-bar.vue
│   │   └── nav-view-vessel.vue
│   └── sa-code.js
├── sa-view/
│   ├── article/
│   │   ├── art-add.html
│   │   ├── art-list.html
│   │   └── data-list.js
│   ├── case/
│   │   ├── mock-data-list.js
│   │   ├── mock-joptions.js
│   │   ├── query-p-case.html
│   │   ├── query-table-case-add.html
│   │   ├── query-table-case.html
│   │   └── submit-form.html
│   ├── cfg/
│   │   ├── system-cfg.html
│   │   └── xxx.vue
│   ├── console/
│   │   ├── com-chart-1.vue
│   │   ├── com-chart-2.vue
│   │   ├── com-chart-3.vue
│   │   ├── com-intro.vue
│   │   ├── com-origin.vue
│   │   ├── com-sta-data.vue
│   │   ├── com-stack.vue
│   │   ├── com-update-log.vue
│   │   └── console-main.html
│   ├── error-page/
│   │   ├── 403.html
│   │   ├── 404.html
│   │   └── 500.html
│   ├── home/
│   │   ├── mock-data-list.js
│   │   ├── swiper-add.html
│   │   └── swiper-list.html
│   ├── role/
│   │   ├── menu-list.html
│   │   ├── menu-setup.html
│   │   ├── mock-data.js
│   │   ├── role-add.html
│   │   └── role-list.html
│   ├── sa-doc/
│   │   ├── check-per.md
│   │   ├── sa-doc.html
│   │   └── start-up.md
│   ├── ser-goods/
│   │   └── ser-goods-add.html
│   ├── sys-type/
│   │   ├── moke-data.js
│   │   ├── sys-type-add.html
│   │   ├── sys-type-info.html
│   │   └── sys-type-list.html
│   └── user/
│       ├── data-list.js
│       ├── user-add.html
│       ├── user-chart.html
│       └── user-list.html
└── static/
    ├── kj/
    │   ├── element-ui/
    │   │   ├── index.js
    │   │   └── theme-chalk/
    │   │       └── index.css
    │   ├── httpVueLoader.js
    │   ├── layer/
    │   │   ├── layer.js
    │   │   ├── mobile/
    │   │   │   ├── layer.js
    │   │   │   └── need/
    │   │   │       └── layer.css
    │   │   └── theme/
    │   │       └── default/
    │   │           └── layer.css
    │   └── upload-util.js
    ├── sa.css
    └── sa.js
Download .txt
SYMBOL INDEX (110 symbols across 7 files)

FILE: sa-frame/index/admin-util.js
  function _dg (line 83) | function _dg(menu_list) {

FILE: sa-frame/index/index.js
  function _dg (line 122) | function _dg(menuList) {

FILE: static/kj/element-ui/index.js
  function i (line 1) | function i(n){if(t[n])return t[n].exports;var r=t[n]={i:n,l:!1,exports:{...
  function c (line 1) | function c(e,t){for(var i=[],n=0,r=e.length;n<r;n++)i.push(e[n].substr(0...
  function h (line 1) | function h(e){return function(t,i,n){var r=n[e].indexOf(i.charAt(0).toUp...
  function d (line 1) | function d(e,t){for(e=String(e),t=t||2;e.length<t;)e="0"+e;return e}
  function a (line 1) | function a(e){return e&&e.__esModule?e:{default:e}}
  function u (line 1) | function u(){s=Number(new Date),i.apply(a,l)}
  function n (line 1) | function n(e,t){return function(){e&&e.apply(this,arguments),t&&t.apply(...
  function s (line 1) | function s(e,t){var i;return t&&!0===t.clone&&n(e)?o((i=e,Array.isArray(...
  function a (line 1) | function a(e,t,i){var r=e.slice();return t.forEach(function(t,a){void 0=...
  function o (line 1) | function o(e,t,i){var r=Array.isArray(t);return r===Array.isArray(e)?r?(...
  function e (line 1) | function e(e,t){var i=-1;return e.some(function(e,n){return e[0]===t&&(i...
  function t (line 1) | function t(){this.__entries__=[]}
  function e (line 1) | function e(){this.connected_=!1,this.mutationEventsAdded_=!1,this.mutati...
  function f (line 1) | function f(e){return parseFloat(e)||0}
  function m (line 1) | function m(e){for(var t=[],i=1;i<arguments.length;i++)t[i-1]=arguments[i...
  function v (line 1) | function v(e){var t=e.clientWidth,i=e.clientHeight;if(!t&&!i)return p;va...
  function b (line 1) | function b(e){return n?g(e)?function(e){var t=e.getBBox();return y(0,0,t...
  function y (line 1) | function y(e,t,i,n){return{x:e,y:t,width:i,height:n}}
  function e (line 1) | function e(e){this.broadcastWidth=0,this.broadcastHeight=0,this.contentR...
  function e (line 1) | function e(e,t,n){if(this.activeObservations_=[],this.observations_=new ...
  function i (line 1) | function i(e,i,n){this._reference=e.jquery?e[0]:e,this.state={};var r=nu...
  function n (line 1) | function n(t){var i=t.style.display,n=t.style.visibility;t.style.display...
  function r (line 1) | function r(e){var t={left:"right",right:"left",bottom:"top",top:"bottom"...
  function s (line 1) | function s(e){var t=Object.assign({},e);return t.right=t.left+t.width,t....
  function a (line 1) | function a(e,t){var i,n=0;for(i in e){if(e[i]===t)return n;n++}return null}
  function o (line 1) | function o(t,i){var n=e.getComputedStyle(t,null);return n[i]}
  function l (line 1) | function l(t){var i=t.offsetParent;return i!==e.document.body&&i?i:e.doc...
  function u (line 1) | function u(t){var i=t.parentNode;return i?i===e.document?e.document.body...
  function c (line 1) | function c(e,t){Object.keys(t).forEach(function(i){var n,r="";-1!==["wid...
  function h (line 1) | function h(e){var t={width:e.offsetWidth,height:e.offsetHeight,left:e.of...
  function d (line 1) | function d(e){var t=e.getBoundingClientRect(),i=-1!=navigator.userAgent....
  function p (line 1) | function p(t){for(var i=["","ms","webkit","moz","o"],n=0;n<i.length;n++)...
  function o (line 1) | function o(e,t){t.forEach(function(t){e.classList.add(t)})}
  function l (line 1) | function l(e,t){t.forEach(function(t){e.setAttribute(t.split(":")[0],t.s...
  function l (line 1) | function l(e){var t=0,i=0,n=0,r=0;return"detail"in e&&(i=e.detail),"whee...
  function b (line 1) | function b(){if(!g){g=!0;var e=navigator.userAgent,t=/(?:MSIE.(\d+\.\d+)...
  function r (line 1) | function r(e,t,i,n,r,s,a,o){var l,u="function"==typeof e?e.options:e;if(...
  function f (line 1) | function f(e){return"[object String]"===Object.prototype.toString.call(e)}
  function m (line 1) | function m(e){return"[object Object]"===Object.prototype.toString.call(e)}
  function v (line 1) | function v(e){return e&&e.nodeType===Node.ELEMENT_NODE}
  function _ (line 1) | function _(){}
  function x (line 1) | function x(e,t){return w.call(e,t)}
  function C (line 1) | function C(e,t){for(var i in t)e[i]=t[i];return e}
  function S (line 1) | function S(e,t,i){for(var n=e,r=(t=(t=t.replace(/\[(\w+)\]/g,".$1")).rep...
  function F (line 1) | function F(e){var t=!1;return function(){for(var i=this,n=arguments.leng...
  function J (line 1) | function J(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[...
  function Q (line 1) | function Q(e){return null!=e}
  function ee (line 1) | function ee(e){return/([(\uAC00-\uD7AF)|(\u3130-\u318F)])+/gi.test(e)}
  function pe (line 1) | function pe(e,t){if(!e||!t)return!1;if(-1!==t.indexOf(" "))throw new Err...
  function fe (line 1) | function fe(e,t){if(e){for(var i=e.className,n=(t||"").split(" "),r=0,s=...
  function me (line 1) | function me(e,t){if(e&&t){for(var i=t.split(" "),n=" "+e.className+" ",r...
  function Ue (line 1) | function Ue(e){var t=e.move,i=e.size,n=e.bar,r={},s="translate"+n.axis+"...
  function st (line 1) | function st(e,t,i){return function(){var n=arguments.length>0&&void 0!==...
  function ot (line 1) | function ot(e,t){if(!h.a.prototype.$isServer)if(t){for(var i=[],n=t.offs...
  function e (line 1) | function e(){!function(e,t){if(!(e instanceof t))throw new TypeError("Ca...
  function vn (line 1) | function vn(e,t){return Object.prototype.hasOwnProperty.call(e,t)}
  function gn (line 1) | function gn(e){return void 0!==e&&(e=parseInt(e,10),isNaN(e)&&(e=null)),e}
  function bn (line 1) | function bn(e){return"number"==typeof e?e:"string"==typeof e?/^\d+(?:px)...
  function yn (line 1) | function yn(e,t,i){var n=!1,r=e.indexOf(t),s=-1!==r,a=function(){e.push(...
  function wn (line 1) | function wn(e,t){var i=arguments.length>2&&void 0!==arguments[2]?argumen...
  function En (line 1) | function En(e){var t={};return Object.keys(e).forEach(function(i){var n=...
  function e (line 1) | function e(t){for(var i in function(e,t){if(!(e instanceof t))throw new ...
  function Zn (line 1) | function Zn(e,t){var i=t.row,n=t.column,r=t.$index,s=n.property,a=s&&S(i...
  function br (line 1) | function br(e,t,i,n){for(var r=t;r<i;r++)e[r]=n}
  function ua (line 1) | function ua(e){return null!==e&&"object"===(void 0===e?"undefined":la(e)...
  function Ba (line 1) | function Ba(){for(var e=arguments.length,t=Array(e),i=0;i<e;i++)t[i]=arg...
  function za (line 1) | function za(e,t){return null==e||(!("array"!==t||!Array.isArray(e)||e.le...
  function Ha (line 1) | function Ha(e,t,i){var n=0,r=e.length;!function s(a){if(a&&a.length)i(a)...
  function Ra (line 1) | function Ra(e,t,i,n){if(t.first)return Ha(function(e){var t=[];return Ob...
  function Wa (line 1) | function Wa(e){return function(t){return t&&t.message?(t.field=t.field||...
  function ja (line 1) | function ja(e,t){if(t)for(var i in t)if(t.hasOwnProperty(i)){var n=t[i];...
  function to (line 1) | function to(){return{default:"Validation error on field %s",required:"%s...
  function no (line 1) | function no(e){this.rules=null,this._messages=io,this.define(e)}
  function r (line 1) | function r(e,t){return Ia()({},t,{fullField:i.fullField+"."+e})}
  function a (line 1) | function a(){var a=arguments.length>0&&void 0!==arguments[0]?arguments[0...
  function fo (line 1) | function fo(){}
  function e (line 1) | function e(e,t){for(var i=0;i<t.length;i++){var n=t[i];n.enumerable=n.en...
  function e (line 1) | function e(t){for(var i in function(e,t){if(!(e instanceof t))throw new ...
  function e (line 1) | function e(t){var i=this;for(var n in function(e,t){if(!(e instanceof t)...
  function jl (line 1) | function jl(){}
  function e (line 1) | function e(e,t){for(var i=0;i<t.length;i++){var n=t[i];n.enumerable=n.en...
  function e (line 1) | function e(t,i,n){!function(e,t){if(!(e instanceof t))throw new TypeErro...
  function e (line 1) | function e(t,i){!function(e,t){if(!(e instanceof t))throw new TypeError(...
  function e (line 1) | function e(t){for(var i in function(e,t){if(!(e instanceof t))throw new ...

FILE: static/kj/httpVueLoader.js
  function process (line 36) | function process() {
  function StyleContext (line 129) | function StyleContext(component, elt) {
  function ScriptContext (line 182) | function ScriptContext(component, elt) {
  function TemplateContext (line 222) | function TemplateContext(component, elt) {
  function Component (line 339) | function Component(name) {
  function identity (line 348) | function identity(value) {
  function parseComponentURL (line 353) | function parseComponentURL(url) {
  function resolveURL (line 362) | function resolveURL(baseURL, url) {
  function httpVueLoader (line 471) | function httpVueLoader(url, name) {

FILE: static/kj/layer/layer.js
  function e (line 2) | function e(){var e=a.cancel&&a.cancel(t.index,n);e===!1||r.close(t.index)}
  function o (line 2) | function o(e,t,i){var n=new Image;return n.src=e,n.complete?t(n):(n.onlo...

FILE: static/kj/upload-util.js
  function startUploadImage (line 98) | function startUploadImage(file, successCB) {
  function startUploadVideo (line 104) | function startUploadVideo(file, successCB) {
  function startUploadAudio (line 108) | function startUploadAudio(file, successCB) {
  function startUploadApk (line 112) | function startUploadApk(file, successCB) {
  function startUpload (line 117) | function startUpload(file, url, successCB) {
  function get_suffix (line 171) | function get_suffix(filename) {
  function xhrOnProgress (line 180) | function xhrOnProgress(fun) {
  function randomString (line 198) | function randomString(len) {
  function show_jdt (line 212) | function show_jdt() {
  function close_jdt (line 246) | function close_jdt() {
  function set_jdt_value (line 259) | function set_jdt_value(value) {

FILE: static/sa.js
  function solveLayerBug (line 409) | function solveLayerBug(index) {
  function smoothscroll (line 830) | function smoothscroll(){
Condensed preview — 77 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,321K chars).
[
  {
    "path": "LICENSE",
    "chars": 1060,
    "preview": "MIT License\n\nCopyright (c) 2020 省长\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this"
  },
  {
    "path": "README.md",
    "chars": 11755,
    "preview": "<p align=\"center\">\n    <img alt=\"logo\" src=\"https://oss.dev33.cn/sa-admin/admin-logo.png\" width=\"150\" height=\"150\" style"
  },
  {
    "path": "index.html",
    "chars": 2670,
    "preview": "<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<title></title>\n\t\t<meta charset=\"utf-8\">\n\t\t<link rel=\"shortcut icon\" type=\"image/x-icon"
  },
  {
    "path": "login.html",
    "chars": 4349,
    "preview": "<!DOCTYPE html>\n<html lang=\"zh\">\n\t<head>\n\t\t<meta charset=\"utf-8\">\n\t\t<title>登录</title>\n\t\t<meta name=\"description\" content"
  },
  {
    "path": "main.html",
    "chars": 529,
    "preview": "<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<meta charset=\"UTF-8\">\n\t\t<title></title>\n\t\t<link rel=\"stylesheet\" href=\"static/sa.css\" "
  },
  {
    "path": "sa-frame/com/sa-info.vue",
    "chars": 7894,
    "preview": "<template>\n\t<!-- 自定义slot -->\n\t<div class=\"c-item\" :class=\"{br: br}\" v-if=\"$slots.default\">\n\t\t<label class=\"c-label\" v-if"
  },
  {
    "path": "sa-frame/com/sa-item.vue",
    "chars": 17135,
    "preview": "<template>\n\t<!-- 自定义slot -->\n\t<div class=\"c-item\" :class=\"{br: br}\" v-if=\"$slots.default && type != 'fast-btn'\">\n\t\t<labe"
  },
  {
    "path": "sa-frame/com/sa-td.vue",
    "chars": 9765,
    "preview": "<template>\n\t<!-- 自定义slot -->\n\t<el-table-column v-if=\"$slots.default || $scopedSlots.default\" :label=\"name\" :width=\"width"
  },
  {
    "path": "sa-frame/index/admin-util.js",
    "chars": 2350,
    "preview": "// ======================== 一些工具方法 ======================== \n\nvar sa_admin_code_util = {\n\t// 删除数组某个元素\n\tarrayDelete: func"
  },
  {
    "path": "sa-frame/index/index.css",
    "chars": 2810,
    "preview": "*{margin: 0; padding: 0; }\nhtml,body{height: 100%; background-color: #EEE;} \nbody{height: 100vh;background-color: #EEE;b"
  },
  {
    "path": "sa-frame/index/index.js",
    "chars": 12163,
    "preview": "// 首页 \nvar homeTab = {\n\tid: 'home',\t// 唯一标识 \n\tname: '首页',\n\turl: 'main.html',\t// 页面地址 \n\tisNeedLoad: false,\t\t// 标注:是否需要此刻加"
  },
  {
    "path": "sa-frame/index/theme.css",
    "chars": 6631,
    "preview": "/* 样式调整为继承父级 */\n.nav-left .el-submenu__title i,\n.nav-left .el-menu-item i,\n.nav-right-1 .el-dropdown,\n.tab-title:hover ."
  },
  {
    "path": "sa-frame/login/app.js",
    "chars": 2193,
    "preview": "/* -----------------------------------------------\n/* How to use? : Check the GitHub README\n/* -------------------------"
  },
  {
    "path": "sa-frame/login/reset.css",
    "chars": 1436,
    "preview": "@charset \"utf-8\";\n/* CSS Document */\n/*Reset*/\n*{box-sizing:content-box;}\na:hover, a:focus{text-decoration:none;}\nbody,"
  },
  {
    "path": "sa-frame/login/style.css",
    "chars": 5593,
    "preview": "@charset \"utf-8\";\n/* CSS Document */\n/*Reset*/\n*{box-sizing:content-box;}\na:hover, a:focus{text-decoration:none;}\nbody,d"
  },
  {
    "path": "sa-frame/menu-list.js",
    "chars": 9234,
    "preview": "// 一个菜单可以包括的所有属性 \n// {\n// \tid: '12345',\t\t// 菜单id, 必须唯一\n// \tname: '用户中心',\t\t// 菜单名称, 同时也是tab选项卡上显示的名称\n// \ticon: 'el-icon-u"
  },
  {
    "path": "sa-frame/nav/com-add-tab.vue",
    "chars": 1638,
    "preview": "<template>\n\t<!-- 双击弹出的窗口 -->\n\t<div class=\"at-form-fox\" style=\"width: 0px; height: 0px; overflow: hidden; \">\n\t\t<div class"
  },
  {
    "path": "sa-frame/nav/com-right-menu.vue",
    "chars": 4651,
    "preview": "<template>\n\t<!-- 鼠标右键弹出的盒子 -->\n\t<!-- 【向下展开动画,坐标平移动画】二者只可得其一 -->\n\t<div class=\"right-box\" :style=\"rightStyle\" v-show=\"righ"
  },
  {
    "path": "sa-frame/nav/nav-logo.vue",
    "chars": 614,
    "preview": "<!-- 左上:logo部分 -->\n<template>\n\t<div class=\"com-logo-box\" :title=\"$root.title\" @click=\"$root.showHome()\">\n\t\t<img :src=\"$r"
  },
  {
    "path": "sa-frame/nav/nav-menu-bar.vue",
    "chars": 4974,
    "preview": "<template>\n\t<!-- 左下:菜单栏 -->\n\t<div class=\"menu-box-1\">\n\t\t<div class=\"menu-box-2\">\n\t\t\t<!-- \n\t\t\t\t菜单:\n\t\t\t\t\tunique-opened = 是"
  },
  {
    "path": "sa-frame/nav/nav-tab-bar.vue",
    "chars": 5621,
    "preview": "<template>\n\t<!-- 右边,第二行:tab栏 -->\n\t<div class=\"towards-box\">\n\t\t<div class=\"towards-left\" @click=\"scrollToLeft()\" title=\"向"
  },
  {
    "path": "sa-frame/nav/nav-tool-bar.vue",
    "chars": 10986,
    "preview": "<!-- 右边第一行,工具栏 -->\n<template>\n\t<div class=\"tools-panel\">\n\t\t<div class=\"tools-left\">\n\t\t\t<span title=\"折叠菜单\" class=\"tool-fo"
  },
  {
    "path": "sa-frame/nav/nav-view-vessel.vue",
    "chars": 1761,
    "preview": "<template>\n\t<div class=\"view-vessel\">\n\t\t<div class=\"a-view\" v-for=\"tab in $root.viewList\" :key=\"tab.id\" :class=\"tab == $"
  },
  {
    "path": "sa-frame/sa-code.js",
    "chars": 2740,
    "preview": "// 在使用时,不建议你直接魔改模板的代码,以免在运行时出现意外bug,而是在本文件中根据模板的提供的API,来适应你的业务逻辑 \n// ....\n\n\n\n// ================================= 示例:一些基"
  },
  {
    "path": "sa-view/article/art-add.html",
    "chars": 6099,
    "preview": "<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<title>文章-添加/修改</title>\n\t\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=U"
  },
  {
    "path": "sa-view/article/art-list.html",
    "chars": 5889,
    "preview": "<!DOCTYPE html>\n<html>\n\t<head>\n\t    <title>帖子列表</title>\n\t    <meta charset=\"utf-8\">\n\t\t<meta name=\"viewport\" content=\"wid"
  },
  {
    "path": "sa-view/article/data-list.js",
    "chars": 1903,
    "preview": "var mockData = {\n\tcode: 200,\n\tmsg: 'ok',\n\tdata: [{\n\t\t\t\"id\": 20,\n\t\t\t\"title\": \"那个清晨\",\n\t\t\t\"content\": \"那个清晨一大早,便被母亲叫起。我有些不满,"
  },
  {
    "path": "sa-view/case/mock-data-list.js",
    "chars": 2042,
    "preview": "var mockDataList = {\n\tcode: 200,\n\tmsg: 'ok',\n\tdata: [\n\t\t{\n\t\t\tid: 101, \n\t\t\tname: '苹果', \n\t\t\ticon: 'el-icon-apple',\n\t\t\tavat"
  },
  {
    "path": "sa-view/case/mock-joptions.js",
    "chars": 617,
    "preview": "var mockJoptions = [\t\t// 级联选项  \n\t{\n\t\tvalue: 'v1', \n\t\tlabel: '选项1',\n\t\tchildren: [\n\t\t\t{\n\t\t\t\tvalue: 'v1-1', \n\t\t\t\tlabel: '选项"
  },
  {
    "path": "sa-view/case/query-p-case.html",
    "chars": 6867,
    "preview": "<!DOCTYPE html>\n<html>\n\t<head>\n\t    <title>各种查询参数示例</title>\n\t    <meta charset=\"utf-8\">\n\t\t<meta name=\"viewport\" content="
  },
  {
    "path": "sa-view/case/query-table-case-add.html",
    "chars": 3824,
    "preview": "<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<title>表格显示示例-修改</title>\n\t\t<meta charset=\"utf-8\">\n\t\t<meta name=\"viewport\" content=\"widt"
  },
  {
    "path": "sa-view/case/query-table-case.html",
    "chars": 6429,
    "preview": "<!DOCTYPE html>\n<html>\n\t<head>\n\t    <title>各种表格显示示例</title>\n\t    <meta charset=\"utf-8\">\n\t\t<meta name=\"viewport\" content="
  },
  {
    "path": "sa-view/case/submit-form.html",
    "chars": 4688,
    "preview": "<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<title>表单提交示例</title>\n\t\t<meta charset=\"utf-8\">\n\t\t<meta name=\"viewport\" content=\"width=d"
  },
  {
    "path": "sa-view/cfg/system-cfg.html",
    "chars": 3126,
    "preview": "<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<title>角色列表</title>\n\t\t<meta charset=\"utf-8\">\n\t\t<meta name=\"viewport\" content=\"width=dev"
  },
  {
    "path": "sa-view/cfg/xxx.vue",
    "chars": 975,
    "preview": "<template>\n\t<div>\n\t\t<div style=\"padding: 60px; text-align: center;\">\n\t\t\t<h1 style=\"height: 100px;\">.vue组件 (此页面由 .vue 文件提"
  },
  {
    "path": "sa-view/console/com-chart-1.vue",
    "chars": 2111,
    "preview": "<!-- 统计图1 -->\n<template>\n\t<div class=\"echarts-div\" id='bar-chart' ref=\"bar-chart\"></div>\n</template>\n\n<script>\n\tmodule.e"
  },
  {
    "path": "sa-view/console/com-chart-2.vue",
    "chars": 2764,
    "preview": "<!-- 统计图2 -->\n<template>\n\t<div class=\"echarts-div\" id='pic-chart' ref='pic-chart'></div>\n</template>\n\n<script>\n\tmodule.e"
  },
  {
    "path": "sa-view/console/com-chart-3.vue",
    "chars": 2369,
    "preview": "<!-- 统计图 3 -->\n<template>\n\t<div class=\"echarts-div\" id='line-chart' ref='line-chart'></div>\n</template>\n\n<script>\n\tmodul"
  },
  {
    "path": "sa-view/console/com-intro.vue",
    "chars": 1345,
    "preview": "<template>\n\t<div>\n\t\t<el-alert type=\"success\" :closable=\"false\" title=\"基础\">\n\t\t\t架构:基于iframe,无后台代码,纯html模板,可方便的适配任何后端语言 <br"
  },
  {
    "path": "sa-view/console/com-origin.vue",
    "chars": 1311,
    "preview": "<template>\n\t<div>\n\t\t<el-alert type=\"success\" :closable=\"false\" title=\"缘起\" style=\"border-width: 0px;\">\n\t\t\t<p>虽然已经用过不少优秀的后"
  },
  {
    "path": "sa-view/console/com-sta-data.vue",
    "chars": 3346,
    "preview": "<!-- 第一行统计数据 -->\n<template>\n\t<el-row :gutter=\"14\">\n\t\t<el-col :lg=\"4\" :sm=\"8\" :xs=\"24\">\n\t\t\t<div class=\"sa-wnk\">\n\t\t\t\t<img "
  },
  {
    "path": "sa-view/console/com-stack.vue",
    "chars": 2857,
    "preview": "<!-- 第一行统计数据 -->\n<template>\n\t<div>\n\t\t<div class=\"btn-box\">\n\t\t\t<el-popover placement=\"top-start\" trigger=\"hover\">\n\t\t\t    "
  },
  {
    "path": "sa-view/console/com-update-log.vue",
    "chars": 11641,
    "preview": "<!-- 第一行统计数据 -->\n<template>\n\t<el-timeline>\n\t\t<!-- ---------- 一个版本 第41个----------- -->\n\t\t<el-timeline-item timestamp=\"v1."
  },
  {
    "path": "sa-view/console/console-main.html",
    "chars": 3841,
    "preview": "<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<title>Sa-Admin 控制台</title>\n\t\t<meta charset=\"utf-8\">\n\t\t<meta name=\"viewport\" content=\"w"
  },
  {
    "path": "sa-view/error-page/403.html",
    "chars": 1258,
    "preview": "<!DOCTYPE html>\n<html>\n\t<head>\n        <title>403</title>\n    \t<meta charset=\"utf-8\">\n    \t<meta name=\"viewport\" content"
  },
  {
    "path": "sa-view/error-page/404.html",
    "chars": 1255,
    "preview": "<!DOCTYPE html>\n<html>\n\t<head>\n        <title>404</title>\n    \t<meta charset=\"utf-8\">\n    \t<meta name=\"viewport\" content"
  },
  {
    "path": "sa-view/error-page/500.html",
    "chars": 1255,
    "preview": "<!DOCTYPE html>\n<html>\n\t<head>\n        <title>500</title>\n    \t<meta charset=\"utf-8\">\n    \t<meta name=\"viewport\" content"
  },
  {
    "path": "sa-view/home/mock-data-list.js",
    "chars": 1179,
    "preview": "// 数据 \nvar mockDataList = {\n\tcode: 200,\n\tmsg: 'ok',\n\tdata: [{\n\t\t\"id\": 1,\n\t\t\"title\": \"壁纸1\",\n\t\t\"img_src\": \"https://color-t"
  },
  {
    "path": "sa-view/home/swiper-add.html",
    "chars": 4430,
    "preview": "<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<title>商品分类表-添加/修改</title>\n\t\t<meta http-equiv=\"Content-Type\" content=\"text/html; charse"
  },
  {
    "path": "sa-view/home/swiper-list.html",
    "chars": 4837,
    "preview": "<!DOCTYPE html>\n<html>\n\t<head>\n\t    <title>各种查询参数示例</title>\n\t    <meta charset=\"utf-8\">\n\t\t<meta name=\"viewport\" content="
  },
  {
    "path": "sa-view/role/menu-list.html",
    "chars": 2279,
    "preview": "<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<title>菜单预览</title>\n\t\t<meta charset=\"utf-8\">\n\t\t<meta name=\"viewport\" content=\"width=dev"
  },
  {
    "path": "sa-view/role/menu-setup.html",
    "chars": 4326,
    "preview": "<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<title>权限分配</title>\n\t\t<meta charset=\"utf-8\">\n\t\t<meta name=\"viewport\" content=\"width=dev"
  },
  {
    "path": "sa-view/role/mock-data.js",
    "chars": 444,
    "preview": "// 模拟数据\nvar mockData = {\n\tcode: 200,\n\tmsg: 'ok',\n\tdata: [{\n\t\t\"id\": 1,\n\t\t\"role_name\": \"普通用户\",\n\t\t\"role_info\": \"普通用户\",\n\t\t\"i"
  },
  {
    "path": "sa-view/role/role-add.html",
    "chars": 3396,
    "preview": "<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<title>商品分类表-添加/修改</title>\n\t\t<meta http-equiv=\"Content-Type\" content=\"text/html; charse"
  },
  {
    "path": "sa-view/role/role-list.html",
    "chars": 4651,
    "preview": "<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<title>角色列表</title>\n\t\t<meta charset=\"utf-8\">\n\t\t<meta name=\"viewport\" content=\"width=dev"
  },
  {
    "path": "sa-view/sa-doc/check-per.md",
    "chars": 1518,
    "preview": "# sa-admin 集成鉴权功能 \n\n\n#### 1、首先在登录时,写入当前会话所具有的权限码集合\n```\n\tvar arr = ['1', '2', '3', 'a', 'b', 'c'];\t\t// 一般由后端提供接口返回当前会话所具有"
  },
  {
    "path": "sa-view/sa-doc/sa-doc.html",
    "chars": 1639,
    "preview": "<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<meta charset=\"utf-8\">\n\t\t<title>文档</title>\n\t\t<style type=\"text/css\">\n\t\t\tbody{background"
  },
  {
    "path": "sa-view/sa-doc/start-up.md",
    "chars": 2992,
    "preview": "# sa-admin 使用步骤 <span style=\"font-size: 14px;\">五分钟快速上手</span>\n\n#### 1、获取 源码\n> - 你有两种方式获取源码\n> - 通过gitee、或github获取源码\n> - ["
  },
  {
    "path": "sa-view/ser-goods/ser-goods-add.html",
    "chars": 18838,
    "preview": "\n<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<title>商品表-添加/修改</title>\n\t\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset"
  },
  {
    "path": "sa-view/sys-type/moke-data.js",
    "chars": 1561,
    "preview": "var getMockData = function(name) {\n\tname = name || \"\";\n\tvar dataList = [\n\t\t{\n\t\t\t\"id\": 101,\n\t\t\t\"name\": \"虚拟物品\",\n\t\t\t\"icon\":"
  },
  {
    "path": "sa-view/sys-type/sys-type-add.html",
    "chars": 3682,
    "preview": "<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<title>商品分类表-添加/修改</title>\n\t\t<meta http-equiv=\"Content-Type\" content=\"text/html; charse"
  },
  {
    "path": "sa-view/sys-type/sys-type-info.html",
    "chars": 2419,
    "preview": "<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<title>商品分类-详情</title>\n\t\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UT"
  },
  {
    "path": "sa-view/sys-type/sys-type-list.html",
    "chars": 4845,
    "preview": "<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<title>商品分类-列表</title>\n\t\t<meta charset=\"utf-8\">\n\t\t<meta name=\"viewport\" content=\"width="
  },
  {
    "path": "sa-view/user/data-list.js",
    "chars": 10050,
    "preview": "// 模拟数据\nvar mockData = {\n\tcode: 200,\n\tmsg: 'ok',\n\tdata: [{\n\t\t\t\"id\": 12001,\n\t\t\t\"username\": \"省长\",\n\t\t\t\"avatar\": \"https://co"
  },
  {
    "path": "sa-view/user/user-add.html",
    "chars": 2501,
    "preview": "<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<title>用户添加</title>\n\t\t<meta charset=\"utf-8\">\n\t\t<meta name=\"viewport\" content=\"width=dev"
  },
  {
    "path": "sa-view/user/user-chart.html",
    "chars": 8299,
    "preview": "<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<title>用户注册统计</title>\n\t\t<meta charset=\"utf-8\">\n\t\t<meta name=\"viewport\" content=\"width=d"
  },
  {
    "path": "sa-view/user/user-list.html",
    "chars": 7861,
    "preview": "<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<title>用户列表</title>\n\t\t<meta charset=\"utf-8\">\n\t\t<meta name=\"viewport\" content=\"width=dev"
  },
  {
    "path": "static/kj/element-ui/index.js",
    "chars": 566607,
    "preview": "!function(e,t){\"object\"==typeof exports&&\"object\"==typeof module?module.exports=t(require(\"vue\")):\"function\"==typeof def"
  },
  {
    "path": "static/kj/element-ui/theme-chalk/index.css",
    "chars": 232460,
    "preview": "@charset \"UTF-8\";.el-pagination--small .arrow.disabled,.el-table .hidden-columns,.el-table td.is-hidden>*,.el-table th.i"
  },
  {
    "path": "static/kj/httpVueLoader.js",
    "chars": 10414,
    "preview": "(function umd(root,factory){\n\tif(typeof module==='object' && typeof exports === 'object' )\n\t\tmodule.exports=factory()\n\te"
  },
  {
    "path": "static/kj/layer/layer.js",
    "chars": 22104,
    "preview": "/*! layer-v3.1.1 Web弹层组件 MIT License  http://layer.layui.com/  By 贤心 */\n ;!function(e,t){\"use strict\";var i,n,a=e.layui&"
  },
  {
    "path": "static/kj/layer/mobile/layer.js",
    "chars": 3290,
    "preview": "/*! layer mobile-v2.0.0 Web弹层组件 MIT License  http://layer.layui.com/mobile  By 贤心 */\n ;!function(e){\"use strict\";var t=d"
  },
  {
    "path": "static/kj/layer/mobile/need/layer.css",
    "chars": 5260,
    "preview": ".layui-m-layer{position:relative;z-index:19891014}.layui-m-layer *{-webkit-box-sizing:content-box;-moz-box-sizing:conten"
  },
  {
    "path": "static/kj/layer/theme/default/layer.css",
    "chars": 14367,
    "preview": ".layui-layer-imgbar,.layui-layer-imgtit a,.layui-layer-tab .layui-layer-title span,.layui-layer-title{text-overflow:elli"
  },
  {
    "path": "static/kj/upload-util.js",
    "chars": 7495,
    "preview": "// ======================= upload-util.js (纯前端实现,blob版本) 公共方法 ===========================\n// 依赖库:jquery   \n// 本代码更新于:201"
  },
  {
    "path": "static/sa.css",
    "chars": 8648,
    "preview": "/* \n\t更新于2022-2-16 优化 el-alert 样式\n */\n\n/** 公共css */\n*{margin: 0px;padding: 0px;}\nhtml{font-size: 10px; height: 100%;}\nbod"
  },
  {
    "path": "static/sa.js",
    "chars": 30733,
    "preview": "// =========================== sa对象封装一系列工具方法 ===========================  \nvar sa = {\n\tversion: '2.4.3',\n\tupdate_time: '"
  }
]

About this extraction

This page contains the full source code of the shengzhang666/sa-admin GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 77 files (1.1 MB), approximately 378.6k tokens, and a symbol index with 110 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!