├── static ├── .gitkeep ├── test.css └── config.js ├── .eslintignore ├── test ├── unit │ ├── setup.js │ ├── .eslintrc │ ├── specs │ │ └── HelloWorld.spec.js │ └── jest.conf.js └── e2e │ ├── specs │ └── test.js │ ├── custom-assertions │ └── elementCount.js │ ├── nightwatch.conf.js │ └── runner.js ├── 20180117170917.png ├── 20180117171004.png ├── 20180117175701.png ├── src ├── assets │ ├── logo.ai │ ├── logo.png │ ├── user.jpg │ ├── logo-img.png │ ├── font-awesome-4.7.0 │ │ ├── fonts │ │ │ ├── FontAwesome.otf │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.ttf │ │ │ ├── fontawesome-webfont.woff │ │ │ └── fontawesome-webfont.woff2 │ │ └── HELP-US-OUT.txt │ └── css │ │ ├── reset.css │ │ ├── scrollbar.css │ │ └── main.css ├── m │ ├── box │ │ ├── index.js │ │ └── box.vue │ ├── vcrop │ │ └── index.js │ ├── container │ │ ├── row.vue │ │ ├── index.js │ │ ├── container.vue │ │ ├── README.md │ │ ├── col.vue │ │ └── container.less │ ├── input │ │ ├── index.js │ │ ├── README.md │ │ ├── input.less │ │ └── input.vue │ ├── alert │ │ ├── index.js │ │ ├── README.md │ │ └── alert.vue │ ├── loader │ │ ├── index.js │ │ └── loader.js │ ├── switch │ │ ├── index.js │ │ ├── README.md │ │ ├── switch.vue │ │ └── switch.less │ ├── loading │ │ ├── index.js │ │ ├── README.md │ │ └── loading.vue │ ├── back-top │ │ ├── index.js │ │ ├── README.md │ │ ├── back-top.css │ │ └── back-top.vue │ ├── keyboard │ │ ├── index.js │ │ ├── package.json │ │ ├── README.md │ │ ├── pagination.js │ │ └── dict-helper.js │ ├── button │ │ ├── button-group.vue │ │ ├── index.js │ │ ├── README.md │ │ ├── button.vue │ │ └── btn.less │ ├── number-keyboard │ │ ├── index.js │ │ ├── README.md │ │ └── number-keyboard.vue │ ├── list-group │ │ ├── list-group-item.vue │ │ ├── index.js │ │ └── list-group.vue │ ├── dropdown │ │ ├── dropdown-panel.vue │ │ ├── index.js │ │ ├── README.md │ │ ├── dropdown-item.vue │ │ ├── dropdown.less │ │ └── dropdown.vue │ ├── navbar │ │ ├── nav.vue │ │ ├── index.js │ │ ├── navbar-brand.vue │ │ ├── navbar.vue │ │ ├── README.md │ │ ├── nav-item.vue │ │ └── navbar.less │ ├── checkbox │ │ ├── index.js │ │ ├── checkbox-group.vue │ │ ├── README.md │ │ ├── checkbox.vue │ │ └── checkbox.less │ ├── sticky │ │ └── sticky.vue │ └── context-menu │ │ ├── index.js │ │ └── context-menu.vue ├── components │ ├── vmenu │ │ ├── index.js │ │ ├── README.md │ │ ├── chunk.vue │ │ └── menu.vue │ ├── app-footer.vue │ ├── theme.js │ ├── menus.js │ ├── app-side.vue │ ├── menu-list.vue │ ├── app-view.vue │ └── app-header.vue ├── App.vue ├── store │ ├── index.js │ ├── modules │ │ ├── editMovie.js │ │ ├── count.js │ │ ├── addMovie.js │ │ ├── login.js │ │ ├── movie.js │ │ ├── search.js │ │ ├── admin.js │ │ └── members.js │ └── user.js ├── utils │ ├── index.js │ └── img-cutter.js ├── pages │ ├── c-loading.vue │ ├── about.vue │ ├── 404.vue │ ├── 500.vue │ ├── c-keyboard.vue │ ├── search-user.vue │ ├── home.vue │ ├── search-movie.vue │ ├── editPwd.vue │ ├── login.vue │ ├── addMovie.vue │ └── userlist.vue ├── router │ └── index.js └── main.js ├── 2018-02-08_231542.png ├── config ├── prod.env.js ├── test.env.js ├── dev.env.js ├── version.js └── index.js ├── .editorconfig ├── server ├── db.js ├── index.js └── route.js ├── .gitignore ├── .postcssrc.js ├── .babelrc ├── .eslintrc.js ├── index.html ├── README.md └── package.json /static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | config/*.js 3 | -------------------------------------------------------------------------------- /static/test.css: -------------------------------------------------------------------------------- 1 | body{ 2 | color: red; 3 | } -------------------------------------------------------------------------------- /test/unit/setup.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | Vue.config.productionTip = false 4 | -------------------------------------------------------------------------------- /20180117170917.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pang-xf/vue-admin/HEAD/20180117170917.png -------------------------------------------------------------------------------- /20180117171004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pang-xf/vue-admin/HEAD/20180117171004.png -------------------------------------------------------------------------------- /20180117175701.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pang-xf/vue-admin/HEAD/20180117175701.png -------------------------------------------------------------------------------- /src/assets/logo.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pang-xf/vue-admin/HEAD/src/assets/logo.ai -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pang-xf/vue-admin/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/user.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pang-xf/vue-admin/HEAD/src/assets/user.jpg -------------------------------------------------------------------------------- /2018-02-08_231542.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pang-xf/vue-admin/HEAD/2018-02-08_231542.png -------------------------------------------------------------------------------- /src/assets/logo-img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pang-xf/vue-admin/HEAD/src/assets/logo-img.png -------------------------------------------------------------------------------- /static/config.js: -------------------------------------------------------------------------------- 1 | console.log('load config.js') 2 | 3 | var jQuery = { 4 | name: 'hello' 5 | } 6 | -------------------------------------------------------------------------------- /test/unit/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true 4 | }, 5 | "globals": { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/assets/font-awesome-4.7.0/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pang-xf/vue-admin/HEAD/src/assets/font-awesome-4.7.0/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /src/m/box/index.js: -------------------------------------------------------------------------------- 1 | import MBox from './box' 2 | 3 | MBox.install = function (Vue) { 4 | Vue.component(MBox.name, MBox) 5 | } 6 | 7 | export default MBox 8 | -------------------------------------------------------------------------------- /src/m/vcrop/index.js: -------------------------------------------------------------------------------- 1 | import MCrop from './crop' 2 | 3 | MCrop.install = function (Vue) { 4 | Vue.component(MCrop.name, MCrop) 5 | } 6 | 7 | export default MCrop 8 | -------------------------------------------------------------------------------- /src/assets/font-awesome-4.7.0/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pang-xf/vue-admin/HEAD/src/assets/font-awesome-4.7.0/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /src/assets/font-awesome-4.7.0/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pang-xf/vue-admin/HEAD/src/assets/font-awesome-4.7.0/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /src/assets/font-awesome-4.7.0/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pang-xf/vue-admin/HEAD/src/assets/font-awesome-4.7.0/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /src/m/container/row.vue: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | var version = require('./version') 3 | module.exports = { 4 | NODE_ENV: '"production"', 5 | APP_INFO: JSON.stringify(version) 6 | } 7 | -------------------------------------------------------------------------------- /src/assets/font-awesome-4.7.0/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pang-xf/vue-admin/HEAD/src/assets/font-awesome-4.7.0/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /src/components/vmenu/index.js: -------------------------------------------------------------------------------- 1 | import VMenu from './menu' 2 | 3 | VMenu.install = function (Vue) { 4 | Vue.component(VMenu.name, VMenu) 5 | } 6 | 7 | export default VMenu 8 | -------------------------------------------------------------------------------- /src/m/input/index.js: -------------------------------------------------------------------------------- 1 | import MInput from './input' 2 | 3 | MInput.install = function (Vue) { 4 | Vue.component(MInput.name, MInput) 5 | } 6 | 7 | export default MInput 8 | -------------------------------------------------------------------------------- /src/m/alert/index.js: -------------------------------------------------------------------------------- 1 | import MAlert from './alert' 2 | 3 | 4 | MAlert.install = function (Vue) { 5 | Vue.component(MAlert.name, MAlert) 6 | } 7 | 8 | export default MAlert 9 | -------------------------------------------------------------------------------- /src/m/loader/index.js: -------------------------------------------------------------------------------- 1 | import MLoader from './loader' 2 | 3 | MLoader.install = function (Vue) { 4 | Vue.component(MLoader.name, MLoader) 5 | } 6 | 7 | export default MLoader 8 | -------------------------------------------------------------------------------- /src/m/switch/index.js: -------------------------------------------------------------------------------- 1 | import MSwitch from './switch' 2 | 3 | MSwitch.install = function (Vue) { 4 | Vue.component(MSwitch.name, MSwitch) 5 | } 6 | 7 | export default MSwitch 8 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /src/m/loading/index.js: -------------------------------------------------------------------------------- 1 | import MLoading from './loading' 2 | 3 | MLoading.install = function (Vue) { 4 | Vue.component(MLoading.name, MLoading) 5 | } 6 | 7 | export default MLoading 8 | -------------------------------------------------------------------------------- /src/m/back-top/index.js: -------------------------------------------------------------------------------- 1 | import MBackTop from './back-top' 2 | 3 | MBackTop.install = function (Vue) { 4 | Vue.component(MBackTop.name, MBackTop) 5 | } 6 | 7 | export default MBackTop 8 | -------------------------------------------------------------------------------- /config/test.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const devEnv = require('./dev.env') 4 | 5 | module.exports = merge(devEnv, { 6 | NODE_ENV: '"testing"' 7 | }) 8 | -------------------------------------------------------------------------------- /src/m/keyboard/index.js: -------------------------------------------------------------------------------- 1 | import MKeyboard from './keyboard' 2 | 3 | MKeyboard.install = function (Vue) { 4 | Vue.component(MKeyboard.name, MKeyboard) 5 | } 6 | 7 | export default MKeyboard 8 | -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const prodEnv = require('./prod.env') 4 | 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"' 7 | }) 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /server/db.js: -------------------------------------------------------------------------------- 1 | // 数据库连接配置 2 | module.exports = { 3 | mysql: { 4 | host: 'localhost', 5 | user: 'root', 6 | password: '123456', 7 | database: 'moviedb', 8 | port: '3306' 9 | } 10 | } -------------------------------------------------------------------------------- /src/m/button/button-group.vue: -------------------------------------------------------------------------------- 1 | 6 | 11 | -------------------------------------------------------------------------------- /src/m/number-keyboard/index.js: -------------------------------------------------------------------------------- 1 | import MNumberKeyboard from './number-keyboard' 2 | 3 | MNumberKeyboard.install = function (Vue) { 4 | Vue.component(MNumberKeyboard.name, MNumberKeyboard) 5 | } 6 | 7 | export default MNumberKeyboard 8 | -------------------------------------------------------------------------------- /src/m/list-group/list-group-item.vue: -------------------------------------------------------------------------------- 1 | 6 | 11 | -------------------------------------------------------------------------------- /src/m/dropdown/dropdown-panel.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /src/m/switch/README.md: -------------------------------------------------------------------------------- 1 | ## Switch 开关 2 | 3 | ```js 4 | 5 | 6 | ``` 7 | # Switch Attributes 8 | 9 | + value Boolean 当前状态值 10 | + type 主题类型primary,success,danger,warning 11 | + size 大小 lg,sm 12 | + invert 反相主题 13 | + name input标签的name字段# 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | test/unit/coverage 7 | test/e2e/reports 8 | selenium-debug.log 9 | 10 | # Editor directories and files 11 | .idea 12 | .vscode 13 | *.suo 14 | *.ntvs* 15 | *.njsproj 16 | *.sln 17 | -------------------------------------------------------------------------------- /src/m/back-top/README.md: -------------------------------------------------------------------------------- 1 | ## BackTop 2 | 3 | ```html 4 | 5 | ``` 6 | 7 | ### BackTop Attributes 8 | 9 | + body-id 滚动容器id 10 | + offset-top 滚动距离顶部位置x时才会显示BackTop按钮,默认100px 11 | 12 | ### BackTop Events 13 | 14 | + scroll-top 滚动到顶部事件 15 | -------------------------------------------------------------------------------- /src/m/button/index.js: -------------------------------------------------------------------------------- 1 | import MButton from './button' 2 | import MButtonGroup from './button-group' 3 | 4 | 5 | MButton.install = function (Vue) { 6 | Vue.component(MButton.name, MButton) 7 | Vue.component(MButtonGroup.name, MButtonGroup) 8 | } 9 | 10 | export default MButton 11 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | "postcss-import": {}, 6 | "postcss-url": {}, 7 | // to edit target browsers: use "browserslist" field in package.json 8 | "autoprefixer": {} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/m/navbar/nav.vue: -------------------------------------------------------------------------------- 1 | 6 | 17 | -------------------------------------------------------------------------------- /src/m/checkbox/index.js: -------------------------------------------------------------------------------- 1 | import MCheckbox from './checkbox' 2 | import MCheckboxGroup from './checkbox-group' 3 | 4 | 5 | MCheckbox.install = function (Vue) { 6 | Vue.component(MCheckbox.name, MCheckbox) 7 | Vue.component(MCheckboxGroup.name, MCheckboxGroup) 8 | } 9 | 10 | export default MCheckbox 11 | -------------------------------------------------------------------------------- /src/m/list-group/index.js: -------------------------------------------------------------------------------- 1 | import MListGroup from './list-group' 2 | import MListGroupItem from './list-group-item' 3 | 4 | MListGroup.install = function (Vue) { 5 | Vue.component(MListGroup.name, MListGroup) 6 | Vue.component(MListGroupItem.name, MListGroupItem) 7 | } 8 | 9 | export default MListGroup 10 | -------------------------------------------------------------------------------- /src/m/loading/README.md: -------------------------------------------------------------------------------- 1 | ## Loading 加载图标 2 | 3 | 加载等待图标组件 4 | 5 | ```html 6 | 7 | 8 | ``` 9 | 10 | ## Loading Attributes 11 | 12 | + width 尺寸大小,字符串或者数字,默认40 13 | + type 类型,字符串,可选值:wipe 14 | + color 路径颜色,字符串 15 | + bg-color 背景路径颜色,字符串或boolean,默认true -------------------------------------------------------------------------------- /src/m/back-top/back-top.css: -------------------------------------------------------------------------------- 1 | .m-back-top{ 2 | width: 30px; 3 | height: 30px; 4 | position: fixed; 5 | right: 30px; 6 | bottom: 50px; 7 | background: rgba(40,44,52, 0.7); 8 | border-radius: 4px; 9 | text-align: center; 10 | line-height: 30px; 11 | color: #fff; 12 | font-weight: 100; 13 | } 14 | -------------------------------------------------------------------------------- /src/m/checkbox/checkbox-group.vue: -------------------------------------------------------------------------------- 1 | 6 | 16 | -------------------------------------------------------------------------------- /src/m/container/index.js: -------------------------------------------------------------------------------- 1 | import MContainer from './container' 2 | import MRow from './row' 3 | import Mcol from './col' 4 | 5 | 6 | MContainer.install = function (Vue) { 7 | Vue.component(MContainer.name, MContainer) 8 | Vue.component(MRow.name, MRow) 9 | Vue.component(Mcol.name, Mcol) 10 | } 11 | 12 | export default MContainer 13 | -------------------------------------------------------------------------------- /src/assets/font-awesome-4.7.0/HELP-US-OUT.txt: -------------------------------------------------------------------------------- 1 | I hope you love Font Awesome. If you've found it useful, please do me a favor and check out my latest project, 2 | Fort Awesome (https://fortawesome.com). It makes it easy to put the perfect icons on your website. Choose from our awesome, 3 | comprehensive icon sets or copy and paste your own. 4 | 5 | Please. Check it out. 6 | 7 | -Dave Gandy 8 | -------------------------------------------------------------------------------- /src/m/container/container.vue: -------------------------------------------------------------------------------- 1 | 9 | 17 | 20 | -------------------------------------------------------------------------------- /src/m/alert/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Alert 3 | 4 | 用于页面提示信息的组件。 5 | 6 | ``` 7 | 8 | ``` 9 | 10 | ## Alert Attributes 11 | 12 | + type 情景类型 info,primary,success,danger,warning 13 | + closable Boolean;默认false 14 | + message String 标题 15 | + description String 描述 16 | + hide Boolean 关闭 17 | 18 | ## Alert Events 19 | 20 | + close 关闭触发 21 | + show 显示触发(初始化时不会触发) 22 | -------------------------------------------------------------------------------- /config/version.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const pkg = require(path.resolve(process.cwd(), 'package.json')) 3 | 4 | module.exports = { 5 | projectName: pkg.name, 6 | version: pkg.version, 7 | description: pkg.description, 8 | author: pkg.author, 9 | appName: pkg.app && pkg.app.name || pkg.appName, 10 | dependencies: pkg.dependencies, 11 | engines: pkg.engines, 12 | license: pkg.license 13 | } -------------------------------------------------------------------------------- /src/m/box/box.vue: -------------------------------------------------------------------------------- 1 | 6 | 11 | 23 | -------------------------------------------------------------------------------- /src/m/input/README.md: -------------------------------------------------------------------------------- 1 | ## Input 2 | 3 | Input 输入框。 4 | 5 | ```html 6 | 7 | ``` 8 | 9 | 10 | 11 | ### Input Attributes 12 | 13 | + disabled 禁用状态 14 | + type 类型input, text, password, textarea 15 | + size 大小 max, large, 默认, samll, mini 16 | + theme danger,success 17 | + placeholder 18 | + block 块 19 | + maxHeight 20 | + maxWidth 21 | + minHeight 22 | + minWidth 23 | + width 24 | + height 25 | -------------------------------------------------------------------------------- /src/m/number-keyboard/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## NumberKeyboard 3 | 4 | 英文+数字+常用符号虚拟键盘 5 | 6 | ```html 7 | 8 | ``` 9 | 10 | ## NumberKeyboard Attributes 11 | 12 | + size 尺寸,Number;默认320 13 | + disabledKeys 禁用键,Array 14 | 15 | ## NumberKeyboard Event 16 | 17 | 18 | + enter 按下enter键 19 | + space 按下空格键 20 | + key 按下输入键,回调参数为键值 21 | -------------------------------------------------------------------------------- /test/unit/specs/HelloWorld.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import HelloWorld from '@/components/HelloWorld' 3 | 4 | describe('HelloWorld.vue', () => { 5 | it('should render correct contents', () => { 6 | const Constructor = Vue.extend(HelloWorld) 7 | const vm = new Constructor().$mount() 8 | expect(vm.$el.querySelector('.hello h1').textContent) 9 | .toEqual('Welcome to Your Vue.js App') 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /src/m/dropdown/index.js: -------------------------------------------------------------------------------- 1 | 2 | import Dropdown from './dropdown' 3 | import DropdownPanel from './dropdown-panel' 4 | import DropdownItem from './dropdown-item' 5 | 6 | 7 | const DropdownPlugin = {} 8 | 9 | DropdownPlugin.install = function (Vue) { 10 | Vue.component(Dropdown.name, Dropdown) 11 | Vue.component(DropdownPanel.name, DropdownPanel) 12 | Vue.component(DropdownItem.name, DropdownItem) 13 | } 14 | 15 | export default DropdownPlugin 16 | -------------------------------------------------------------------------------- /src/m/navbar/index.js: -------------------------------------------------------------------------------- 1 | import MNav from './nav' 2 | import MNavItem from './nav-item' 3 | import MNavbar from './navbar' 4 | import MNavbarBrand from './navbar-brand' 5 | 6 | const MNavPlugin = {} 7 | 8 | MNavPlugin.install = function (Vue) { 9 | Vue.component(MNav.name, MNav) 10 | Vue.component(MNavItem.name, MNavItem) 11 | Vue.component(MNavbar.name, MNavbar) 12 | Vue.component(MNavbarBrand.name, MNavbarBrand) 13 | } 14 | 15 | export default MNavPlugin 16 | 17 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins": ["transform-vue-jsx", "transform-runtime"], 12 | "env": { 13 | "test": { 14 | "presets": ["env", "stage-2"], 15 | "plugins": ["transform-vue-jsx", "transform-es2015-modules-commonjs", "dynamic-import-node"] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/assets/css/reset.css: -------------------------------------------------------------------------------- 1 | .el-breadcrumb .el-breadcrumb__inner{ 2 | color: #4B4D52; 3 | font-weight: 100; 4 | } 5 | .el-breadcrumb .el-breadcrumb__separator{ 6 | margin: 0 5px; 7 | font-weight: 100; 8 | } 9 | .el-table .el-table__header thead th{ 10 | background: #E6EBF5; 11 | color: #3c464e; 12 | } 13 | 14 | #nprogress .bar { 15 | background: #812CB2 !important; 16 | } 17 | 18 | #nprogress .spinner-icon { 19 | border-top-color: #812CB2 !important; 20 | border-left-color: #812CB2 !important; 21 | } 22 | -------------------------------------------------------------------------------- /src/m/navbar/navbar-brand.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 21 | -------------------------------------------------------------------------------- /src/m/navbar/navbar.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 27 | 30 | -------------------------------------------------------------------------------- /src/m/keyboard/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "m-keyboard", 3 | "version": "1.0.0", 4 | "description": "虚拟键盘", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/mengdu/vue-element-admin-tpl/tree/master/src/m/keyboard" 12 | }, 13 | "keywords": [ 14 | "mui", 15 | "m-keyboard", 16 | "keyboard", 17 | "虚拟键盘", 18 | "键盘" 19 | ], 20 | "author": "lanyue", 21 | "license": "MIT" 22 | } 23 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | const routerApi = require('./route'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const bodyParser = require('body-parser'); 5 | const express = require('express'); 6 | const cookieParase = require('cookie-parser'); 7 | const app = express(); 8 | app.use(bodyParser.json()); 9 | app.use(cookieParase()); 10 | app.use(bodyParser.urlencoded({extended: false})); 11 | app.use(express.static(path.join(__dirname,'public'))); 12 | // 后端api路由 13 | app.use('/api', routerApi); 14 | // 监听端口 15 | app.listen(3030); 16 | console.log('success listen at port:3030......'); -------------------------------------------------------------------------------- /src/m/button/README.md: -------------------------------------------------------------------------------- 1 | ## Button 2 | 3 | Button常用的操作按钮。 4 | 5 | ```html 6 | defaut 7 | ``` 8 | 9 | **按钮组:** 10 | 11 | ```html 12 | 13 | 首页 14 | 热门 15 | 专栏 16 | 17 | ``` 18 | 19 | ### Button Attributes 20 | 21 | + disabled 禁用状态 22 | + type 主题 info,primary,warning,danger,success 23 | + size 大小 max, large, 默认, samll, mini 24 | + plain 朴素按钮 25 | + round 圆形按钮 26 | + block 块按钮 27 | + active 焦点 28 | + effect 焦点涟漪效果,默认开启 29 | + router 路由对象会这路由path,内部执行的是this.$router.push方法 30 | -------------------------------------------------------------------------------- /test/e2e/specs/test.js: -------------------------------------------------------------------------------- 1 | // For authoring Nightwatch tests, see 2 | // http://nightwatchjs.org/guide#usage 3 | 4 | module.exports = { 5 | 'default e2e tests': function (browser) { 6 | // automatically uses dev Server port from /config.index.js 7 | // default: http://localhost:8080 8 | // see nightwatch.conf.js 9 | const devServer = browser.globals.devServerURL 10 | 11 | browser 12 | .url(devServer) 13 | .waitForElementVisible('#app', 5000) 14 | .assert.elementPresent('.hello') 15 | .assert.containsText('h1', 'Welcome to Your Vue.js App') 16 | .assert.elementCount('img', 1) 17 | .end() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/m/navbar/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Navbar 3 | 4 | ```html 5 | 6 | Navbar 7 | 8 | 首页 9 | 专栏 10 | 用户中心 11 | 12 | 13 | ``` 14 | 15 | ### Navbar Attributes 16 | 17 | + theme 主题类型 drak,info,primary,success,warmning 18 | 19 | ### Navbar Events 20 | 21 | + command 点击非连接时回调cmd 22 | 23 | ### Nav Attributes 24 | 25 | + align 浮动,left/right 26 | 27 | ### NavItem Attributes 28 | 29 | + name 路由名称 30 | + path 路由path 31 | + disabled 禁用 32 | + off-click 禁用click响应 33 | + active 焦点 34 | + cmd item标签 35 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | import user from './user' 5 | import members from './modules/members'; 6 | import addMovie from './modules/addMovie'; 7 | import admin from './modules/admin'; 8 | import editMovie from './modules/editMovie'; 9 | import movie from './modules/movie'; 10 | import search from './modules/search'; 11 | import count from './modules/count'; 12 | import login from './modules/login'; 13 | Vue.use(Vuex) 14 | 15 | export default new Vuex.Store({ 16 | strict: process.env.NODE_ENV !== 'production', 17 | modules: { 18 | user, 19 | members, 20 | addMovie, 21 | admin, 22 | editMovie, 23 | movie, 24 | search, 25 | count, 26 | login 27 | } 28 | }) 29 | -------------------------------------------------------------------------------- /src/assets/css/scrollbar.css: -------------------------------------------------------------------------------- 1 | ::-webkit-scrollbar { 2 | width: 9px; 3 | height: 9px; 4 | background: transparent; 5 | } 6 | ::-webkit-scrollbar-corner { 7 | background: transparent; 8 | } 9 | ::-webkit-resizer{ 10 | background: transparent; 11 | } 12 | ::-webkit-scrollbar-track, 13 | ::-webkit-scrollbar-thumb { 14 | border-radius: 999px; 15 | background-clip: content-box; 16 | border: solid 2px transparent; 17 | } 18 | /*滚动轨道*/ 19 | ::-webkit-scrollbar-track { 20 | background-color: rgba(33, 37, 43, 0.26); 21 | } 22 | /*滚动条*/ 23 | ::-webkit-scrollbar-thumb { 24 | min-height: 20px; 25 | background-color: rgba(33, 37, 43, 0.26); 26 | 27 | } 28 | ::-webkit-scrollbar-thumb:hover{ 29 | background-color: #5A6274; 30 | border-width: 1px; 31 | } 32 | -------------------------------------------------------------------------------- /src/m/dropdown/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Dropdown 3 | 4 | 5 | ```html 6 | 7 | Dropdown 8 | 9 | 点赞 10 | 转发 11 | 用户信息 12 | 分享 13 | 退出 14 | 15 | 16 | ``` 17 | 18 | ### Dropdown Attributes 19 | 20 | + align 水平对齐,可选left,right;默认left。 21 | + vertical-align 垂直方向对齐,可选top,bottom;默认top; 22 | + trigger 23 | + waitTime 24 | 25 | ### Dropdown Events 26 | 27 | + command 点击item后触发的命令,回调一个cmd 28 | 29 | 30 | ### DropdownItem Attributes 31 | 32 | + name 路由名称 33 | + path 路由路径 34 | + cmd item的标签 35 | + disabled 是否禁用,默认false 36 | + offClick 禁用点击后关闭,默认点击后关闭 37 | -------------------------------------------------------------------------------- /test/unit/jest.conf.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | rootDir: path.resolve(__dirname, '../../'), 5 | moduleFileExtensions: [ 6 | 'js', 7 | 'json', 8 | 'vue' 9 | ], 10 | moduleNameMapper: { 11 | '^@/(.*)$': '/src/$1' 12 | }, 13 | transform: { 14 | '^.+\\.js$': '/node_modules/babel-jest', 15 | '.*\\.(vue)$': '/node_modules/vue-jest' 16 | }, 17 | testPathIgnorePatterns: [ 18 | '/test/e2e' 19 | ], 20 | snapshotSerializers: ['/node_modules/jest-serializer-vue'], 21 | setupFiles: ['/test/unit/setup'], 22 | mapCoverage: true, 23 | coverageDirectory: '/test/unit/coverage', 24 | collectCoverageFrom: [ 25 | 'src/**/*.{js,vue}', 26 | '!src/main.js', 27 | '!src/router/index.js', 28 | '!**/node_modules/**' 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | // 进入全屏 2 | export function requestFullScreen () { 3 | var elem = document.documentElement 4 | if (elem.requestFullscreen) { 5 | elem.requestFullscreen() 6 | } else if (elem.mozRequestFullScreen) { 7 | elem.mozRequestFullScreen() 8 | } else if (elem.webkitRequestFullScreen) { 9 | elem.webkitRequestFullScreen() 10 | } else if (elem.msRequestFullscreen) { 11 | // elem.msRequestFullscreen() 没有指定元素 12 | document.body.msRequestFullscreen() 13 | } 14 | } 15 | // 退出全屏 16 | export function exitFullscreen () { 17 | var doc = document 18 | if (doc.exitFullscreen) { 19 | doc.exitFullscreen() 20 | } else if (doc.mozCancelFullScreen) { 21 | doc.mozCancelFullScreen() 22 | } else if (doc.webkitCancelFullScreen) { 23 | doc.webkitCancelFullScreen() 24 | } else if (doc.msExitFullscreen) { 25 | document.msExitFullscreen() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/pages/c-loading.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/e2e/custom-assertions/elementCount.js: -------------------------------------------------------------------------------- 1 | // A custom Nightwatch assertion. 2 | // The assertion name is the filename. 3 | // Example usage: 4 | // 5 | // browser.assert.elementCount(selector, count) 6 | // 7 | // For more information on custom assertions see: 8 | // http://nightwatchjs.org/guide#writing-custom-assertions 9 | 10 | exports.assertion = function (selector, count) { 11 | this.message = 'Testing if element <' + selector + '> has count: ' + count 12 | this.expected = count 13 | this.pass = function (val) { 14 | return val === this.expected 15 | } 16 | this.value = function (res) { 17 | return res.value 18 | } 19 | this.command = function (cb) { 20 | var self = this 21 | return this.api.execute(function (selector) { 22 | return document.querySelectorAll(selector).length 23 | }, [selector], function (res) { 24 | cb.call(self, res) 25 | }) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/store/modules/editMovie.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | const state = { 3 | // 修改影片信息 返回给前端状态码 1为成功 -1 为失败 4 | editStatus:null 5 | }; 6 | const mutations = { 7 | EDIT_Movie_STUTAS(state,payload){ 8 | state.editStatus=payload; 9 | }, 10 | }; 11 | 12 | const actions = { 13 | editMovie({commit},params){ 14 | console.log(params); 15 | axios.get("/api/movie/editMovie",{ 16 | params:{ 17 | id:params.id, 18 | form:params.form 19 | } 20 | }) 21 | .then(res=>{ 22 | let payload = res.code; 23 | commit("EDIT_Movie_STUTAS",payload) 24 | }) 25 | .catch(function (error) { 26 | console.log(error); 27 | }); 28 | }, 29 | }; 30 | const getters = { 31 | // 获取添加状态1或者0 32 | editMovieStatus:state => { 33 | return state.editStatus 34 | }, 35 | } 36 | export default{ 37 | state, 38 | mutations, 39 | actions, 40 | getters 41 | } -------------------------------------------------------------------------------- /src/components/app-footer.vue: -------------------------------------------------------------------------------- 1 | 9 | 20 | 37 | -------------------------------------------------------------------------------- /src/store/modules/count.js: -------------------------------------------------------------------------------- 1 | // 计算当前所有的用户数 和 所有的影片数 2 | import axios from 'axios' 3 | const state = { 4 | movieNum:{ 5 | length:'' 6 | }, 7 | memberNum:{ 8 | length:'' 9 | } 10 | }; 11 | const mutations = { 12 | GET_MOVIE_NUM(state,payload){ 13 | state.movieNum=payload; 14 | }, 15 | GET_MEMBER_NUM(state,payload){ 16 | state.memberNum=payload; 17 | } 18 | }; 19 | 20 | const actions = { 21 | getMemNum({commit},params){ 22 | axios.get("/api/user/getUserCount") 23 | .then(res=>{ 24 | let payload = res; 25 | // console.log(res); 26 | commit("GET_MEMBER_NUM",payload) 27 | }) 28 | .catch(function (error) { 29 | console.log(error); 30 | }); 31 | } 32 | }; 33 | const getters = { 34 | getMemNum:state => { 35 | return state.memberNum 36 | }, 37 | getMovieNum:state => { 38 | return state.movieNum 39 | }, 40 | } 41 | export default{ 42 | state, 43 | mutations, 44 | actions, 45 | getters 46 | } -------------------------------------------------------------------------------- /src/store/modules/addMovie.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | const state = { 3 | // 添加影片信息 返回给前端状态码 1为成功 -1 为失败 4 | addStatus:null 5 | }; 6 | const mutations = { 7 | ADD_Movie(state,payload){ 8 | state.addStatus=payload; 9 | }, 10 | }; 11 | 12 | const actions = { 13 | addMovie({commit},params){ 14 | if(params.form.status == '完结'){ 15 | params.form.status = 1 16 | }else{ 17 | params.form.status = 0 18 | } 19 | axios.get("/api/movie/addMovie",{ 20 | params:{ 21 | form:params.form 22 | } 23 | }) 24 | .then(res=>{ 25 | let payload = res; 26 | console.log(res); 27 | commit("ADD_Movie",payload) 28 | }) 29 | .catch(function (error) { 30 | console.log(error); 31 | }); 32 | }, 33 | }; 34 | const getters = { 35 | // 获取添加状态1或者0 36 | addStatus:state => { 37 | return state.addStatus 38 | }, 39 | } 40 | export default{ 41 | state, 42 | mutations, 43 | actions, 44 | getters 45 | } -------------------------------------------------------------------------------- /src/m/checkbox/README.md: -------------------------------------------------------------------------------- 1 | ## Checkbox 2 | 3 | 多选框 4 | 5 | ```html 6 | 多选框 7 | ``` 8 | 9 | or 10 | 11 | ```html 12 | 13 | ``` 14 | 15 | 组 16 | 17 | ```html 18 | 19 | aaa 20 | bbb 21 | ccc 22 | eee 23 | 24 | ``` 25 | 26 | 27 | ### Checkbox Attributes 28 | 29 | + value 绑定的数组 30 | + name 原生name 31 | + type 主题类型 primary, success, danger, warning 32 | + checked 初始化选中(mounted中触发,服务端渲染不支持),默认false 33 | + disabled 禁用 34 | + value v-model 35 | + val 当前多选框值,Boolean, Array, String, Number;默认Boolean 36 | + trueVal 选中值 37 | + falseVal 取消选择值 38 | + label 文本 39 | 40 | ### Checkbox Events 41 | 42 | + chagne 值变化触发 43 | 44 | ### CheckboxGroup Attributes 45 | 46 | + value 绑定的数组 47 | 48 | ### CheckboxGroup Attributes 49 | 50 | + chagne 之变化触发 51 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // https://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parser: 'babel-eslint', 6 | parserOptions: { 7 | sourceType: 'module' 8 | }, 9 | env: { 10 | browser: true, 11 | }, 12 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md 13 | extends: 'standard', 14 | // required to lint *.vue files 15 | plugins: [ 16 | 'html' 17 | ], 18 | // add your custom rules here 19 | 'rules': { 20 | // allow paren-less arrow functions 21 | 'arrow-parens': 0, 22 | // allow async-await 23 | 'generator-star-spacing': 0, 24 | // allow debugger during development 25 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, 26 | "no-unused-vars": ['warn', { 27 | // 允许声明未使用变量 28 | "vars": "local", 29 | // 参数不检查 30 | "args": "none" 31 | }], 32 | //空行最多不能超过100行 33 | "no-multiple-empty-lines": ['warn', {"max": 100}], 34 | //关闭禁止混用tab和空格 35 | "no-mixed-spaces-and-tabs": ['warn'] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/m/navbar/nav-item.vue: -------------------------------------------------------------------------------- 1 | 15 | 44 | -------------------------------------------------------------------------------- /src/m/container/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## MContainer 3 | 4 | 响应式布局容器,与Boostrap 3的栅格系统效果一致 5 | 6 | ```html 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | ``` 16 | 17 | 样式来自:[100行less实现bootstrap的12栅格布局](https://segmentfault.com/a/1190000010104455) 18 | 19 | ### MContainer Attributes 20 | 21 | + fluid 是否占用100%宽度,与Boostrap .container-fluid一致默认false。 22 | 23 | ### MCol Attributes 24 | 25 | + xs Number or String 与Boostrap .col-xs一致 26 | + sm Number or String 与Boostrap .col-sm一致 27 | + md Number or String 28 | + lg Number or String 29 | + offset Number or String 与Boostrap .col-md-offset一致 30 | + pull Number or String 31 | + push Number or String 32 | + xs-offset Number or String 与Boostrap .col-xs-offset一致 33 | + sm-offset Number or String 34 | + lg-offset Number or String 35 | + xs-pull Number or String 36 | + sm-pull Number or String 37 | + lg-pull Number or String 38 | + xs-push Number or String 39 | + sm-push Number or String 40 | + lg-push Number or String 41 | -------------------------------------------------------------------------------- /src/components/vmenu/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## VMenu 3 | 4 | ```html 5 | 6 | ``` 7 | 8 | `v-menu`组件基于`el-menu`一组件,新增加了 `menus` props属性,这个是个对象树,这样可以直接以对象形式快速菜单目录 9 | 10 | ## Props menus 11 | 12 | ```js 13 | 14 | var menus = [ 15 | {id: '0', label: '菜单1'}, 16 | {id: '1', label: '菜单2'}, 17 | { 18 | id: '2', 19 | label: '展开菜单1', 20 | // 具有分组 21 | submenu: [ 22 | [ 23 | '组名1', 24 | [ 25 | {id: '2-1', label: '选项1'}, 26 | {id: '2-2', label: '选项2'} 27 | ] 28 | ], 29 | [ 30 | '组名2', 31 | [ 32 | {id: '2-3', label: '选项3'}, 33 | {id: '2-4', label: '选项4'} 34 | ] 35 | ] 36 | ] 37 | }, 38 | { 39 | id: '3', 40 | label: '展开菜单2', 41 | // 不分组 42 | submenu: [ 43 | {id: '3-1', label: '选项1'}, 44 | {id: '3-2', label: '选项2'} 45 | ] 46 | }, 47 | ] 48 | 49 | ``` 50 | > 应为第一层分组如果缩小侧边栏时会存在问题,所以不支持第一层分组 51 | 52 | 更多的属性,请查阅饿了么的开发文档[menu](http://element.eleme.io/#/zh-CN/component/menu)。 53 | -------------------------------------------------------------------------------- /src/m/keyboard/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Keyboard 3 | 4 | 英文+数字+常用符号虚拟键盘 5 | 6 | 主要用于触控设备使用。 7 | 8 | ```html 9 | 10 | ``` 11 | 12 | ## Install 13 | 14 | ``` 15 | npm install -S m-keyboard 16 | ``` 17 | 18 | **user**: 19 | 20 | ```bat 21 | import MKeyboard from 'm-keyboard' 22 | Vue.use(MKeyboard) 23 | ``` 24 | 25 | ## Keyboard Attributes 26 | 27 | + capsLock 大写,默认false 28 | + symbol 字符,默认false 29 | + width 宽度,String 或 Number;默认480 30 | + height 高度,String 或 Number;默认200 31 | + disabledKeys 禁用键,Array 32 | + lang-hide 隐藏语言切换按键,默认true 33 | + lang 语言,zh,en,默认en 34 | 35 | 36 | ## Keyboard Event 37 | 38 | + shift 按下shift事件 39 | + symbol 按下符号键 40 | + enter 按下enter键 41 | + back 按下退格键 42 | + key 按下输入键,回调参数为键值 43 | + lang 切换语言 44 | 45 | ## Py-Keyboard Attributes 46 | 47 | 48 | 49 | `Py-Keyboard` 组件是中英文键盘,因为需要字典文件(2.7k+)支持,所以`index.js`导出的组件并不包含,需要手动导入。 50 | `Py-Keyboard` 组件继承了 `Keyboard` 组件的属性与事件 51 | 52 | + candidate-len 候选词长度 53 | + sync 是否同步更新,默认false 54 | + listener 是否监听键盘事件,默认true 55 | 56 | 57 | [字典文件来自sxei/pinyinjs](https://github.com/sxei/pinyinjs) 58 | -------------------------------------------------------------------------------- /src/components/theme.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | label: '默认', 4 | name: 'default', 5 | theme: { 6 | // 默认,dark, info, primary, danger, success, warming 7 | headerTheme: 'info' 8 | } 9 | }, 10 | { 11 | label: '黑色', 12 | name: 'dark', 13 | theme: { 14 | backgroundColor: '#333940', 15 | textColor: '#E0E0E0', 16 | activeTextColor: '#fff', 17 | headerTheme: 'dark' 18 | } 19 | }, 20 | { 21 | label: '科技', 22 | name: 'keji', 23 | theme: { 24 | backgroundColor: '#222D32', 25 | textColor: '#b8c7ce', 26 | activeTextColor: '#fff', 27 | headerTheme: 'info' 28 | } 29 | }, 30 | { 31 | label: '红艳', 32 | name: 'hongyan', 33 | theme: { 34 | backgroundColor: '#282C34', 35 | textColor: '#F0F0F5', 36 | activeTextColor: '#fff', 37 | headerTheme: 'danger' 38 | } 39 | }, 40 | { 41 | label: '白色', 42 | name: 'white', 43 | theme: { 44 | backgroundColor: '#FFFFFF', 45 | textColor: '#303133', 46 | activeTextColor: '#282C34', 47 | headerTheme: '' 48 | } 49 | } 50 | ] 51 | -------------------------------------------------------------------------------- /src/m/list-group/list-group.vue: -------------------------------------------------------------------------------- 1 | 6 | 11 | 41 | -------------------------------------------------------------------------------- /src/m/switch/switch.vue: -------------------------------------------------------------------------------- 1 | 16 | 48 | 51 | 52 | -------------------------------------------------------------------------------- /test/e2e/nightwatch.conf.js: -------------------------------------------------------------------------------- 1 | require('babel-register') 2 | var config = require('../../config') 3 | 4 | // http://nightwatchjs.org/gettingstarted#settings-file 5 | module.exports = { 6 | src_folders: ['test/e2e/specs'], 7 | output_folder: 'test/e2e/reports', 8 | custom_assertions_path: ['test/e2e/custom-assertions'], 9 | 10 | selenium: { 11 | start_process: true, 12 | server_path: require('selenium-server').path, 13 | host: '127.0.0.1', 14 | port: 4444, 15 | cli_args: { 16 | 'webdriver.chrome.driver': require('chromedriver').path 17 | } 18 | }, 19 | 20 | test_settings: { 21 | default: { 22 | selenium_port: 4444, 23 | selenium_host: 'localhost', 24 | silent: true, 25 | globals: { 26 | devServerURL: 'http://localhost:' + (process.env.PORT || config.dev.port) 27 | } 28 | }, 29 | 30 | chrome: { 31 | desiredCapabilities: { 32 | browserName: 'chrome', 33 | javascriptEnabled: true, 34 | acceptSslCerts: true 35 | } 36 | }, 37 | 38 | firefox: { 39 | desiredCapabilities: { 40 | browserName: 'firefox', 41 | javascriptEnabled: true, 42 | acceptSslCerts: true 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/m/keyboard/pagination.js: -------------------------------------------------------------------------------- 1 | function Pagination (total, len) { 2 | // 总条目数 3 | this.total = Math.abs(~~total) 4 | // 当前页 5 | this.currentPage = 1 6 | var l = Math.abs(~~len) 7 | len = l > this.total ? this.total : l 8 | // 每页显示条目个数 9 | this.pageSize = len || 10 10 | // 总页数 11 | this.pageCount = Math.ceil(~~Math.abs(this.total) / ~~Math.abs(this.pageSize)) 12 | } 13 | // 指定页数据 14 | Pagination.prototype.page = function (n) { 15 | var page = Math.abs(~~n) || this.currentPage 16 | if (page > this.pageCount) { 17 | // 防止出现当前页为零情况 18 | this.currentPage = this.pageCount || 1 19 | } else { 20 | this.currentPage = page 21 | } 22 | // console.log(this.currentPage) 23 | var index1 = (this.currentPage > 0 ? this.currentPage - 1 : 0) * this.pageSize 24 | var index2 = this.currentPage * this.pageSize 25 | index2 = index2 > this.total ? this.total : index2 26 | // slice(index1, index2) 实际arr[index1] ~ arr[index2-1] 27 | return [index1, index2] 28 | } 29 | 30 | Pagination.prototype.slice = function (arr, n) { 31 | return arr.slice.apply(arr, this.page(n)) 32 | } 33 | 34 | Pagination.prototype.sql = function (n) { 35 | var pages = this.page(n) 36 | return { 37 | offset: pages[0], 38 | limit: this.pageSize 39 | } 40 | } 41 | 42 | export default Pagination 43 | -------------------------------------------------------------------------------- /src/pages/about.vue: -------------------------------------------------------------------------------- 1 | 12 | 17 | 52 | -------------------------------------------------------------------------------- /src/m/dropdown/dropdown-item.vue: -------------------------------------------------------------------------------- 1 | 4 | 18 | 19 | 59 | -------------------------------------------------------------------------------- /src/components/menus.js: -------------------------------------------------------------------------------- 1 | const menus = [ 2 | {id: '0', label: '主页', path: '/', icon: 'fa fa-dashboard'}, 3 | { 4 | id: 'userlist', 5 | icon: 'fa fa-user-circle-o', 6 | label: '用户列表', 7 | path: '/userlist' 8 | }, 9 | { 10 | id: 'movielist', 11 | icon: 'fa fa-film', 12 | label: '影片列表', 13 | path: '/movielist' 14 | }, 15 | { 16 | id: 'addMovie', 17 | icon: 'fa fa-folder', 18 | label: '添加影片', 19 | path: '/addMovie' 20 | }, 21 | { 22 | id: 'search', 23 | icon: 'fa fa-search-minus', 24 | label: '搜索', 25 | path: '/search', 26 | submenu: [ 27 | {id: 'search-user', path: '/search-user', label: '用户搜索'}, 28 | {id: 'search-movie', path: '/search-movie', label: '资源搜索'}, 29 | ] 30 | }, 31 | { 32 | id: 'setting', 33 | icon: 'fa fa-cog', 34 | label: '设置', 35 | path: '/setting', 36 | }, 37 | { 38 | id: 'about', 39 | icon: 'fa fa-smile-o', 40 | label: '关于', 41 | path: '/about' 42 | }, 43 | // { 44 | // id: 'pages', 45 | // icon: 'fa fa-circle-o', 46 | // label: 'Pages 页面', 47 | // submenu: [ 48 | // {id: 'p1', name: 'login', label: 'Login 登录'}, 49 | // {id: '400', path: '/404', label: '404'}, 50 | // {id: '500', path: '/500', label: '500'} 51 | // ] 52 | // }, 53 | ] 54 | export default menus 55 | -------------------------------------------------------------------------------- /src/store/modules/login.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | const state = { 3 | login:'', 4 | admin:'', 5 | }; 6 | const mutations = { 7 | LOGIN(state,payload){ 8 | state.login=payload; 9 | }, 10 | ADMIN(state,payload){ 11 | state.admin=payload; 12 | }, 13 | }; 14 | 15 | const actions = { 16 | login2({commit},params){ 17 | axios.get("/api/login",{ 18 | params:{ 19 | user: params.username, 20 | pwd: params.password 21 | } 22 | }) 23 | .then(res=>{ 24 | let user = {} 25 | user.id = 1 26 | user.nickName = '管理员' 27 | let payload = res.code 28 | sessionStorage.setItem('token', JSON.stringify(res.token)) 29 | sessionStorage.setItem('user',JSON.stringify(user)) 30 | // 清除缓存的验证码cookie 31 | commit("LOGIN",payload) 32 | }) 33 | .catch(function (error) { 34 | console.log(error); 35 | }); 36 | }, 37 | getLoginAdmin ({commit}) { 38 | var user = sessionStorage.getItem('user') 39 | commit('ADMIN', JSON.parse(user)) 40 | }, 41 | logout ({commit}) { 42 | sessionStorage.removeItem('user') 43 | sessionStorage.removeItem('token') 44 | commit('ADMIN', null) 45 | } 46 | }; 47 | const getters = { 48 | // 获取添加状态1或者0 49 | Loginadmin:state => { 50 | return state.admin 51 | }, 52 | login:state => { 53 | return state.login 54 | }, 55 | } 56 | export default{ 57 | state, 58 | mutations, 59 | actions, 60 | getters 61 | } -------------------------------------------------------------------------------- /src/components/app-side.vue: -------------------------------------------------------------------------------- 1 | 15 | 49 | 66 | -------------------------------------------------------------------------------- /src/m/container/col.vue: -------------------------------------------------------------------------------- 1 | 23 | 51 | -------------------------------------------------------------------------------- /src/m/loader/loader.js: -------------------------------------------------------------------------------- 1 | 2 | const loads = {} 3 | 4 | export default { 5 | name: 'MLoader', 6 | props: { 7 | src: String, 8 | script: Boolean, 9 | mount: String 10 | }, 11 | data () { 12 | return { 13 | elem: null 14 | } 15 | }, 16 | methods: { 17 | onload () { 18 | console.log(loads) 19 | loads[this.src].loaded = true 20 | loads[this.src].mount = window[this.mount] 21 | this.$emit('load', window[this.mount], loads[this.src]) 22 | }, 23 | onerror () { 24 | console.log('error') 25 | }, 26 | insert () { 27 | console.log('insert') 28 | loads[this.src] = { 29 | loaded: false, 30 | script: this.script 31 | } 32 | this.elem = document.createElement(this.script ? 'SCRIPT' : 'LINK') 33 | if (this.script) { 34 | this.elem.src = this.src 35 | this.elem.type = 'text/javascript' 36 | } else { 37 | this.elem.href = this.src 38 | this.elem.type = 'text/css' 39 | this.elem.rel = 'stylesheet' 40 | } 41 | 42 | this.elem.id = Math.random().toString(16).substring(2) 43 | loads[this.src]['id'] = this.elem.id 44 | loads[this.src]['el'] = this.elem 45 | this.listener() 46 | document.head.appendChild(this.elem) 47 | }, 48 | listener () { 49 | var el = loads[this.src].el 50 | el.addEventListener('load', this.onload, false) 51 | el.addEventListener('error', this.onerror, false) 52 | } 53 | }, 54 | mounted () { 55 | console.log(this.src) 56 | this.insert() 57 | }, 58 | render () { 59 | return null 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | 4 | import AppView from '@/components/app-view' 5 | import Home from '@/pages/home' 6 | 7 | Vue.use(Router) 8 | 9 | const page = name => () => import('@/pages/' + name) 10 | 11 | export default new Router({ 12 | mode: 'history', 13 | routes: [ 14 | { 15 | path: '', 16 | component: AppView, 17 | children: [ 18 | {path: '/', name: 'home', component: Home}, 19 | // {path: '/keyboard', name: 'c-keyboard', component: page('c-keyboard')}, 20 | {path: '/loading', name: 'c-loading', component: page('c-loading')}, 21 | // 这里开始是我需要的 22 | {path: '/404', name: '404', component: page('404')}, 23 | {path: '/500', name: '500', component: page('500')}, 24 | {path: '/userlist', name: 'userlist', component: page('userlist')}, 25 | {path: '/movielist', name: 'movielist', component: page('movielist')}, 26 | {path: '/about', name: 'about', component: page('about')}, 27 | {path: '/setting', name: 'setting', component: page('setting')}, 28 | {path: '/editPwd', name: 'editPwd', component: page('editPwd')}, 29 | {path: '/search-user', name: 'search-user', component: page('search-user')}, 30 | {path: '/search-movie', name: 'search-movie', component: page('search-movie')}, 31 | {path: '/addMovie', name: 'addMovie', component: page('addMovie')}, 32 | ] 33 | }, 34 | {path: '/login', name: 'login', component: page('login')}, 35 | // pages 36 | // {path: '/keyboard', name: 'p-keyboard', component: page('c-keyboard')}, 37 | {path: '*', redirect: {name: '404'}} 38 | ] 39 | }) 40 | -------------------------------------------------------------------------------- /src/pages/404.vue: -------------------------------------------------------------------------------- 1 | 12 | 54 | -------------------------------------------------------------------------------- /src/pages/500.vue: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /src/components/vmenu/chunk.vue: -------------------------------------------------------------------------------- 1 | 42 | 62 | -------------------------------------------------------------------------------- /test/e2e/runner.js: -------------------------------------------------------------------------------- 1 | // 1. start the dev server using production config 2 | process.env.NODE_ENV = 'testing' 3 | 4 | const webpack = require('webpack') 5 | const DevServer = require('webpack-dev-server') 6 | 7 | const webpackConfig = require('../../build/webpack.prod.conf') 8 | const devConfigPromise = require('../../build/webpack.dev.conf') 9 | 10 | let server 11 | 12 | devConfigPromise.then(devConfig => { 13 | const devServerOptions = devConfig.devServer 14 | const compiler = webpack(webpackConfig) 15 | server = new DevServer(compiler, devServerOptions) 16 | const port = devServerOptions.port 17 | const host = devServerOptions.host 18 | return server.listen(port, host) 19 | }) 20 | .then(() => { 21 | // 2. run the nightwatch test suite against it 22 | // to run in additional browsers: 23 | // 1. add an entry in test/e2e/nightwatch.conf.js under "test_settings" 24 | // 2. add it to the --env flag below 25 | // or override the environment flag, for example: `npm run e2e -- --env chrome,firefox` 26 | // For more information on Nightwatch's config file, see 27 | // http://nightwatchjs.org/guide#settings-file 28 | let opts = process.argv.slice(2) 29 | if (opts.indexOf('--config') === -1) { 30 | opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js']) 31 | } 32 | if (opts.indexOf('--env') === -1) { 33 | opts = opts.concat(['--env', 'chrome']) 34 | } 35 | 36 | const spawn = require('cross-spawn') 37 | const runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' }) 38 | 39 | runner.on('exit', function (code) { 40 | server.close() 41 | process.exit(code) 42 | }) 43 | 44 | runner.on('error', function (err) { 45 | server.close() 46 | throw err 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /src/components/menu-list.vue: -------------------------------------------------------------------------------- 1 | 44 | 65 | -------------------------------------------------------------------------------- /src/m/input/input.less: -------------------------------------------------------------------------------- 1 | @input: m-input; 2 | 3 | .@{input}-wrapper{ 4 | display: inline-block; 5 | } 6 | .@{input}-block{ 7 | display: block; 8 | & @{input} { 9 | display: block; 10 | } 11 | } 12 | .@{input}{ 13 | display: inline-block; 14 | padding: 5px 12px; 15 | font-size: 14px; 16 | font-family: inherit; 17 | line-height: 1.42857143; 18 | height: 36px; 19 | width: 100%; 20 | color: #555; 21 | background-color: #fff; 22 | background-image: none; 23 | border: 1px solid #DCDFE6; 24 | border-radius: 4px; 25 | vertical-align: middle; 26 | outline: none; 27 | box-sizing: border-box; 28 | box-shadow: 0 0 0px 0px rgba(44, 147, 250, 0.18); 29 | transition: border,box-shadow 0.5s ease; 30 | &&-textarea{ 31 | padding: 5px 12px; 32 | min-height: 54px; 33 | height: 54px; 34 | } 35 | &:hover{ 36 | border-color: #29A0F2; 37 | } 38 | &:focus{ 39 | border-color: #29A0F2; 40 | box-shadow: 0 0 0px 3px rgba(44, 147, 250, 0.18); 41 | } 42 | &.disabled, 43 | &:disabled{ 44 | background: #f9f9f9; 45 | border-color: #ccc; 46 | cursor: not-allowed; 47 | } 48 | &&-max{ 49 | font-size: 18px; 50 | height: 57px; 51 | } 52 | &&-large{ 53 | height: 42px; 54 | } 55 | &&-small{ 56 | height: 30px; 57 | font-size: 13px; 58 | } 59 | &&-mini{ 60 | height: 27px; 61 | font-size: 12px; 62 | } 63 | &&-success{ 64 | &:hover{ 65 | border-color: #39C12E; 66 | } 67 | &:focus{ 68 | border-color: #39C12E; 69 | box-shadow: 0 0 0px 3px rgba(5,152,21, 0.18); 70 | } 71 | } 72 | &&-danger{ 73 | &:hover{ 74 | border-color: #F56764; 75 | } 76 | &:focus{ 77 | border-color: #F56764; 78 | box-shadow: 0 0 0px 3px rgba(229,62,26, 0.18); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/m/sticky/sticky.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | -------------------------------------------------------------------------------- /src/store/modules/movie.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | const state = { 3 | movie:[], 4 | movieNum:null, 5 | delStatus:{} 6 | }; 7 | const mutations = { 8 | GET_ALL_MOVIE(state,payload){ 9 | state.movie=payload; 10 | }, 11 | DEL_MOVIE(state,payload){ 12 | state.delStatus=payload; 13 | }, 14 | GET_MOVIE_NUM(state,payload){ 15 | state.movieNum = payload; 16 | } 17 | }; 18 | 19 | const actions = { 20 | getMovieNum({commit},params){ 21 | axios.get("/api/movie/getMovieCount") 22 | .then(res=>{ 23 | let payload = res.length; 24 | commit("GET_MOVIE_NUM",payload) 25 | }) 26 | .catch(function (error) { 27 | console.log(error); 28 | }); 29 | }, 30 | getAllMovie({commit},params){ 31 | axios.get("/api/movie/getMovieAll",{ 32 | params:{ 33 | pageSize:params.pageSize, 34 | curPage:params.curPage, 35 | } 36 | }) 37 | .then(res=>{ 38 | let payload = res.data; 39 | // console.log(payload); 40 | commit("GET_ALL_MOVIE",payload) 41 | }) 42 | .catch(function (error) { 43 | console.log(error); 44 | }); 45 | }, 46 | delMovie({commit},params){ 47 | // console.log(params); 48 | axios.get("/api/movie/delMovie",{ 49 | params:{ 50 | id:params.id, 51 | pageSize:params.pageSize, 52 | curPage:params.curPage, 53 | } 54 | }) 55 | .then(res=>{ 56 | let payload = res; 57 | // console.log(payload); 58 | commit("DEL_MOVIE",payload) 59 | }) 60 | .catch(function (error) { 61 | console.log(error); 62 | }); 63 | }, 64 | }; 65 | const getters = { 66 | // 获取按更新状态的数据 67 | movie:state => { 68 | return state.movie 69 | }, 70 | // 用户总数 71 | movieNum:state => { 72 | return state.movieNum 73 | } 74 | } 75 | export default{ 76 | state, 77 | mutations, 78 | actions, 79 | getters 80 | } -------------------------------------------------------------------------------- /src/store/user.js: -------------------------------------------------------------------------------- 1 | // import Vue from 'vue' 2 | 3 | const state = { 4 | user: null, 5 | isLogin: false 6 | } 7 | 8 | const actions = { 9 | async login ({commit}, user) { 10 | commit('SET_DOING_LOGIN', true) 11 | // 模拟登陆 12 | var res = await new Promise((resolve, reject) => { 13 | user.id = 1 14 | user.nickName = '管理员' 15 | sessionStorage.setItem('user', JSON.stringify(user)) 16 | setTimeout(() => { 17 | resolve({bool: true, user}) 18 | }, 2000) 19 | }) 20 | commit('SET_LOGIN_USER', user) 21 | commit('SET_LOGIN_TOKEN', '4eea90fd-2752-481d-ae67-c75f8641a94a') 22 | commit('SET_DOING_LOGIN', false) 23 | return res 24 | }, 25 | async getLoginUser ({commit}) { 26 | // 模拟请求用户信息 27 | return new Promise((resolve, reject) => { 28 | setTimeout(() => { 29 | var user = sessionStorage.getItem('user') 30 | // console.log(JSON.parse(user)) 31 | commit('SET_LOGIN_USER', JSON.parse(user)) 32 | resolve({bool: true, user}) 33 | }, 1000) 34 | }) 35 | }, 36 | // async logout ({commit}) { 37 | // // 模拟退出 38 | // return new Promise((resolve, reject) => { 39 | // setTimeout(() => { 40 | // sessionStorage.removeItem('user') 41 | // sessionStorage.removeItem('token') 42 | // commit('SET_LOGIN_USER', null) 43 | // resolve({bool: true}) 44 | // }, 2000) 45 | // }) 46 | // } 47 | } 48 | 49 | const mutations = { 50 | SET_DOING_LOGIN (state, isLogin) { 51 | state.isLogin = isLogin 52 | }, 53 | SET_LOGIN_USER (state, user) { 54 | state.user = user 55 | }, 56 | SET_LOGIN_TOKEN (state, token) { 57 | if (token) { 58 | sessionStorage.setItem('token', token) 59 | } else { 60 | sessionStorage.removeItem('token') 61 | } 62 | } 63 | } 64 | 65 | export default { 66 | state, 67 | actions, 68 | mutations 69 | } 70 | -------------------------------------------------------------------------------- /src/m/dropdown/dropdown.less: -------------------------------------------------------------------------------- 1 | @dorpdown-name: m-dropdown; 2 | 3 | 4 | .@{dorpdown-name}{ 5 | display: inline-block; 6 | position: relative; 7 | user-select: none; 8 | 9 | & .caret { 10 | display: inline-block; 11 | width: 0; 12 | height: 0; 13 | margin-left: 2px; 14 | vertical-align: middle; 15 | border-top: 4px solid; 16 | border-right: 4px solid transparent; 17 | border-left: 4px solid transparent; 18 | } 19 | 20 | & &-panel{ 21 | display: none; 22 | min-width: 100%; 23 | margin: 0; 24 | padding: 5px 10px; 25 | list-style: none; 26 | position: absolute; 27 | z-index: 1000; 28 | background: #fff; 29 | border-radius: 3px; 30 | box-shadow: 0px 2px 3px 1px rgba(0, 0, 0, 0.09); 31 | border: solid 1px #D9D9D9; 32 | box-sizing: border-box; 33 | } 34 | 35 | &.left &-panel{ 36 | left: 0; 37 | } 38 | &.right &-panel{ 39 | right: 0; 40 | } 41 | &.bottom &-panel{ 42 | bottom: 100%; 43 | } 44 | &.top &-panel{ 45 | top: 0; 46 | } 47 | &.open{ 48 | background-color: rgba(0,0,0,0.05); 49 | } 50 | &.open &-panel{ 51 | display: block; 52 | } 53 | } 54 | 55 | .@{dorpdown-name}-item { 56 | min-width: 100px; 57 | height: 35px; 58 | margin-left: -10px; 59 | margin-right: -10px; 60 | list-style: none; 61 | line-height: 35px; 62 | padding: 0px 10px; 63 | color: #282C34; 64 | cursor: pointer; 65 | white-space: nowrap; 66 | box-sizing: border-box; 67 | 68 | & > a { 69 | display: inline-block; 70 | width: 100%; 71 | color: inherit; 72 | margin-left: -10px; 73 | margin-right: -10px; 74 | padding: 0 10px; 75 | } 76 | &.active, 77 | &:hover { 78 | background: #f3f3f5; 79 | font-weight: 500; 80 | color: #212529; 81 | } 82 | &.disabled{ 83 | cursor: not-allowed; 84 | color: #b3b3b3; 85 | background: inherit; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/m/dropdown/dropdown.vue: -------------------------------------------------------------------------------- 1 | 12 | 78 | 81 | -------------------------------------------------------------------------------- /src/m/button/button.vue: -------------------------------------------------------------------------------- 1 | 20 | 84 | 87 | -------------------------------------------------------------------------------- /src/m/loading/loading.vue: -------------------------------------------------------------------------------- 1 | 26 | 40 | 90 | -------------------------------------------------------------------------------- /server/route.js: -------------------------------------------------------------------------------- 1 | var express = require('express') 2 | var router = express.Router(); 3 | var api = require('./api') 4 | // 管理员登录 5 | router.get('/api/login', function(req, res, next) { 6 | return api.login(req, res, next); 7 | }) 8 | // 获取验证码 9 | router.get('/api/getCaptcha', function(req, res, next) { 10 | return api.getCaptcha(req, res, next); 11 | }) 12 | // 获取所有用户数量 13 | router.get('/api/user/getUserCount', function(req, res, next) { 14 | return api.getUserCount(req, res, next); 15 | }) 16 | // 获取所有电影数量 17 | router.get('/api/movie/getMovieCount', function(req, res, next) { 18 | return api.getMovieCount(req, res, next); 19 | }) 20 | // 获取所有用户信息 21 | router.get('/api/user/getUserAll', function(req, res, next) { 22 | return api.getUserAll(req, res, next); 23 | }) 24 | // 获取所有电影信息 25 | router.get('/api/movie/getMovieAll', function(req, res, next) { 26 | return api.getMovieAll(req, res, next); 27 | }) 28 | // 删除用户信息 29 | router.get('/api/user/delUser', function(req, res, next) { 30 | return api.delUser(req, res, next); 31 | }) 32 | // 删除电影信息 33 | router.get('/api/movie/delMovie', function(req, res, next) { 34 | return api.delMovie(req, res, next); 35 | }) 36 | // 添加影片信息 37 | router.get('/api/movie/addMovie', function(req, res, next) { 38 | return api.addMovie(req, res, next); 39 | }) 40 | // 修改用户信息 41 | router.get('/api/user/editUser', function(req, res, next) { 42 | return api.editUser(req, res, next); 43 | }) 44 | // 修改管理员信息 45 | router.get('/api/admin/editAdmin', function(req, res, next) { 46 | return api.editAdmin(req, res, next); 47 | }) 48 | // 修改影片信息 49 | router.get('/api/movie/editMovie', function(req, res, next) { 50 | return api.editMovie(req, res, next); 51 | }) 52 | // 获取管理员信息 53 | router.get('/api/admin/getAdminMsg', function(req, res, next) { 54 | return api.getAdminMsg(req, res, next); 55 | }) 56 | // 搜索用户信息 57 | router.get('/api/searchUser', function(req, res, next) { 58 | return api.searchUser(req, res, next); 59 | }) 60 | // 搜索影片信息 61 | router.get('/api/searchMovie', function(req, res, next) { 62 | return api.searchMovie(req, res, next); 63 | }) 64 | module.exports = router; -------------------------------------------------------------------------------- /src/store/modules/search.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | const state = { 3 | // 查询用户 4 | searchUser:{ 5 | code:'2' 6 | }, 7 | // 查询影片 8 | searchMovie:{ 9 | code:'2' 10 | }, 11 | }; 12 | const mutations = { 13 | SEARCH_USER(state,payload){ 14 | state.searchUser=payload; 15 | }, 16 | SEARCH_MOVIE(state,payload){ 17 | state.searchMovie=payload; 18 | }, 19 | }; 20 | 21 | const actions = { 22 | searchUser({commit},params){ 23 | axios.get("/api/searchUser",{ 24 | params:{ 25 | name:params.name, 26 | } 27 | }) 28 | .then(res=>{ 29 | if(res.code == -1){ 30 | let payload = res; 31 | commit("SEARCH_USER",payload) 32 | return 33 | } 34 | for(let i=0;i{ 55 | console.log(res); 56 | if(res.code == -1){ 57 | let payload = res; 58 | commit("SEARCH_MOVIE",payload) 59 | return 60 | } 61 | for(let i=0;i { 79 | return state.searchUser 80 | }, 81 | // 获取搜索的影片结果 82 | search_Movie:state => { 83 | return state.searchMovie 84 | }, 85 | } 86 | export default{ 87 | state, 88 | mutations, 89 | actions, 90 | getters 91 | } -------------------------------------------------------------------------------- /src/pages/c-keyboard.vue: -------------------------------------------------------------------------------- 1 | 50 | 77 | -------------------------------------------------------------------------------- /src/store/modules/admin.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | const state = { 3 | // 获取管理员信息 修改管理员信息 4 | admin:'', 5 | editMsg:'',//修改结果 值为0或1 6 | editPwdStatus:false,//修改密码 值为0或1 7 | }; 8 | const mutations = { 9 | GET_ADMIN_MSG(state,payload){ 10 | state.admin=payload; 11 | }, 12 | GET_EDIT_STATUS(state,payload){ 13 | state.editMsg=payload; 14 | }, 15 | GET_EDITPWD_STATUS(state,payload){ 16 | state.editPwdStatus=payload; 17 | }, 18 | CHANGE_EDITPWD_STATUS(state,payload){ 19 | state.editPwdStatus=payload; 20 | }, 21 | }; 22 | 23 | const actions = { 24 | getAdminMsg({commit},params){ 25 | axios.get("/api/admin/getAdminMsg") 26 | .then(res=>{ 27 | let payload = res.data[0]; 28 | commit("GET_ADMIN_MSG",payload) 29 | }) 30 | .catch(function (error) { 31 | console.log(error); 32 | }); 33 | }, 34 | editMsg({commit},params){ 35 | // 两个参数 一个是修改的key 一个是value 36 | axios.get("/api/admin/editAdmin",{ 37 | params:{ 38 | key:params.key, 39 | value:params.value 40 | } 41 | }) 42 | .then(res=>{ 43 | let payload = res; 44 | commit("GET_EDIT_STATUS",payload) 45 | }) 46 | .catch(function (error) { 47 | console.log(error); 48 | }); 49 | }, 50 | editPwd({commit},params){ 51 | // 传入新的密码 52 | axios.get("/api/admin/editAdmin", 53 | { 54 | params:{ 55 | key: 'pwd', 56 | value:params.pwd 57 | } 58 | } 59 | ) 60 | .then(res=>{ 61 | let payload 62 | if(res.code == -1){ 63 | payload = false 64 | }else{ 65 | payload = true 66 | } 67 | commit("GET_EDITPWD_STATUS",payload) 68 | }) 69 | .catch(function (error) { 70 | console.log(error); 71 | }); 72 | }, 73 | handlrPwdStatus({commit},params){ 74 | let payload = false 75 | commit("CHANGE_EDITPWD_STATUS",payload) 76 | }, 77 | }; 78 | const getters = { 79 | admin:state => { 80 | return state.admin 81 | }, 82 | editStatus:state =>{ 83 | return state.editMsg 84 | }, 85 | editPwdStatus:state =>{ 86 | return state.editPwdStatus 87 | }, 88 | } 89 | export default{ 90 | state, 91 | mutations, 92 | actions, 93 | getters 94 | } -------------------------------------------------------------------------------- /src/m/context-menu/index.js: -------------------------------------------------------------------------------- 1 | import ContextMenu from './context-menu' 2 | 3 | const ContextMenuPlugin = {} 4 | 5 | function contextMenuListener (dom, cb) { 6 | dom.addEventListener('contextmenu', function (e) { 7 | if (e.button === 2) { 8 | e.preventDefault() 9 | e.stopPropagation() 10 | cb && cb(e) 11 | return false 12 | } 13 | }, false) 14 | } 15 | 16 | ContextMenuPlugin.install = function (Vue) { 17 | // Vue.component(ContextMenu.name, ContextMenu) 18 | let Menu = Vue.extend(ContextMenu) 19 | let div = document.createElement('div') 20 | let menu = new Menu({ 21 | el: div, 22 | data () { 23 | return {contextMenu: {}, hasIcon: false} 24 | } 25 | }) 26 | Vue.directive('cmenu', { 27 | inserted: (el, binding) => { 28 | if (toString.call(binding.value) !== '[object Object]') { 29 | throw new Error('must be has an object parameters.') 30 | } 31 | let elem = binding.modifiers.fullscreen ? document.body : el 32 | menu.$data.contextMenu = binding.value 33 | menu.$data.hasIcon = !!binding.modifiers.hasIcon 34 | elem.appendChild(menu.$el) 35 | // 监听右键 36 | contextMenuListener(elem, (e) => { 37 | console.log(e.clientX, e.clientY, menu.$el.offsetWidth) 38 | let left = e.clientX 39 | let top = e.clientY 40 | let bodyWidth = document.body.offsetWidth 41 | if (bodyWidth - left < 280) { 42 | menu.nearRight = true 43 | if (bodyWidth - left < 140) { 44 | left = bodyWidth - 140 45 | } 46 | } else { 47 | menu.nearRight = false 48 | } 49 | if (menu.contextMenu && typeof menu.contextMenu.open === 'function') { 50 | let openResult = menu.contextMenu.open(e, menu) 51 | if (openResult !== undefined) { 52 | openResult && menu.setPosition(left + 'px', top + 'px') 53 | } else { 54 | menu.setPosition(left + 'px', top + 'px') 55 | } 56 | } else { 57 | menu.setPosition(left + 'px', top + 'px') 58 | } 59 | }) 60 | // 菜单上禁止鼠标按键事件 61 | menu.$el.addEventListener('mousedown', function (e) { 62 | e.preventDefault() 63 | e.stopPropagation() 64 | return false 65 | }) 66 | // 全局鼠标按键事件 67 | document.addEventListener('mousedown', function (e) { 68 | if (e.button !== 2) { 69 | menu.hide() 70 | } 71 | }) 72 | } 73 | }) 74 | } 75 | 76 | export default ContextMenuPlugin 77 | -------------------------------------------------------------------------------- /src/m/input/input.vue: -------------------------------------------------------------------------------- 1 | 44 | 99 | 102 | -------------------------------------------------------------------------------- /src/pages/search-user.vue: -------------------------------------------------------------------------------- 1 | 37 | 56 | 85 | -------------------------------------------------------------------------------- /src/store/modules/members.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | const state = { 3 | members:[], 4 | userNum:null, 5 | editStutas:'' //一个状态值 告诉前端是否修改成功 6 | }; 7 | const mutations = { 8 | GET_ALL_MEMBERS(state,payload){ 9 | state.members=payload; 10 | }, 11 | DEL_MEMBERS(state,payload){ 12 | state.members=payload; 13 | }, 14 | GET_USER_NUM(state,payload){ 15 | state.userNum = payload; 16 | }, 17 | EDIT_USER(state,payload){ 18 | state.editStutas = payload; 19 | }, 20 | }; 21 | 22 | const actions = { 23 | editUser({commit},params){ 24 | console.log(params); 25 | axios.get("/api/user/editUser",{ 26 | params:{ 27 | id:params.id, //传入要修改得用户ID 和新值 28 | form:params.form 29 | } 30 | }) 31 | .then(res=>{ 32 | let payload = res; 33 | console.log('editUser:'+ payload); 34 | console.log(res); 35 | commit("EDIT_USER",payload) 36 | }) 37 | .catch(function (error) { 38 | console.log(error); 39 | }); 40 | }, 41 | getUserNum({commit},params){ 42 | axios.get("/api/user/getUserCount") 43 | .then(res=>{ 44 | let payload = res.length; 45 | // console.log('getUserNum:'+ payload); 46 | commit("GET_USER_NUM",payload) 47 | }) 48 | .catch(function (error) { 49 | console.log(error); 50 | }); 51 | }, 52 | getAllMembers({commit},params){ 53 | axios.get("/api/user/getUserAll",{ 54 | params:{ 55 | root:params.root, 56 | pageSize:params.pageSize, 57 | curPage:params.curPage, 58 | } 59 | }) 60 | .then(res=>{ 61 | let payload = res; 62 | // console.log('getAllMembers'); 63 | commit("GET_ALL_MEMBERS",payload) 64 | }) 65 | .catch(function (error) { 66 | console.log(error); 67 | }); 68 | }, 69 | delMembers({commit},params){ 70 | axios.get("/api/user/delUser",{ 71 | params:{ 72 | root:params.root, 73 | id:params.id, 74 | pageSize:params.pageSize, 75 | curPage:params.curPage, 76 | } 77 | }) 78 | .then(res=>{ 79 | let payload = res; 80 | // console.log('delMembers'); 81 | commit("DEL_MEMBERS",payload) 82 | }) 83 | .catch(function (error) { 84 | console.log(error); 85 | }); 86 | } 87 | }; 88 | const getters = { 89 | // 获取按更新状态的数据 90 | members:state => { 91 | return state.members 92 | }, 93 | // 用户总数 94 | userNum:state => { 95 | return state.userNum 96 | }, 97 | // 修改信息 98 | editStutas:state => { 99 | return state.editStutas 100 | } 101 | } 102 | export default{ 103 | state, 104 | mutations, 105 | actions, 106 | getters 107 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 影视追剧后台管理系统 6 | 7 | 8 |
9 |
10 | 75 |
76 | 77 | 78 | 79 | 80 |
81 | 82 |

初次加载,请耐心等待...

83 |
84 |
85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Template version: 1.3.1 3 | // see http://vuejs-templates.github.io/webpack for documentation. 4 | 5 | const path = require('path') 6 | 7 | module.exports = { 8 | dev: { 9 | 10 | // Paths 11 | assetsSubDirectory: 'static', 12 | assetsPublicPath: '/', 13 | // 前端端口与后端端口相连 类似于解决跨域 14 | proxyTable: { 15 | '/api':{ 16 | target: 'http://localhost:3030/api/', 17 | changeOrigin:true, 18 | pathWrite:{ 19 | '^/api':'' 20 | } 21 | } 22 | }, 23 | 24 | // Various Dev Server settings 25 | host: '0.0.0.0', // can be overwritten by process.env.HOST 26 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 27 | autoOpenBrowser: false, 28 | errorOverlay: true, 29 | notifyOnErrors: true, 30 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 31 | 32 | // Use Eslint Loader? 33 | // If true, your code will be linted during bundling and 34 | // linting errors and warnings will be shown in the console. 35 | useEslint: true, 36 | // If true, eslint errors and warnings will also be shown in the error overlay 37 | // in the browser. 38 | showEslintErrorsInOverlay: false, 39 | 40 | /** 41 | * Source Maps 42 | */ 43 | 44 | // https://webpack.js.org/configuration/devtool/#development 45 | devtool: 'cheap-module-eval-source-map', 46 | 47 | // If you have problems debugging vue-files in devtools, 48 | // set this to false - it *may* help 49 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 50 | cacheBusting: true, 51 | 52 | cssSourceMap: true 53 | }, 54 | 55 | build: { 56 | // Template for index.html 57 | index: path.resolve(__dirname, '../dist/index.html'), 58 | 59 | // Paths 60 | assetsRoot: path.resolve(__dirname, '../dist'), 61 | assetsSubDirectory: 'static', 62 | assetsPublicPath: '/', 63 | 64 | /** 65 | * Source Maps 66 | */ 67 | 68 | productionSourceMap: true, 69 | // https://webpack.js.org/configuration/devtool/#production 70 | devtool: '#source-map', 71 | 72 | // Gzip off by default as many popular static hosts such as 73 | // Surge or Netlify already gzip all static assets for you. 74 | // Before setting to `true`, make sure to: 75 | // npm install --save-dev compression-webpack-plugin 76 | productionGzip: false, 77 | productionGzipExtensions: ['js', 'css'], 78 | 79 | // Run the build command with an extra argument to 80 | // View the bundle analyzer report after build finishes: 81 | // `npm run build --report` 82 | // Set to `true` or `false` to always turn it on or off 83 | bundleAnalyzerReport: process.env.npm_config_report 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/pages/home.vue: -------------------------------------------------------------------------------- 1 | 21 | 86 | 114 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 这是一个追剧网站的后台管理系统。模板参考了 [mengdu](https://github.com/mengdu)的项目,并在其基础上加了后台的功能。前端框架采用的`vue`,ui框架用的是`element-ui`,后台的框架用的`express`,数据库是`mysql`。 3 | 4 | 主要的功能是对影片资料的展示,影片的增删改查,对用户信息的展示,用户的增删改查,还有管理员自身资料的管理等等。 并且整个管理后台只支持管理员登录,不支持用户登录以及注册,后期如果涉及到更改,会增加权限分配功能,让用户也可以登录,只是管理的权限不同。(不过就针对这个项目而言,貌似不需要) 5 | 6 | 7 | 项目地址: [vue-admin](https://github.com/liyushilezhi/vue-admin) 8 | 9 | 警告,下面多图预警 10 | 11 | - 登录页 12 | 13 | ![](http://oo9xy1zeh.bkt.clouddn.com/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20180419114012.png) 14 | 15 | 登录页是整个项目的入口,会做用户名密码和验证码的判断,验证码是后台生成的,实现了点击刷新,并且验证码的值保存在cookie当中,方便前端做登录判断 16 | 17 | - 首页 18 | 19 | 登录成功后跳转首页,首页显示了当前注册的总人数和当前收录的总影片数。 20 | 21 | ![](http://oo9xy1zeh.bkt.clouddn.com/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20180419152727.png) 22 | 23 | 咳,ui功底不行啊,,,,将就看吧.. 24 | 25 | - 用户列表 26 | 27 | 用户列表页支持分页查询,支持对用户信息的修改以及用户的删除操作。 28 | 29 | ![](http://oo9xy1zeh.bkt.clouddn.com/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20180419152845.png) 30 | 31 | ![](http://oo9xy1zeh.bkt.clouddn.com/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20180419152947.png) 32 | 33 | ![](http://oo9xy1zeh.bkt.clouddn.com/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20180419153020.png) 34 | 35 | - 影片列表 36 | 37 | 影片列表的功能同用户列表 38 | 39 | ![](http://oo9xy1zeh.bkt.clouddn.com/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20180419153135.png) 40 | 41 | ![](http://oo9xy1zeh.bkt.clouddn.com/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20180419153145.png) 42 | 43 | ![](http://oo9xy1zeh.bkt.clouddn.com/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20180419153154.png) 44 | 45 | - 添加影片 46 | 47 | ![](http://oo9xy1zeh.bkt.clouddn.com/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20180419153511.png) 48 | 49 | - 搜索 50 | 51 | 搜索包括用户搜索以及影片搜索,只做了按名称模糊搜索,如果有时间的话可以做全,原理都是很简单的. 52 | 53 | ![](http://oo9xy1zeh.bkt.clouddn.com/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20180419153648.png) 54 | 55 | - 设置 56 | 57 | 设置页可以对管理员的信息进行修改 58 | 59 | ![](http://oo9xy1zeh.bkt.clouddn.com/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20180419153737.png) 60 | 61 | - 关于 62 | 63 | ![](http://oo9xy1zeh.bkt.clouddn.com/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20180419153830.png) 64 | 65 | 66 | 67 | 后台数据库表: 68 | 69 | 影片数据表: movie 70 | 71 | ![](http://oo9xy1zeh.bkt.clouddn.com/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20180419155133.png) 72 | 73 | 用户数据表: user 74 | 75 | ![](http://oo9xy1zeh.bkt.clouddn.com/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20180419155118.png) 76 | 77 | > 安装 78 | 79 | `git clone git@github.com:liyushilezhi/vue-admin.git` 80 | 81 | `cd vue-admin` 82 | 83 | `cnpm i` 84 | 85 | 开启node后台服务 86 | 87 | `npm start` 88 | 89 | `npm run dev` 90 | 91 | 上面的操作都是基于你有数据库的情况下。 92 | 93 | 在本地新建数据库,结构参照上面的数据表,并且配置`server`文件夹下的`db.js` 94 | 95 | ```javascript 96 | // 数据库连接配置 97 | module.exports = { 98 | mysql: { 99 | host: 'localhost', 100 | user: 'root', 101 | password: '123456', 102 | database: 'moviedb', 103 | port: '3306' 104 | } 105 | } 106 | ``` 107 | 108 | 这时候再执行 `npm start` `npm run dev`即可 109 | 110 | 我的博客: [前端开发小哥](http://neverlove.me) -------------------------------------------------------------------------------- /src/m/navbar/navbar.less: -------------------------------------------------------------------------------- 1 | @nav-name: m-nav; 2 | @navbar-name: m-navbar; 3 | 4 | .@{navbar-name} { 5 | height: 50px; 6 | padding: 0 15px; 7 | background-color: #F8F9FA; 8 | line-height: 50px; 9 | 10 | & &-brand{ 11 | font-weight: bold; 12 | display: inline-block; 13 | margin-right: 15px; 14 | } 15 | & &-toggler{ 16 | margin-top: 10px; 17 | background: none; 18 | outline: none; 19 | border: none; 20 | cursor: pointer; 21 | float: right; 22 | display: none; 23 | 24 | & .m-icon-bar{ 25 | display: block; 26 | width: 22px; 27 | height: 2px; 28 | border-radius: 1px; 29 | margin: 5px 0; 30 | background-color: #646464; 31 | } 32 | } 33 | } 34 | 35 | .@{nav-name} { 36 | display: inline-block; 37 | list-style: none; 38 | margin: 0; 39 | padding: 0; 40 | vertical-align: top; 41 | 42 | &.right{ 43 | float: right; 44 | } 45 | 46 | & &-item { 47 | display: inline-block; 48 | padding: 0 15px; 49 | cursor: pointer; 50 | 51 | & > a { 52 | color: inherit; 53 | margin: 0 -15px; 54 | padding: 0 15px; 55 | line-height: inherit; 56 | display: inherit; 57 | } 58 | &.active { 59 | background-color: rgba(0,0,0, 0.05); 60 | } 61 | &.disabled { 62 | color: #b3b3b3; 63 | cursor: not-allowed; 64 | } 65 | } 66 | } 67 | 68 | @media (max-width: 767.98px) { 69 | .@{navbar-name} { 70 | & &-brand { 71 | display: block; 72 | margin-right: 0; 73 | } 74 | & &-toggler { 75 | display: block; 76 | } 77 | } 78 | .@{navbar-name}.open { 79 | height: auto; 80 | } 81 | .@{navbar-name} .@{nav-name} { 82 | display: none; 83 | } 84 | .@{navbar-name}.open .@{nav-name} { 85 | display: block; 86 | float: none !important; 87 | &-item{ 88 | display: block; 89 | } 90 | } 91 | } 92 | 93 | /**theme**/ 94 | .@{navbar-name} { 95 | &.dark { 96 | background-color: #333940; 97 | color: #e0e0e0; 98 | 99 | & .@{nav-name} .@{nav-name}-item.disabled { 100 | color: rgba(255, 255, 255, 0.42); 101 | } 102 | } 103 | &.info { 104 | background-color: #12AFE3; 105 | color: #f7f7f7; 106 | & .@{nav-name} .@{nav-name}-item.disabled { 107 | color: rgba(255, 255, 255, 0.42); 108 | } 109 | } 110 | &.primary { 111 | background-color: #7952B3; 112 | color: #f7f7f7; 113 | & .@{nav-name} .@{nav-name}-item.disabled { 114 | color: rgba(255, 255, 255, 0.42); 115 | } 116 | } 117 | &.danger { 118 | background-color: #F56C6C; 119 | color: #f7f7f7; 120 | & .@{nav-name} .@{nav-name}-item.disabled { 121 | color: rgba(255, 255, 255, 0.42); 122 | } 123 | } 124 | &.warning { 125 | background-color: #F9AA40; 126 | color: #f7f7f7; 127 | & .@{nav-name} .@{nav-name}-item.disabled { 128 | color: rgba(255, 255, 255, 0.42); 129 | } 130 | } 131 | &.success { 132 | background-color: #03B976; 133 | color: #f7f7f7; 134 | & .@{nav-name} .@{nav-name}-item.disabled { 135 | color: rgba(255, 255, 255, 0.42); 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/assets/css/main.css: -------------------------------------------------------------------------------- 1 | html, 2 | body{ 3 | background-color: #fff; 4 | } 5 | body{ 6 | padding: 0; 7 | margin: 0; 8 | color: #464C5B; 9 | font-size: 14px; 10 | line-height: 1.4; 11 | font-family: "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif; 12 | /*font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol", "Microsoft YaHei";*/ 13 | /*font-family: Roboto, Segoe UI, "Microsoft YaHei", Helvetica Neue For Number,-apple-system,BlinkMacSystemFont,PingFang SC,Hiragino Sans GB, "Helvetica Neue", Helvetica, Arial, "PingFang SC", "Hiragino Sans GB", "Heiti SC", "WenQuanYi Micro Hei", sans-serif;*/ 14 | } 15 | div{ 16 | box-sizing: border-box; 17 | } 18 | a{ 19 | text-decoration: none; 20 | color: #0366d6; 21 | } 22 | pre,code{ 23 | font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace, "Microsoft YaHei"; 24 | } 25 | code{ 26 | color: #d03bc8; 27 | padding: 0.1em 0.4em; 28 | margin: 0; 29 | background-color: rgba(111, 66, 193, 0.07); 30 | border-radius: 3px; 31 | } 32 | ::selection{ 33 | background: #CBD1DE; 34 | } 35 | .f-left{ 36 | float: left; 37 | } 38 | .f-right{ 39 | float: right; 40 | } 41 | .clearfix:before, 42 | .clearfix:after{ 43 | content: ""; 44 | display: table; 45 | clear: both; 46 | } 47 | .text-center{ 48 | text-align: center; 49 | } 50 | 51 | 52 | 53 | 54 | 55 | 56 | .priview-box{ 57 | margin-bottom: 50px; 58 | } 59 | .priview-box .description{ 60 | color: #62676C; 61 | } 62 | 63 | 64 | 65 | 66 | 67 | .page-body{ 68 | padding: 15px; 69 | } 70 | .page-body .page-header{ 71 | background: #fff; 72 | padding: 15px; 73 | margin: -15px -15px 15px -15px; 74 | } 75 | .page-body .page-header .page-title{ 76 | font-size: 18px; 77 | margin: 0; 78 | margin-bottom: 15px; 79 | color: #585A5F; 80 | } 81 | 82 | .page-body h1{ 83 | font-size: 2rem; 84 | font-weight: 400; 85 | margin: 20px 0; 86 | } 87 | .page-body h2{ 88 | font-size: 1.5rem; 89 | font-weight: 400; 90 | } 91 | 92 | 93 | 94 | 95 | .box{ 96 | background: #fff; 97 | border-radius: 3px; 98 | padding: 15px; 99 | } 100 | .box .box-header:before, 101 | .box .box-header:after, 102 | .box .box-footer:before, 103 | .box .box-footer:after{ 104 | content: ""; 105 | display: table; 106 | clear: both; 107 | } 108 | .box .box-header{ 109 | padding-bottom: 15px; 110 | } 111 | .box .box-footer{ 112 | padding: 15px 0; 113 | } 114 | .box .box-header .box-title{ 115 | font-size: 18px; 116 | font-weight: 100; 117 | margin: 0; 118 | } 119 | 120 | .progress-bg{ 121 | margin: 0 auto; 122 | width: 60%; 123 | height: 4px; 124 | background: rgba(245,245,245, 0.2); 125 | overflow: hidden; 126 | } 127 | .progress-bg .progress{ 128 | width: 50%; 129 | height: 4px; 130 | margin-left: -50%; 131 | background: #19a05e; 132 | float: left; 133 | animation: progress 1s linear infinite; 134 | } 135 | 136 | @keyframes progress { 137 | 0% { 138 | margin-left: -50%; 139 | } 140 | 100% { 141 | margin-left: 100%; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/m/keyboard/dict-helper.js: -------------------------------------------------------------------------------- 1 | import dicts from './dict' 2 | const dict = { 3 | pyhz: dicts, 4 | azhz: {} 5 | } 6 | 7 | var pykeys = Object.keys(dict.pyhz) 8 | // window.pykeys = pykeys 9 | // window.dict = dict 10 | // 构造a~z的汉字映射 11 | for (var i = 97; i < 123; i++) { 12 | var ch = String.fromCharCode(i) 13 | var pylist = pykeys.filter(key => key.indexOf(ch) === 0) 14 | if (pylist.length > 0) { 15 | dict.azhz[ch] = '' 16 | pylist.forEach(key => { 17 | dict.azhz[ch] += dict.pyhz[key] 18 | }) 19 | } else { 20 | dict.azhz[ch] = ch 21 | } 22 | } 23 | 24 | // 从拼音获取汉字 25 | function getPinyinHanzi (pinyin) { 26 | if (dict.azhz[pinyin]) { 27 | return dict.azhz[pinyin] 28 | } else if (dict.pyhz[pinyin]) { 29 | return dict.pyhz[pinyin] 30 | } else { 31 | return '' 32 | } 33 | } 34 | // 分词 35 | // function pinyinAnalysis (pinyin = '') { 36 | // var result = { 37 | // input: '', 38 | // result: [] 39 | // } 40 | // var chs = pinyin.split('') 41 | // for (var i = 0; i < chs.length; i++) { 42 | // var ch = chs[i] 43 | // result.input += ch 44 | // var keys = pykeys.filter(pyk => pyk === result.input) 45 | 46 | // if (keys.length > 1) { 47 | // if (keys[0] === ch) { 48 | 49 | // } 50 | // } else if (keys.length === 1) { 51 | // result.result.push(result.input) 52 | // result.input = '' 53 | // } else { 54 | // result.result[result.result.length - 1] = result.input 55 | // } 56 | // } 57 | // return result 58 | // } 59 | 60 | 61 | function pinyinAnalysis (pinyin) { 62 | var result = { 63 | input: '', 64 | result: [] 65 | } 66 | if (getPinyinHanzi(pinyin)) { 67 | result.input = pinyin 68 | result.result = pinyin 69 | } 70 | var temp = '' 71 | for (var i = 0; i < pinyin.length; i++) { 72 | temp += pinyin[i] 73 | if (!getPinyinHanzi(temp)) continue 74 | // flag表示如果当前能匹配到结果、并且往后5个字母不能匹配结果,因为最长可能是5个字母,如 zhuang 75 | var flag = false 76 | if ((i + 1) < pinyin.length) { 77 | for (var j = 1; j <= 5 && (i + j) < pinyin.length; j++) { 78 | if (getPinyinHanzi(pinyin.substr(0, i + j + 1))) { 79 | flag = true 80 | break 81 | } 82 | } 83 | } 84 | if (!flag) { 85 | result.result = pinyin.substr(0, i + 1) + '\'' + pinyin.substr(i + 1) 86 | } 87 | } 88 | result.input = pinyin 89 | return result 90 | } 91 | 92 | 93 | function getHanzi (py) { 94 | var result = { 95 | input: '', 96 | pinyin: '', 97 | result: [] 98 | } 99 | if (!py) return result 100 | py = py.toLocaleLowerCase() 101 | if (!/^[a-zA-Z]*$/.test(py)) { 102 | result.result = [py].concat(py.split('')) 103 | } else { 104 | if (py.length === 1) { 105 | result.result = dict.azhz[py].split('') 106 | } else if (dict.pyhz[py]) { 107 | result.result = dict.pyhz[py].split('') 108 | } else { 109 | result.result = [py].concat(py.split('')) 110 | } 111 | } 112 | result.input = py 113 | result.pinyin = py 114 | return result 115 | } 116 | 117 | export default { 118 | dict, 119 | getHanzi, 120 | getPinyinHanzi, 121 | pinyinAnalysis 122 | } 123 | -------------------------------------------------------------------------------- /src/components/vmenu/menu.vue: -------------------------------------------------------------------------------- 1 | 61 | 114 | 127 | -------------------------------------------------------------------------------- /src/m/container/container.less: -------------------------------------------------------------------------------- 1 | 2 | @container: m-container; 3 | 4 | @row-name: m-row; 5 | @columns-name: m-col; 6 | @columns-pading: 15px; 7 | @grid-count: 12; 8 | 9 | @screen-sm-min: 768px; 10 | @screen-md-min: 992px; 11 | @screen-lg-min: 1200px; 12 | 13 | .@{container}, 14 | .@{container}-fluid{ 15 | padding-left: @columns-pading; 16 | padding-right: @columns-pading; 17 | margin-right: auto; 18 | margin-left: auto; 19 | min-width: 960px;/*为了兼容不支持媒体选择的浏览器*/ 20 | -webkit-transition:width 0.9s cubic-bezier(1,-0.02, 0, 1.04);// for Safari and Chrome 21 | -moz-transition:width 0.9s cubic-bezier(1,-0.02, 0, 1.04);// for Firefox 22 | -o-transition:width 0.9s cubic-bezier(1,-0.02, 0, 1.04);// for Opera 23 | -ms-transition:width 0.9s cubic-bezier(1,-0.02, 0, 1.04);// for ie 24 | transition:width 0.5s cubic-bezier(1,-0.02, 0, 1.04); 25 | -webkit-box-sizing: border-box; 26 | box-sizing:border-box; 27 | -moz-box-sizing:border-box; 28 | } 29 | .@{container}-fluid{ 30 | min-width: 0; 31 | width: 100%; 32 | } 33 | .@{row-name}{ 34 | min-height: 1px; 35 | margin-left: -@columns-pading; 36 | margin-right: -@columns-pading; 37 | clear: both; 38 | &:before, 39 | &:after{ 40 | content: ""; 41 | display: table; 42 | clear: both; 43 | } 44 | } 45 | // 列基础css 46 | .columns-base-css() { 47 | position: relative; 48 | min-height: 1px; 49 | padding-right: @columns-pading; 50 | padding-left: @columns-pading; 51 | 52 | -webkit-box-sizing: border-box; 53 | box-sizing:border-box; 54 | -moz-box-sizing:border-box; 55 | } 56 | // 循环列,设置基础css 57 | .make-grid-columns(@len: @grid-count) { 58 | .col(@i) { 59 | @classList: ~".@{columns-name}-xs-@{i},.@{columns-name}-sm-@{i},.@{columns-name}-md-@{i},.@{columns-name}-lg-@{i}"; 60 | .col(@i + 1, ~"@{classList}"); 61 | } 62 | .col(@i, @list) when (@i =< @len){ 63 | @classList: ~".@{columns-name}-xs-@{i},.@{columns-name}-sm-@{i},.@{columns-name}-md-@{i},.@{columns-name}-lg-@{i}"; 64 | .col(@i + 1, ~"@{classList},@{list}"); 65 | } 66 | .col(@i, @list) when (@i > @len) { 67 | @{list} { 68 | .columns-base-css(); 69 | } 70 | } 71 | .col(1) 72 | } 73 | .make-grid-columns(@grid-count); 74 | 75 | // 循环生成列 76 | .make-columns-loop(@type, @n, @i: 1) when (@i <= @n){ 77 | @col-class-name: ~"@{columns-name}-@{type}"; 78 | .@{col-class-name}-@{i}{ 79 | width: @i/@n*100%; 80 | float: left; 81 | } 82 | // 偏移 83 | .@{col-class-name}-offset-@{i}{ 84 | margin-left: @i/@n*100%; 85 | } 86 | // 排序 87 | .@{col-class-name}-pull-@{i}{ 88 | right: @i/@n*100%; 89 | } 90 | .@{col-class-name}-push-@{i}{ 91 | left: @i/@n*100%; 92 | } 93 | .make-columns-loop(@type, @n, (@i + 1)); 94 | } 95 | .make-columns-loop(xs, @grid-count); 96 | 97 | // 媒体查询 98 | .@{container}{ 99 | @media (max-width: @screen-sm-min) { 100 | min-width: 0; 101 | } 102 | @media (min-width: @screen-sm-min) { 103 | width: 750px; 104 | min-width: 0; 105 | } 106 | @media (min-width: @screen-md-min) { 107 | width: 970px; 108 | min-width: 0; 109 | } 110 | @media (min-width: @screen-lg-min) { 111 | width: 1170px; 112 | min-width: 0; 113 | } 114 | } 115 | // 媒体查询设置对应列类型css 116 | @media (min-width: @screen-sm-min) { 117 | .make-columns-loop(sm, @grid-count); 118 | } 119 | @media (min-width: @screen-md-min) { 120 | .make-columns-loop(md, @grid-count); 121 | } 122 | @media (min-width: @screen-lg-min) { 123 | .make-columns-loop(lg, @grid-count); 124 | } 125 | -------------------------------------------------------------------------------- /src/pages/search-movie.vue: -------------------------------------------------------------------------------- 1 | 43 | 70 | 107 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import Vue from 'vue' 4 | import 'babel-polyfill' 5 | import ElementUI from 'element-ui' 6 | import Axios from 'axios' 7 | import NProgress from 'nprogress' 8 | 9 | import App from './App' 10 | import store from './store' 11 | import router from './router' 12 | import 'element-ui/lib/theme-chalk/index.css' 13 | import '@/assets/font-awesome-4.7.0/css/font-awesome.min.css' 14 | import '@/assets/css/main.css' 15 | import '@/assets/css/scrollbar.css' 16 | import '@/assets/css/reset.css' 17 | import 'nprogress/nprogress.css' 18 | import 'animate.css' 19 | 20 | 21 | import DropdownPlugin from './m/dropdown' 22 | import NavbarPlugin from './m/navbar' 23 | import ContextMenuPlugin from './m/context-menu' 24 | import MButton from '@/m/button' 25 | import MSwitch from '@/m/switch' 26 | import MAlert from '@/m/alert' 27 | import MCheckbox from '@/m/checkbox' 28 | import MInput from '@/m/input' 29 | import MLoading from '@/m/loading' 30 | import Mkeyboard from '@/m/keyboard' 31 | import MNumberkeyboard from '@/m/number-keyboard' 32 | import MBox from '@/m/box' 33 | import MBackTop from '@/m/back-top' 34 | import MLoader from '@/m/loader' 35 | import MContainer from '@/m/container' 36 | 37 | Vue.use(ElementUI) 38 | 39 | Vue.use(DropdownPlugin) 40 | Vue.use(NavbarPlugin) 41 | Vue.use(ContextMenuPlugin) 42 | Vue.use(MButton) 43 | Vue.use(MSwitch) 44 | Vue.use(MAlert) 45 | Vue.use(MCheckbox) 46 | Vue.use(MInput) 47 | Vue.use(MLoading) 48 | Vue.use(Mkeyboard) 49 | Vue.use(MNumberkeyboard) 50 | Vue.use(MBox) 51 | Vue.use(MBackTop) 52 | Vue.use(MLoader) 53 | Vue.use(MContainer) 54 | 55 | var whiteList = ['demo', 'login'] 56 | router.beforeEach((to, from, next) => { 57 | NProgress.start() 58 | var token = sessionStorage.getItem('token') 59 | if (!token && whiteList.indexOf(to.name) === -1) { 60 | app && app.$message.warning('未授权,请登陆授权后继续') 61 | NProgress.done() 62 | return next({name: 'login'}) 63 | } 64 | return next() 65 | }) 66 | 67 | router.afterEach(transition => { 68 | setTimeout(() => { 69 | NProgress.done() 70 | }) 71 | }) 72 | 73 | 74 | window.APP_INFO = process.env.APP_INFO 75 | // status < 500 不会抛错误 76 | Axios.defaults.validateStatus = status => { 77 | return status < 500 78 | } 79 | // 设置请求token 80 | Axios.interceptors.request.use(config => { 81 | var token = sessionStorage.getItem('token') 82 | config.headers['Authorization'] = 'Bearer ' + token 83 | // console.log(config) 84 | return config 85 | }) 86 | 87 | // 接口错误拦截 88 | Axios.interceptors.response.use(res => { 89 | // console.log(res) 90 | if (res.status === 401) { 91 | app && app.$message({ 92 | type: 'warning', 93 | message: '登录身份过期,请重新登录。' 94 | }) 95 | sessionStorage.removeItem('token') 96 | sessionStorage.removeItem('user') 97 | router.push({name: 'login'}) 98 | return Promise.reject(new Error('身份过期')) 99 | } else { 100 | return res.data 101 | } 102 | }, err => { 103 | app.$notify.error({ 104 | title: '服务错误', 105 | message: '服务器响应错误 ' + err.message 106 | }) 107 | return Promise.reject(err) 108 | }) 109 | 110 | 111 | Vue.prototype.$http = Axios 112 | Vue.http = Axios 113 | 114 | 115 | Vue.config.productionTip = false 116 | 117 | /* eslint-disable no-new */ 118 | var app = new Vue({ 119 | el: '#app', 120 | store, 121 | router, 122 | template: '', 123 | components: { App } 124 | }) 125 | 126 | window.app = app 127 | 128 | -------------------------------------------------------------------------------- /src/m/number-keyboard/number-keyboard.vue: -------------------------------------------------------------------------------- 1 | 29 | 83 | 137 | -------------------------------------------------------------------------------- /src/m/alert/alert.vue: -------------------------------------------------------------------------------- 1 | 14 | 47 | 133 | -------------------------------------------------------------------------------- /src/m/back-top/back-top.vue: -------------------------------------------------------------------------------- 1 | 11 | 114 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "admin", 3 | "version": "1.0.4", 4 | "description": "admin", 5 | "author": "是李宇呀丶", 6 | "private": true, 7 | "app": { 8 | "name": "追剧管家后台管理系统" 9 | }, 10 | "license": "MIT", 11 | "scripts": { 12 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 13 | "start": "node ./server/index", 14 | "publish": "node build/publish.js", 15 | "unit": "jest --config test/unit/jest.conf.js --coverage", 16 | "e2e": "node test/e2e/runner.js", 17 | "test": "npm run unit && npm run e2e", 18 | "lint": "eslint --ext .js,.vue src test/unit test/e2e/specs", 19 | "build": "node build/build.js" 20 | }, 21 | "dependencies": { 22 | "animate.css": "^3.5.2", 23 | "axios": "^0.17.1", 24 | "babel-polyfill": "^6.26.0", 25 | "cookie-parser": "^1.4.3", 26 | "element-ui": "^2.0.11", 27 | "express": "^4.16.3", 28 | "less": "^2.7.3", 29 | "less-loader": "^4.1.0", 30 | "mysql": "^2.15.0", 31 | "nprogress": "^0.2.0", 32 | "svg-captcha": "^1.3.11", 33 | "vue": "^2.5.2", 34 | "vue-router": "^3.0.1", 35 | "vuex": "^3.0.1" 36 | }, 37 | "devDependencies": { 38 | "autoprefixer": "^7.1.2", 39 | "babel-core": "^6.22.1", 40 | "babel-eslint": "^8.2.1", 41 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 42 | "babel-jest": "^21.0.2", 43 | "babel-loader": "^7.1.1", 44 | "babel-plugin-dynamic-import-node": "^1.2.0", 45 | "babel-plugin-syntax-jsx": "^6.18.0", 46 | "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0", 47 | "babel-plugin-transform-runtime": "^6.22.0", 48 | "babel-plugin-transform-vue-jsx": "^3.5.0", 49 | "babel-preset-env": "^1.3.2", 50 | "babel-preset-stage-2": "^6.22.0", 51 | "babel-register": "^6.22.0", 52 | "chalk": "^2.0.1", 53 | "chromedriver": "^2.27.2", 54 | "copy-webpack-plugin": "^4.0.1", 55 | "cross-spawn": "^5.0.1", 56 | "css-loader": "^0.28.0", 57 | "eslint": "^4.15.0", 58 | "eslint-config-standard": "^10.2.1", 59 | "eslint-friendly-formatter": "^3.0.0", 60 | "eslint-loader": "^1.7.1", 61 | "eslint-plugin-html": "^4.0.2", 62 | "eslint-plugin-import": "^2.7.0", 63 | "eslint-plugin-node": "^5.2.0", 64 | "eslint-plugin-promise": "^3.4.0", 65 | "eslint-plugin-standard": "^3.0.1", 66 | "eslint-plugin-vue": "^4.0.0", 67 | "extract-text-webpack-plugin": "^3.0.0", 68 | "file-loader": "^1.1.4", 69 | "friendly-errors-webpack-plugin": "^1.6.1", 70 | "html-webpack-plugin": "^2.30.1", 71 | "jest": "^22.0.4", 72 | "jest-serializer-vue": "^0.3.0", 73 | "less": "^2.7.3", 74 | "less-loader": "^4.0.5", 75 | "nightwatch": "^0.9.12", 76 | "node-notifier": "^5.1.2", 77 | "optimize-css-assets-webpack-plugin": "^3.2.0", 78 | "ora": "^1.2.0", 79 | "portfinder": "^1.0.13", 80 | "postcss-import": "^11.0.0", 81 | "postcss-loader": "^2.0.8", 82 | "postcss-url": "^7.2.1", 83 | "rimraf": "^2.6.0", 84 | "selenium-server": "^3.0.1", 85 | "semver": "^5.3.0", 86 | "shelljs": "^0.7.6", 87 | "uglifyjs-webpack-plugin": "^1.1.1", 88 | "url-loader": "^0.5.8", 89 | "vue-jest": "^1.0.2", 90 | "vue-loader": "^13.3.0", 91 | "vue-style-loader": "^3.0.1", 92 | "vue-template-compiler": "^2.5.2", 93 | "webpack": "^3.6.0", 94 | "webpack-bundle-analyzer": "^2.9.0", 95 | "webpack-dev-server": "^2.9.1", 96 | "webpack-merge": "^4.1.0" 97 | }, 98 | "engines": { 99 | "node": ">= 6.0.0", 100 | "npm": ">= 3.0.0" 101 | }, 102 | "browserslist": [ 103 | "> 1%", 104 | "last 2 versions", 105 | "not ie <= 8" 106 | ] 107 | } 108 | -------------------------------------------------------------------------------- /src/pages/editPwd.vue: -------------------------------------------------------------------------------- 1 | 37 | 100 | 120 | -------------------------------------------------------------------------------- /src/m/checkbox/checkbox.vue: -------------------------------------------------------------------------------- 1 | 13 | 139 | 142 | -------------------------------------------------------------------------------- /src/components/app-view.vue: -------------------------------------------------------------------------------- 1 | 38 | 73 | 141 | -------------------------------------------------------------------------------- /src/utils/img-cutter.js: -------------------------------------------------------------------------------- 1 | function image (src) { 2 | return new Promise((resolve, reject) => { 3 | var img = new Image() 4 | img.onload = () => { 5 | resolve({ 6 | scr: src, 7 | el: img, 8 | width: img.width, 9 | height: img.height 10 | }) 11 | } 12 | img.onerror = () => { 13 | reject(new Error('图片加载失败')) 14 | } 15 | img.src = src 16 | }) 17 | } 18 | function ImgCutter () {} 19 | 20 | function copyCanvas (data) { 21 | var canvas = document.createElement('CANVAS') 22 | canvas.height = data.height 23 | canvas.width = data.width 24 | var ctx = canvas.getContext('2d') 25 | ctx.putImageData(data, 0, 0, 0, 0, data.width, data.height) 26 | return ctx 27 | } 28 | function Cutter (img) { 29 | this.img = img 30 | var canvas = document.createElement('CANVAS') 31 | this.canvas = canvas 32 | this.ctx = canvas.getContext('2d') 33 | this.init() 34 | } 35 | // 设置图片 36 | ImgCutter.prototype.src = function (src) { 37 | return new Promise((resolve) => { 38 | if (!src) reject(new Error('图片路径不能为空')) 39 | image(src).then(img => { 40 | resolve(new Cutter(img)) 41 | }) 42 | }) 43 | } 44 | 45 | ImgCutter.ImgCutter = ImgCutter 46 | 47 | /** 48 | * 初始化 49 | **/ 50 | Cutter.prototype.init = function () { 51 | this.canvas.width = this.img.width 52 | this.canvas.height = this.img.height 53 | this.ctx.drawImage(this.img.el, 0, 0) 54 | return this 55 | } 56 | 57 | /** 58 | * 缩放 59 | * @param {zoom} 缩放系数,注意canvas最大尺寸有上限 60 | * @return {this} 61 | **/ 62 | Cutter.prototype.zoom = function (zoom) { 63 | if (typeof zoom !== 'number') { 64 | console.wran('zoom 必须是 number') 65 | zoom = 1 66 | } 67 | // 保存上一帧,canvas修改宽度需要重绘 68 | var imageData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height) 69 | var temp = copyCanvas(imageData) 70 | this.canvas.width = this.canvas.width * zoom 71 | this.canvas.height = this.canvas.height * zoom 72 | this.ctx.drawImage(temp.canvas, 0, 0, this.canvas.width, this.canvas.height) 73 | return this 74 | } 75 | 76 | /** 77 | * 指定尺寸 78 | * @param {w} 宽度,必须 79 | * @param {h} 高度,可选,如果为空,宽度默认为宽度 80 | * @return {this} 81 | **/ 82 | Cutter.prototype.size = function (w, h) { 83 | var width = Math.abs(parseInt(w)) 84 | var height = Math.abs(parseInt(h)) 85 | var imageData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height) 86 | var temp = copyCanvas(imageData) 87 | if (width && height) { 88 | this.canvas.width = width 89 | this.canvas.height = height 90 | } else if (width && !height) { 91 | this.canvas.width = width 92 | this.canvas.height = width 93 | } 94 | this.ctx.drawImage(temp.canvas, 0, 0, this.canvas.width, this.canvas.height) 95 | return this 96 | } 97 | 98 | /** 99 | * 从x,y开始裁剪图片,尺寸w,h 100 | * @param {x} 开始位置x 101 | * @param {y} 开始位置y 102 | * @param {w} 截取宽度 103 | * @param {h} 截取高度 104 | **/ 105 | Cutter.prototype.clip = function (x, y, w, h) { 106 | var left = Math.abs(parseInt(x)) 107 | var top = Math.abs(parseInt(y)) 108 | var width = Math.abs(parseInt(w)) || this.canvas.width 109 | var height = Math.abs(parseInt(h)) || this.canvas.height 110 | var imageData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height) 111 | var temp = copyCanvas(imageData) 112 | this.canvas.width = width 113 | this.canvas.height = height 114 | this.ctx.drawImage(temp.canvas, left, top, width, height, 0, 0, this.canvas.width, this.canvas.height) 115 | return this 116 | } 117 | 118 | /** 119 | * 导出Blob 120 | * @param {mime} 图片类型,默认image/png 121 | * @param {quality} 图片质量,0~1;默认1 122 | * @return {Blob} 123 | **/ 124 | Cutter.prototype.toBlob = function (mime, quality) { 125 | return new Promise((resolve, reject) => { 126 | if (!this.canvas.toBlob) reject(new Error('canvas对象不支持toBlob方法')) 127 | this.canvas.toBlob(blob => resolve(blob), mime, quality) 128 | }) 129 | } 130 | 131 | /** 132 | * 导出base64 133 | * @param {mime} 图片类型,默认image/png 134 | * @param {quality} 图片质量,0~1;默认1 135 | * @return {String} 136 | **/ 137 | Cutter.prototype.toBase64 = function (mime, quality) { 138 | return new Promise((resolve, reject) => { 139 | var base64 = this.canvas.toDataURL(mime, quality) 140 | resolve(base64) 141 | }) 142 | } 143 | 144 | const imgcutter = new ImgCutter 145 | export { 146 | imgcutter as default, 147 | image, 148 | Cutter 149 | } 150 | -------------------------------------------------------------------------------- /src/components/app-header.vue: -------------------------------------------------------------------------------- 1 | 77 | 139 | 151 | -------------------------------------------------------------------------------- /src/m/checkbox/checkbox.less: -------------------------------------------------------------------------------- 1 | // 类前缀 2 | @checkbox: m-checkbox; 3 | // 未选中背景 4 | @c-unchecked-gb-color: #fff; 5 | // 未选中边框 6 | @c-unchecked-bd-color: #C5CFDC; 7 | // hover边框 8 | @c-hover-bd-color: #20A0FF; 9 | // 选中背景 10 | @c-checked-bg-color: #3399FF; 11 | // 禁用边框 12 | @c-disabled-bd-color: #E4E4E4; 13 | // 禁用字体 14 | @c-disabled-text-color: #BFBFBF; 15 | // 选中且禁用 16 | @c-disabled-checked-bg-color: #b1b9c1; 17 | 18 | .@{checkbox}{ 19 | display: inline-block; 20 | position: relative; 21 | min-width: 18px; 22 | margin: 10px; 23 | line-height: 18px; 24 | font-size: 14px; 25 | cursor: pointer; 26 | 27 | user-select: none; 28 | moz-user-select: -moz-none; 29 | -moz-user-select: none; 30 | -o-user-select:none; 31 | -khtml-user-select:none; 32 | -webkit-user-select:none; 33 | -ms-user-select:none; 34 | &&-none-text{ 35 | .@{checkbox}-text{ 36 | display: none; 37 | } 38 | } 39 | & input[type=checkbox]{ 40 | padding: 0; 41 | margin: 0; 42 | width: 0; 43 | height: 0; 44 | margin-right: 17px; 45 | cursor: inherit; 46 | & ~ .@{checkbox}-bg{ 47 | content: ''; 48 | position: absolute; 49 | top: 0px; 50 | left: 0px; 51 | display: block; 52 | width: 18px; 53 | height: 18px; 54 | background: @c-unchecked-gb-color; 55 | border: solid 1px @c-unchecked-bd-color; 56 | border-radius: 3px; 57 | box-sizing: border-box; 58 | cursor: inherit; 59 | transition: background 0.2s ease; 60 | } 61 | & ~ .@{checkbox}-mark{ 62 | content: ""; 63 | display: block; 64 | position: absolute; 65 | top: 9px; 66 | left: 9px; 67 | width: 0px; 68 | height: 0px; 69 | border-radius: 1px; 70 | background: none; 71 | transform: rotate(45deg) scaleY(0); 72 | transition: transform .15s cubic-bezier(.71,-.46,.88,.6); 73 | transform-origin: center; 74 | box-sizing: content-box; 75 | cursor: inherit; 76 | } 77 | & ~ .@{checkbox}-text{ 78 | color: inherit; 79 | opacity: .6; 80 | } 81 | &:hover ~ .@{checkbox}-bg{ 82 | border-color: @c-hover-bd-color; 83 | } 84 | &:hover ~ .@{checkbox}-text{ 85 | opacity: 1; 86 | } 87 | &:checked ~ .@{checkbox}-bg{ 88 | border-color: @c-checked-bg-color; 89 | background: @c-checked-bg-color; 90 | } 91 | &:checked ~ .@{checkbox}-mark{ 92 | border: 2px solid #fff; 93 | border-left: 0; 94 | border-top: 0; 95 | left: 6px; 96 | top: 1px; 97 | width: 4px; 98 | height: 9px; 99 | transform: rotate(45deg) scaleY(1); 100 | } 101 | &:checked ~ .@{checkbox}-text{ 102 | opacity: 1; 103 | } 104 | &:disabled ~ .@{checkbox}-bg{ 105 | border-color: @c-disabled-bd-color !important; 106 | cursor: not-allowed; 107 | } 108 | &:checked:disabled ~ .@{checkbox}-bg{ 109 | border-color: @c-disabled-checked-bg-color !important; 110 | background: @c-disabled-checked-bg-color !important; 111 | } 112 | &:checked:disabled ~ .@{checkbox}-mark{ 113 | cursor: not-allowed; 114 | } 115 | &:disabled ~ .@{checkbox}-text{ 116 | color: @c-disabled-text-color; 117 | cursor: not-allowed; 118 | } 119 | } 120 | // 选中部分 121 | &&-portion input[type=checkbox]{ 122 | &:checked ~ .@{checkbox}-bg{ 123 | border-color: @c-checked-bg-color; 124 | background: @c-unchecked-gb-color; 125 | } 126 | &:checked ~ .@{checkbox}-mark{ 127 | border: none; 128 | width: 10px; 129 | height: 10px; 130 | background: @c-checked-bg-color; 131 | transform: rotate(0deg); 132 | top: 4px; 133 | left: 4px; 134 | border-radius: 2px; 135 | } 136 | } 137 | // 使用主题 138 | .make-checkbox-theme(primary, #7266BA, #7266BA); 139 | .make-checkbox-theme(success, #03B976, #24D695); 140 | .make-checkbox-theme(danger, #D9534F, #E55552); 141 | .make-checkbox-theme(warning, #F0AD4E, #F0AD4E); 142 | 143 | } 144 | /** 145 | * 定义主题 146 | * @theme 主题后缀 147 | * @c-checked-bg-color 选中背景 148 | * @c-hover-bd-color hover边框 149 | **/ 150 | .make-checkbox-theme(@theme, @c-checked-bg-color, @c-hover-bd-color) { 151 | &&-@{theme} input[type=checkbox]:hover ~ .@{checkbox}-bg{ 152 | border-color: @c-hover-bd-color; 153 | } 154 | &&-@{theme} input[type=checkbox]:checked ~ .@{checkbox}-bg{ 155 | border-color: @c-checked-bg-color; 156 | background: @c-checked-bg-color; 157 | } 158 | &&-@{theme}&-portion input[type=checkbox]{ 159 | &:checked ~ .@{checkbox}-bg{ 160 | border-color: @c-checked-bg-color; 161 | background: @c-unchecked-gb-color; 162 | } 163 | &:checked ~ .@{checkbox}-mark{ 164 | background: @c-checked-bg-color; 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/m/switch/switch.less: -------------------------------------------------------------------------------- 1 | // 类名前缀 2 | @switch: m-switch; 3 | // 默认尺寸 4 | @switch-width: 48px; 5 | @switch-height: @switch-width/2; 6 | // 小尺寸 7 | @switch-sm-width: 30px; 8 | @switch-sm-height: 15px; 9 | // 尺寸 10 | @switch-lg-width: 60px; 11 | @switch-lg-height: 30px; 12 | 13 | // border 这里并不是真的border 14 | @switch-border-width: 2px; 15 | 16 | // 未选中背景,所有统一使用此颜色 17 | @switch-bgColor: #CED8E3; 18 | // 状态远点颜色,所有统一采用此颜色 19 | @switch-color: #fff; 20 | 21 | 22 | // 默认情景 23 | @switch-checked-bgColor: #36C1FA; 24 | 25 | @switch-success-checked-bgColor: #03B976; 26 | @switch-danger-checked-bgColor: #D9534F; 27 | @switch-warning-checked-bgColor: #F0AD4E; 28 | @switch-primary-checked-bgColor: #7266BA; 29 | 30 | .@{switch}{ 31 | display: inline-block; 32 | width: @switch-width; 33 | height: @switch-height; 34 | border-radius: @switch-height; 35 | vertical-align: middle; 36 | position: relative; 37 | cursor: pointer; 38 | box-sizing: border-box; 39 | overflow: hidden; 40 | } 41 | 42 | .@{switch} .@{switch}-trigger{ 43 | width: 0; 44 | height: 0; 45 | overflow: hidden; 46 | & ~ .@{switch}-bg{ 47 | width: 100%; 48 | height: 100%; 49 | top: 0; 50 | left: 0; 51 | background: @switch-bgColor; 52 | position: absolute; 53 | box-sizing: border-box; 54 | cursor: pointer; 55 | transition: background .2s ease-in-out; 56 | } 57 | /*边长*/ 58 | @squareSide: (@switch-height)-(@switch-border-width*2); 59 | & ~ .@{switch}-inner{ 60 | content: ''; 61 | position: absolute; 62 | width: @squareSide; 63 | height: @squareSide; 64 | border-radius: @squareSide; 65 | background-color: @switch-color; 66 | top: @switch-border-width; 67 | left: @switch-border-width; 68 | cursor: pointer; 69 | transition: left .2s ease-in-out,width .2s ease-in-out, background .2s ease-in-out; 70 | } 71 | &:checked ~ .@{switch}-bg{ 72 | background-color: @switch-checked-bgColor; 73 | } 74 | &:checked ~ .@{switch}-inner{ 75 | left: (@switch-width)-(@squareSide+@switch-border-width); 76 | } 77 | // ie 无效果,暂时未知 78 | &:active ~ .@{switch}-inner{ 79 | width: @squareSide+5; 80 | } 81 | &:checked:active ~ .@{switch}-inner{ 82 | left: (@switch-width)-(@squareSide+@switch-border-width)-5; 83 | } 84 | /*禁用样式*/ 85 | &:disabled ~ .@{switch}-bg, 86 | &.disabled ~ .@{switch}-bg { 87 | background: #F3F3F3; 88 | cursor: not-allowed; 89 | } 90 | &:checked:disabled ~ .@{switch}-bg, 91 | &:checked.disabled ~ .@{switch}-bg{ 92 | background: #D8D8D8; 93 | cursor: not-allowed; 94 | } 95 | &:disabled ~ .@{switch}-inner, 96 | &.disabled ~ .@{switch}-inner{ 97 | background: #e4e4e4; 98 | cursor: not-allowed; 99 | } 100 | } 101 | /*theme*/ 102 | .@{switch}{ 103 | &.@{switch}-success .@{switch}-trigger{ 104 | &:checked ~ .@{switch}-bg{ 105 | background-color: @switch-success-checked-bgColor; 106 | } 107 | } 108 | &.@{switch}-danger .@{switch}-trigger{ 109 | &:checked ~ .@{switch}-bg{ 110 | background-color: @switch-danger-checked-bgColor; 111 | } 112 | } 113 | &.@{switch}-warning .@{switch}-trigger{ 114 | &:checked ~ .@{switch}-bg{ 115 | background-color: @switch-warning-checked-bgColor; 116 | } 117 | } 118 | &.@{switch}-primary .@{switch}-trigger{ 119 | &:checked ~ .@{switch}-bg{ 120 | background-color: @switch-primary-checked-bgColor; 121 | } 122 | } 123 | // 反相 124 | &.@{switch}-invert{ 125 | .@{switch}-trigger{ 126 | &:checked ~ .@{switch}-bg{ 127 | background-color: @switch-color; 128 | } 129 | &:checked ~ .@{switch}-inner{ 130 | background-color: @switch-checked-bgColor; 131 | } 132 | } 133 | &.@{switch}-primary .@{switch}-trigger{ 134 | &:checked ~ .@{switch}-inner{ 135 | background-color: @switch-primary-checked-bgColor; 136 | } 137 | } 138 | &.@{switch}-success .@{switch}-trigger{ 139 | &:checked ~ .@{switch}-inner{ 140 | background-color: @switch-success-checked-bgColor; 141 | } 142 | } 143 | &.@{switch}-danger .@{switch}-trigger{ 144 | &:checked ~ .@{switch}-inner{ 145 | background-color: @switch-danger-checked-bgColor; 146 | } 147 | } 148 | &.@{switch}-warning .@{switch}-trigger{ 149 | &:checked ~ .@{switch}-inner{ 150 | background-color: @switch-warning-checked-bgColor; 151 | } 152 | } 153 | } 154 | // 大小 155 | &.@{switch}-lg{ 156 | width: @switch-lg-width; 157 | height: @switch-lg-height; 158 | border-radius: @switch-lg-height; 159 | .@{switch}-trigger { 160 | // 边长 161 | @squareSide: (@switch-lg-height)-(@switch-border-width*2); 162 | & ~ .@{switch}-inner{ 163 | width: @squareSide; 164 | height: @squareSide; 165 | border-radius: @squareSide; 166 | } 167 | &:checked ~ .@{switch}-inner{ 168 | left: (@switch-lg-width)-(@squareSide+@switch-border-width); 169 | } 170 | &:active ~ .@{switch}-inner{ 171 | width: @squareSide+8; 172 | } 173 | &:checked:active ~ .@{switch}-inner{ 174 | left: (@switch-lg-width)-(@squareSide+@switch-border-width)-8; 175 | } 176 | } 177 | } 178 | &.@{switch}-sm{ 179 | width: @switch-sm-width; 180 | height: @switch-sm-height; 181 | border-radius: @switch-sm-height; 182 | .@{switch}-trigger { 183 | // 边长 184 | @squareSide: (@switch-sm-height)-(@switch-border-width*2); 185 | & ~ .@{switch}-inner{ 186 | width: @squareSide; 187 | height: @squareSide; 188 | border-radius: @squareSide; 189 | } 190 | &:checked ~ .@{switch}-inner{ 191 | left: (@switch-sm-width)-(@squareSide+@switch-border-width); 192 | } 193 | &:active ~ .@{switch}-inner{ 194 | width: @squareSide+3; 195 | } 196 | &:checked:active ~ .@{switch}-inner{ 197 | left: (@switch-sm-width)-(@squareSide+@switch-border-width)-3; 198 | } 199 | } 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/pages/login.vue: -------------------------------------------------------------------------------- 1 | 39 | 105 | 207 | -------------------------------------------------------------------------------- /src/m/context-menu/context-menu.vue: -------------------------------------------------------------------------------- 1 | 80 | 163 | 197 | -------------------------------------------------------------------------------- /src/pages/addMovie.vue: -------------------------------------------------------------------------------- 1 | 70 | 168 | 169 | 184 | -------------------------------------------------------------------------------- /src/m/button/btn.less: -------------------------------------------------------------------------------- 1 | 2 | @btn-name: m-button; 3 | @btn-border-radius: 4px; 4 | @btn-border-default-color: #727B84; 5 | 6 | @btn-default-color: #fff; 7 | @btn-default-bgcolor: #727B84; 8 | 9 | @btn-info-bgcolor: #2DB7F5; 10 | @btn-primary-bgcolor: #7952B3; 11 | @btn-success-bgcolor: #03B976; 12 | @btn-danger-bgcolor: #F56C6C; 13 | @btn-warning-bgcolor: #F9AA40; 14 | 15 | @info-disabled-bgcolor: #6BCCEA; 16 | @primary-disabled-bgcolor: #9E98CE; 17 | @success-disabled-bgcolor: #56ceb0; 18 | @danger-disabled-bgcolor: #FAB6B6; 19 | @warning-disabled-bgcolor: #ffc576; 20 | 21 | @color-default-light: #868E96; 22 | @color-primary-light: #8E74B4; 23 | @color-info-light: #36C1FA; 24 | @color-success-light: #05a56a; 25 | @color-danger-light: #F78989; 26 | @color-warning-light: #FAB458; 27 | 28 | a.@{btn-name}{ 29 | text-decoration: none; 30 | color: inherit; 31 | } 32 | .@{btn-name}{ 33 | color: @btn-default-color; 34 | background-image: none; 35 | background-color: @btn-default-bgcolor; 36 | display: inline-block; 37 | padding: 7px 21px; 38 | font-size: 14px; 39 | font: inherit; 40 | font-weight: 400; 41 | line-height: 1.42857143; 42 | text-align: center; 43 | vertical-align: middle; 44 | white-space: nowrap; 45 | touch-action: manipulation; 46 | cursor: pointer; 47 | outline: none; 48 | border-radius: @btn-border-radius; 49 | border:solid 1px @btn-border-default-color; 50 | transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out; 51 | 52 | user-select: none; 53 | moz-user-select: -moz-none; 54 | -moz-user-select: none; 55 | -o-user-select:none; 56 | -khtml-user-select:none; 57 | -webkit-user-select:none; 58 | -ms-user-select:none; 59 | &:hover{ 60 | background-color: @color-default-light; 61 | border:solid 1px @color-default-light; 62 | } 63 | &:active, 64 | &.active,{ 65 | opacity: 0.9; 66 | position: relative; 67 | } 68 | &.active:after{ 69 | content: ''; 70 | position: absolute; 71 | display: block; 72 | top: -4px; 73 | left: -4px; 74 | bottom: -4px; 75 | right: -4px; 76 | opacity: 0.35; 77 | border: solid 6px; 78 | border-radius: 6px; 79 | border-color: inherit; 80 | } 81 | & + &{ 82 | margin-left: 10px; 83 | } 84 | &:disabled, 85 | &.disabled{ 86 | cursor: not-allowed; 87 | // pointer-events: none; 88 | color: rgba(0, 0, 0, 0.25); 89 | background-color: #f5f5f5; 90 | border-color: #d9d9d9; 91 | } 92 | &&-rounded{ 93 | border-radius: 50px; 94 | } 95 | 96 | &&-max{ 97 | padding: 15px 45px; 98 | font-size: 18px; 99 | } 100 | &&-large{ 101 | padding: 10px 30px; 102 | } 103 | &&-small{ 104 | padding: 5px 15px; 105 | font-size: 13px; 106 | } 107 | &&-mini{ 108 | padding: 4px 12px; 109 | font-size: 12px; 110 | } 111 | &&-block{ 112 | display: block; 113 | width: 100%; 114 | & + & { 115 | margin-left: auto; 116 | margin-top: 10px; 117 | } 118 | } 119 | 120 | .make-btn-theme(info); 121 | .make-btn-theme(primary); 122 | .make-btn-theme(success); 123 | .make-btn-theme(danger); 124 | .make-btn-theme(warning); 125 | 126 | .make-btn-clicked(); 127 | 128 | &&-type-plain{ 129 | color: #868E96; 130 | border-color: @btn-border-default-color; 131 | background-color: transparent; 132 | &:hover{ 133 | background-color: @btn-default-bgcolor; 134 | color: #fff; 135 | } 136 | .make-btn-type-plain-theme(info); 137 | .make-btn-type-plain-theme(primary); 138 | .make-btn-type-plain-theme(success); 139 | .make-btn-type-plain-theme(danger); 140 | .make-btn-type-plain-theme(warning); 141 | } 142 | } 143 | 144 | .make-btn-theme(@theme){ 145 | &&-@{theme} { 146 | color: #fff; 147 | background-color: ~"@{btn-@{theme}-bgcolor}"; 148 | border-color: ~"@{btn-@{theme}-bgcolor}"; 149 | &:hover{ 150 | background-color: ~"@{color-@{theme}-light}"; 151 | border-color: ~"@{color-@{theme}-light}"; 152 | } 153 | &:disabled, 154 | &.disabled{ 155 | cursor: not-allowed; 156 | // pointer-events: none; 157 | background-color: ~"@{@{theme}-disabled-bgcolor}"; 158 | border-color: ~"@{@{theme}-disabled-bgcolor}"; 159 | } 160 | } 161 | } 162 | // 点击涟漪效果 163 | .make-btn-clicked(){ 164 | &.clicked{ 165 | position: relative; 166 | &:before{ 167 | content: ''; 168 | position: absolute; 169 | display: block; 170 | top: -1px; 171 | left: -1px; 172 | bottom: -1px; 173 | right: -1px; 174 | opacity: .8; 175 | border: solid 0px; 176 | border-color: inherit; 177 | border-radius: inherit; 178 | animation: btn-clicked-effect .5s; 179 | } 180 | } 181 | } 182 | .make-btn-type-plain-theme(@theme){ 183 | &.@{btn-name}-@{theme}{ 184 | color: ~"@{btn-@{theme}-bgcolor}"; 185 | border-color: ~"@{color-@{theme}-light}"; 186 | &:hover{ 187 | border-color: ~"@{btn-@{theme}-bgcolor}"; 188 | background-color: ~"@{btn-@{theme}-bgcolor}"; 189 | color: #fff; 190 | } 191 | } 192 | } 193 | .@{btn-name}-block+.@{btn-name}-block{ 194 | margin-top: 5px; 195 | } 196 | .@{btn-name}-group{ 197 | position: relative; 198 | display: inline-block; 199 | vertical-align: middle; 200 | } 201 | .@{btn-name}-group .@{btn-name}{ 202 | position: relative; 203 | float: left; 204 | border-radius: 0; 205 | margin-left: -1px; 206 | &:first-child{ 207 | margin-left: 0; 208 | border-top-left-radius: @btn-border-radius; 209 | border-bottom-left-radius: @btn-border-radius; 210 | } 211 | &:last-child{ 212 | border-top-right-radius: @btn-border-radius; 213 | border-bottom-right-radius: @btn-border-radius; 214 | } 215 | } 216 | 217 | 218 | /** 219 | * animation 220 | * e.g keyframes(effect-test, all, {css}) 221 | */ 222 | .keyframes (@name, @prefix, @content) when (@prefix = def) { 223 | @keyframes @name { 224 | @content(); 225 | } 226 | } 227 | .keyframes (@name, @prefix, @content) when (@prefix = moz) { 228 | @-moz-keyframes @name { 229 | @content(); 230 | } 231 | } 232 | .keyframes (@name, @prefix, @content) when (@prefix = o) { 233 | @-o-keyframes @name { 234 | @content(); 235 | } 236 | } 237 | .keyframes (@name, @prefix, @content) when (@prefix = webkit) { 238 | @-webkit-keyframes @name{ 239 | @content(); 240 | } 241 | } 242 | .keyframes (@name, @prefix, @content) when (@prefix = all) { 243 | .keyframes(@name, moz, @content); 244 | .keyframes(@name, o, @content); 245 | .keyframes(@name, webkit, @content); 246 | .keyframes(@name, def, @content); 247 | } 248 | 249 | .keyframes(btn-clicked-effect, all, { 250 | to{ 251 | top: -6px; 252 | left: -6px; 253 | bottom: -6px; 254 | right: -6px; 255 | border-width: 6px; 256 | opacity: 0; 257 | } 258 | }); 259 | -------------------------------------------------------------------------------- /src/pages/userlist.vue: -------------------------------------------------------------------------------- 1 | 67 | 194 | 195 | 218 | --------------------------------------------------------------------------------