@ -0,0 +1,12 @@ |
|||
root = true |
|||
|
|||
[*] |
|||
charset = utf-8 |
|||
indent_style = space |
|||
indent_size = 4 |
|||
end_of_line = lf |
|||
insert_final_newline = true |
|||
trim_trailing_whitespace = true |
|||
|
|||
[*.md] |
|||
trim_trailing_whitespace = false |
|||
@ -0,0 +1,2 @@ |
|||
VUE_APP_TITLE = 填鸭测试环境 |
|||
VUE_APP_API_ROOT = / |
|||
@ -0,0 +1,2 @@ |
|||
VUE_APP_TITLE = 网站标题 |
|||
VUE_APP_API_ROOT = https://yigou.ketao.com/api/ |
|||
@ -0,0 +1,2 @@ |
|||
dist/ |
|||
node_modules/ |
|||
@ -0,0 +1,87 @@ |
|||
module.exports = { |
|||
root: true, |
|||
env: { |
|||
browser: true, |
|||
es6: true |
|||
}, |
|||
globals: { |
|||
process: true, |
|||
require: true |
|||
}, |
|||
extends: [ |
|||
// 'plugin:vue/strongly-recommended',
|
|||
// 'eslint:recommended'
|
|||
], |
|||
parserOptions: { |
|||
ecmaVersion: 2015, |
|||
parser: 'babel-eslint', |
|||
sourceType: 'module' |
|||
}, |
|||
rules: { |
|||
// 代码风格
|
|||
'block-spacing': [2, 'always'], |
|||
'brace-style': [2, '1tbs', { |
|||
'allowSingleLine': true |
|||
}], |
|||
'comma-spacing': [2, { |
|||
'before': false, |
|||
'after': true |
|||
}], |
|||
'comma-dangle': [2, 'never'], |
|||
'comma-style': [2, 'last'], |
|||
'computed-property-spacing': [2, 'never'], |
|||
'indent': [2, 4, { |
|||
'SwitchCase': 1 |
|||
}], |
|||
'key-spacing': [2, { |
|||
'beforeColon': false, |
|||
'afterColon': true |
|||
}], |
|||
'keyword-spacing': [2, { |
|||
'before': true, |
|||
'after': true |
|||
}], |
|||
'linebreak-style': 0, |
|||
'multiline-ternary': [2, 'always-multiline'], |
|||
'no-multiple-empty-lines': [2, { |
|||
'max': 1 |
|||
}], |
|||
'no-unneeded-ternary': [2, { |
|||
'defaultAssignment': false |
|||
}], |
|||
'quotes': [2, 'single'], |
|||
'semi': [2, 'never'], |
|||
'space-before-blocks': [2, 'always'], |
|||
'space-before-function-paren': [2, 'never'], |
|||
'space-in-parens': [2, 'never'], |
|||
'space-infix-ops': 2, |
|||
'space-unary-ops': [2, { |
|||
'words': true, |
|||
'nonwords': false |
|||
}], |
|||
'spaced-comment': [2, 'always', { |
|||
'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ','] |
|||
}], |
|||
'switch-colon-spacing': [2, { |
|||
'after': true, |
|||
'before': false |
|||
}], |
|||
// ES6
|
|||
'arrow-parens': [2, 'as-needed'], |
|||
'arrow-spacing': [2, { |
|||
'before': true, |
|||
'after': true |
|||
}], |
|||
// Vue - https://github.com/vuejs/eslint-plugin-vue
|
|||
'vue/html-indent': [2, 4], |
|||
'vue/max-attributes-per-line': 0, |
|||
'vue/require-default-prop': 0, |
|||
'vue/singleline-html-element-content-newline': 0, |
|||
'vue/attributes-order': 2, |
|||
'vue/order-in-components': 2, |
|||
'vue/this-in-template': 2, |
|||
'vue/script-indent': [2, 4, { |
|||
'switchCase': 1 |
|||
}] |
|||
} |
|||
}; |
|||
@ -0,0 +1,23 @@ |
|||
.DS_Store |
|||
node_modules |
|||
/dist |
|||
/dist-dev |
|||
/src/assets/sprites/*.* |
|||
|
|||
# local env files |
|||
.env.local |
|||
.env.*.local |
|||
|
|||
# Log files |
|||
npm-debug.log* |
|||
yarn-debug.log* |
|||
yarn-error.log* |
|||
|
|||
# Editor directories and files |
|||
.idea |
|||
.vscode |
|||
*.suo |
|||
*.ntvs* |
|||
*.njsproj |
|||
*.sln |
|||
*.sw* |
|||
@ -0,0 +1,2 @@ |
|||
dist/ |
|||
node_modules/ |
|||
@ -0,0 +1,14 @@ |
|||
{ |
|||
"extends": [ |
|||
"stylelint-config-standard", |
|||
"stylelint-config-recommended-scss" |
|||
], |
|||
"plugins": [ |
|||
"stylelint-scss" |
|||
], |
|||
"rules": { |
|||
"indentation": 4, |
|||
"rule-empty-line-before": "never", |
|||
"at-rule-empty-line-before": "never" |
|||
} |
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
# tduck-front |
|||
|
|||
## 说明 |
|||
|
|||
该仓库是为统一 Vue 项目初期配置而设立,方便快速进行业务开发,基于 Vue CLI 3 。 |
|||
|
|||
## 使用 |
|||
|
|||
### 方法 1 |
|||
|
|||
> 适用于初学者快速上手,项目里包含演示文件,方便学习 |
|||
|
|||
拉取该项目到本地,安装依赖包后运行即可。 |
|||
|
|||
运行后,可以看到功能演示,同时项目目录里带有 `example` 的目录均为演示代码。 |
|||
|
|||
|
|||
## 依赖 |
|||
|
|||
- vue-router |
|||
- vuex |
|||
- axios |
|||
- lodash |
|||
- dayjs |
|||
- vue-cookies |
|||
- vue-meta |
|||
- node-sass |
|||
- sass-loader |
|||
- sass-resources-loader |
|||
- svg-sprite-loader |
|||
- webpack-spritesmith |
|||
|
|||
## 全局 SCSS 资源 |
|||
|
|||
> 全局 SCSS 资源通过 [sass-resources-loader](https://www.npmjs.com/package/sass-resources-loader) 实现 |
|||
> |
|||
> 注意!并不是全局样式,而是 SCSS 资源,是变量、@mixin 、@function 这些东西 |
|||
|
|||
在 `assets/styles/resources/` 目录下存放全局的 scss 资源,也就是说在这个目录里的文件,无需在页面上引用即可生效并使用。 |
|||
|
|||
例子中默认存放了 `utils.scss` 文件,里面有几个 `@mixin` 和 `%` ,你可以尝试在页面中使用它们看看效果。 |
|||
|
|||
同样, `assets/sprites/` 目录下生成的 scss 文件也是默认全局。 |
|||
|
|||
## 全局组件 |
|||
|
|||
> 全局组件会自动注册,可直接使用。 |
|||
|
|||
在 `components/global/` 目录下存放全局组件,需要注意各个组件按文件夹区分,文件夹名称与组件名无关联。 |
|||
|
|||
每个组件的文件夹内至少保留一个文件名为 index 的组件入口,例如 index.vue 。 |
|||
|
|||
普通组件必须设置 name 并保证其唯一,自动注册会将组件的 name 设为组件名,可参考 SvgIcon 组件写法。 |
|||
|
|||
如果组件是通过 js 进行调用,则确保组件入口文件为 index.js,可参考 ExampleNotice 组件。 |
|||
|
|||
|
|||
## 路由 |
|||
|
|||
路由也实现自动注册,但因为有优先级的概念,先定义的会先匹配,所以同一模块下的路由必须放在一个路由配置文件里,具体可参考 `router/modules/example.js` 文件。 |
|||
|
|||
## Vuex |
|||
|
|||
Vuex 同样实现了自动注册,开发只需关注 `store/modules/` 文件夹里的文件即可,同样也按照模块区分文件。 |
|||
|
|||
@ -0,0 +1,5 @@ |
|||
module.exports = { |
|||
presets: [ |
|||
'@vue/cli-plugin-babel/preset' |
|||
] |
|||
} |
|||
@ -0,0 +1,55 @@ |
|||
{ |
|||
"name": "tduck-front", |
|||
"version": "0.1.0", |
|||
"private": true, |
|||
"scripts": { |
|||
"serve": "vue-cli-service serve", |
|||
"build-dev": "vue-cli-service build --mode development --dest dist-dev", |
|||
"build": "vue-cli-service build", |
|||
"lint": "vue-cli-service lint", |
|||
"svgo": "svgo -f src/assets/icons" |
|||
}, |
|||
"dependencies": { |
|||
"axios": "^0.19.0", |
|||
"core-js": "^3.4.1", |
|||
"dayjs": "^1.8.17", |
|||
"element-ui": "^2.13.0", |
|||
"js-md5": "^0.7.3", |
|||
"lodash": "^4.17.15", |
|||
"nprogress": "^0.2.0", |
|||
"vue": "^2.6.0", |
|||
"vue-cookies": "^1.5.7", |
|||
"vue-meta": "^2.3.1", |
|||
"vue-router": "^3.1.3", |
|||
"vuex": "^3.1.2" |
|||
}, |
|||
"devDependencies": { |
|||
"@vue/cli-plugin-babel": "^4.0.0", |
|||
"@vue/cli-plugin-eslint": "^4.0.0", |
|||
"@vue/cli-service": "^4.0.0", |
|||
"babel-eslint": "^10.0.3", |
|||
"eslint": "^6.6.0", |
|||
"eslint-plugin-vue": "^6.0.1", |
|||
"node-sass": "^4.10.0", |
|||
"sass-loader": "^8.0.0", |
|||
"sass-resources-loader": "^2.0.1", |
|||
"stylelint": "^12.0.0", |
|||
"stylelint-config-recommended-scss": "^4.0.0", |
|||
"stylelint-config-standard": "^19.0.0", |
|||
"stylelint-scss": "^3.4.1", |
|||
"svg-sprite-loader": "^4.1.6", |
|||
"svgo": "^1.3.0", |
|||
"vue-template-compiler": "^2.6.0", |
|||
"webpack-spritesmith": "^1.0.2" |
|||
}, |
|||
"postcss": { |
|||
"plugins": { |
|||
"autoprefixer": {} |
|||
} |
|||
}, |
|||
"browserslist": [ |
|||
"> 1%", |
|||
"last 2 versions", |
|||
"not ie <= 8" |
|||
] |
|||
} |
|||
|
After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1,17 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="en"> |
|||
<head> |
|||
<meta charset="utf-8"> |
|||
<meta http-equiv="X-UA-Compatible" content="IE=edge"> |
|||
<meta name="viewport" content="width=device-width,initial-scale=1.0"> |
|||
<link rel="icon" href="<%= BASE_URL %>favicon.ico"> |
|||
<title>vue-automation</title> |
|||
</head> |
|||
<body> |
|||
<noscript> |
|||
<strong>We're sorry but vue-automation doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> |
|||
</noscript> |
|||
<div id="app"></div> |
|||
<!-- built files will be auto injected --> |
|||
</body> |
|||
</html> |
|||
@ -0,0 +1,65 @@ |
|||
{ |
|||
// Default options |
|||
'functions': true, |
|||
'variableNameTransforms': ['dasherize'] |
|||
} |
|||
|
|||
{{#block "sprites"}} |
|||
{{#each sprites}} |
|||
${{../spritesheet_info.strings.name}}-sprite-{{strings.name}}: ({{px.x}}, {{px.y}}, {{px.offset_x}}, {{px.offset_y}}, {{px.width}}, {{px.height}}, {{px.total_width}}, {{px.total_height}}, '{{{escaped_image}}}', '{{name}}'); |
|||
{{/each}} |
|||
|
|||
${{spritesheet_info.strings.name}}-sprites: ( |
|||
{{#each sprites}} |
|||
{{strings.name}}: ${{../spritesheet_info.strings.name}}-sprite-{{strings.name}}, |
|||
{{/each}} |
|||
); |
|||
{{/block}} |
|||
|
|||
{{#block "sprite-functions"}} |
|||
{{#if options.functions}} |
|||
@mixin {{spritesheet_info.strings.name}}-sprite-width($sprite) { |
|||
width: nth($sprite, 5); |
|||
} |
|||
|
|||
@mixin {{spritesheet_info.strings.name}}-sprite-height($sprite) { |
|||
height: nth($sprite, 6); |
|||
} |
|||
|
|||
@mixin {{spritesheet_info.strings.name}}-sprite-position($sprite) { |
|||
$sprite-offset-x: nth($sprite, 3); |
|||
$sprite-offset-y: nth($sprite, 4); |
|||
background-position: $sprite-offset-x $sprite-offset-y; |
|||
} |
|||
|
|||
@mixin {{spritesheet_info.strings.name}}-sprite-size($sprite) { |
|||
background-size: nth($sprite, 7) nth($sprite, 8); |
|||
} |
|||
|
|||
@mixin {{spritesheet_info.strings.name}}-sprite-image($sprite) { |
|||
$sprite-image: nth($sprite, 9); |
|||
background-image: url(#{$sprite-image}); |
|||
} |
|||
|
|||
@mixin {{spritesheet_info.strings.name}}-sprite($name) { |
|||
@include {{spritesheet_info.strings.name}}-sprite-image(map-get(${{spritesheet_info.strings.name}}-sprites, #{$name})); |
|||
@include {{spritesheet_info.strings.name}}-sprite-position(map-get(${{spritesheet_info.strings.name}}-sprites, #{$name})); |
|||
@include {{spritesheet_info.strings.name}}-sprite-size(map-get(${{spritesheet_info.strings.name}}-sprites, #{$name})); |
|||
@include {{spritesheet_info.strings.name}}-sprite-width(map-get(${{spritesheet_info.strings.name}}-sprites, #{$name})); |
|||
@include {{spritesheet_info.strings.name}}-sprite-height(map-get(${{spritesheet_info.strings.name}}-sprites, #{$name})); |
|||
} |
|||
{{/if}} |
|||
{{/block}} |
|||
|
|||
{{#block "spritesheet-functions"}} |
|||
{{#if options.functions}} |
|||
@mixin all-{{spritesheet_info.strings.name}}-sprites() { |
|||
@each $key, $val in ${{spritesheet_info.strings.name}}-sprites { |
|||
$sprite-name: nth($val, 10); |
|||
.{{spritesheet_info.strings.name}}-#{$sprite-name}-sprites { |
|||
@include {{spritesheet_info.strings.name}}-sprite($key); |
|||
} |
|||
} |
|||
} |
|||
{{/if}} |
|||
{{/block}} |
|||
@ -0,0 +1,41 @@ |
|||
<template> |
|||
<div id="app"> |
|||
<router-view v-if="isRouterAlive" /> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
provide() { |
|||
return { |
|||
reload: this.reload |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
isRouterAlive: true |
|||
} |
|||
}, |
|||
watch: { |
|||
$route: 'routeChange' |
|||
}, |
|||
methods: { |
|||
reload() { |
|||
this.isRouterAlive = false |
|||
this.$nextTick(() => (this.isRouterAlive = true)) |
|||
}, |
|||
routeChange(newVal, oldVal) { |
|||
if (newVal.name == oldVal.name) { |
|||
this.reload() |
|||
} |
|||
} |
|||
}, |
|||
metaInfo: { |
|||
titleTemplate: title => { |
|||
return title |
|||
? `${title} - ${process.env.VUE_APP_TITLE}` |
|||
: process.env.VUE_APP_TITLE |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
@ -0,0 +1,85 @@ |
|||
import axios from 'axios' |
|||
import router from '@/router/index' |
|||
import store from '@/store/index' |
|||
import signMd5Utils from '@/util/signMd5Utils' |
|||
|
|||
const toLogin = () => { |
|||
router.push({ |
|||
path: '/login', |
|||
query: { |
|||
redirect: router.currentRoute.fullPath |
|||
} |
|||
}) |
|||
} |
|||
|
|||
const api = axios.create({ |
|||
baseURL: process.env.VUE_APP_API_ROOT, |
|||
timeout: 1000 * 30, |
|||
withCredentials: false, |
|||
headers: { |
|||
'Content-Type': 'application/json; charset=utf-8' |
|||
} |
|||
}) |
|||
|
|||
api.interceptors.request.use( |
|||
request => { |
|||
if (request.method == 'post') { |
|||
if (request.data instanceof FormData) { |
|||
if (store.getters['token/isLogin']) { |
|||
// 如果是 FormData 类型(上传图片)
|
|||
request.data.append('token', store.state.token.token) |
|||
} |
|||
} else { |
|||
// 带上 token
|
|||
if (request.data == undefined) { |
|||
request.data = {} |
|||
} |
|||
if (store.getters['token/isLogin']) { |
|||
request.data.token = store.state.token.token |
|||
} |
|||
let timestamp = new Date().getTime() |
|||
request.data.timestamp = '' + timestamp |
|||
let sign = signMd5Utils.getSign(request.url, request.data) |
|||
request.data.sign = sign |
|||
request.data = JSON.stringify(request.data) |
|||
} |
|||
} else { |
|||
// 带上 token
|
|||
if (request.params == undefined) { |
|||
request.params = {} |
|||
} |
|||
if (store.getters['token/isLogin']) { |
|||
request.params.token = store.state.token.token |
|||
} |
|||
let timestamp = new Date().getTime() |
|||
console.log(request.params) |
|||
request.params.timestamp = '' + timestamp |
|||
let sign = signMd5Utils.getSign(request.url, request.params) |
|||
request.params.sign = sign |
|||
|
|||
} |
|||
return request |
|||
} |
|||
) |
|||
|
|||
api.interceptors.response.use( |
|||
response => { |
|||
if (response.data.code != 200) { |
|||
// 如果接口请求时发现 token 失效,则立马跳转到登录页
|
|||
if (response.data.code == 0) { |
|||
toLogin() |
|||
return false |
|||
} |
|||
return Promise.reject(response.data) |
|||
} |
|||
return Promise.resolve(response.data) |
|||
}, |
|||
error => { |
|||
return Promise.reject(error) |
|||
} |
|||
) |
|||
|
|||
export { |
|||
axios, |
|||
api |
|||
} |
|||
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 823 B |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
@ -0,0 +1,8 @@ |
|||
// 改目录下可存放第三方样式文件,或者公用样式 |
|||
// 该例子可在 view/example/sprite.vue 里查看 |
|||
|
|||
.sprites { |
|||
div { |
|||
border: 1px solid #000; |
|||
} |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
// @mixin 通过 @include 调用使用 |
|||
// % 通过 @extend 调用使用 |
|||
|
|||
// 文字超出隐藏,默认为单行超出隐藏,可设置多行 |
|||
@mixin text-overflow($line: 1, $fixed-width: true) { |
|||
@if ($line==1 and $fixed-width==true) { |
|||
overflow: hidden; |
|||
text-overflow: ellipsis; |
|||
white-space: nowrap; |
|||
} |
|||
@else { |
|||
display: -webkit-box; |
|||
-webkit-box-orient: vertical; |
|||
-webkit-line-clamp: $line; |
|||
overflow: hidden; |
|||
} |
|||
} |
|||
|
|||
// 定位居中,默认水平居中,可选择垂直居中,或者水平垂直都居中 |
|||
@mixin position-center($type: x) { |
|||
position: absolute; |
|||
@if ($type==x) { |
|||
left: 50%; |
|||
transform: translateX(-50%); |
|||
} |
|||
@if ($type==y) { |
|||
top: 50%; |
|||
transform: translateY(-50%); |
|||
} |
|||
@if ($type==xy) { |
|||
left: 50%; |
|||
top: 50%; |
|||
transform: translateX(-50%) translateY(-50%); |
|||
} |
|||
} |
|||
|
|||
// 文字两端对齐 |
|||
%justify-align { |
|||
text-align: justify; |
|||
text-align-last: justify; |
|||
} |
|||
|
|||
// 清除浮动 |
|||
%clearfix { |
|||
zoom: 1; |
|||
&::before, |
|||
&::after { |
|||
content: ''; |
|||
display: block; |
|||
clear: both; |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
<template> |
|||
<div> |
|||
<ul> |
|||
<li v-for="(item, index) in list" :key="index">{{ item }}</li> |
|||
</ul> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: 'ExampleList', |
|||
props: { |
|||
list: Array |
|||
} |
|||
} |
|||
</script> |
|||
@ -0,0 +1,21 @@ |
|||
/** |
|||
* 全局组件自动注册 |
|||
* |
|||
* 全局组件统一放在 ./global 目录下,需要注意各个组件按文件夹区分,文件夹名称与组件名无关联 |
|||
* 文件夹内至少保留一个文件名为 index 的组件入口,例如 index.vue |
|||
* 普通组件必须设置 name 并保证其唯一,自动注册会将组件的 name 设为组件名,可参考 SvgIcon 组件写法 |
|||
* 如果组件是通过 js 进行调用,则确保组件入口文件为 index.js,可参考 ExampleNotice 组件 |
|||
*/ |
|||
|
|||
import Vue from 'vue' |
|||
|
|||
const componentsContext = require.context('./global', true, /index.(vue|js)$/) |
|||
componentsContext.keys().forEach(file_name => { |
|||
// 获取文件中的 default 模块
|
|||
const componentConfig = componentsContext(file_name).default |
|||
if (/.vue$/.test(file_name)) { |
|||
Vue.component(componentConfig.name, componentConfig) |
|||
} else { |
|||
Vue.prototype[`$${componentConfig.name}`] = componentConfig |
|||
} |
|||
}) |
|||
@ -0,0 +1,18 @@ |
|||
import Vue from 'vue' |
|||
|
|||
const constructor = Vue.extend(require('./main.vue').default) |
|||
|
|||
let instance |
|||
|
|||
const exampleNotice = options => { |
|||
options = options || {} |
|||
instance = new constructor({ |
|||
data: options |
|||
}) |
|||
instance.vm = instance.$mount() |
|||
instance.dom = instance.vm.$el |
|||
document.body.appendChild(instance.dom) |
|||
return instance.vm |
|||
} |
|||
|
|||
export default exampleNotice |
|||
@ -0,0 +1,42 @@ |
|||
<template> |
|||
<transition name="notice"> |
|||
<div v-if="show" class="notice"> |
|||
{{ content }} |
|||
</div> |
|||
</transition> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: 'ExampleNotice', |
|||
data() { |
|||
return { |
|||
show: false, |
|||
content: '' |
|||
} |
|||
}, |
|||
mounted() { |
|||
this.show = true |
|||
setTimeout(() => { |
|||
this.show = false |
|||
}, 2000) |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.notice { |
|||
padding: 10px; |
|||
background-color: #eee; |
|||
border-radius: 10px; |
|||
@include position-center(xy); |
|||
} |
|||
.notice-leave-active, |
|||
.notice-enter-active { |
|||
transition: all 0.3s; |
|||
} |
|||
.notice-enter, |
|||
.notice-leave-to { |
|||
opacity: 0; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,36 @@ |
|||
<template> |
|||
<svg :class="svgClass" aria-hidden="true" v-on="$listeners"> |
|||
<use :xlink:href="`#icon-${iconClass}`" /> |
|||
</svg> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: 'SvgIcon', |
|||
props: { |
|||
iconClass: { |
|||
type: String, |
|||
required: true |
|||
}, |
|||
className: { |
|||
type: String, |
|||
default: '' |
|||
} |
|||
}, |
|||
computed: { |
|||
svgClass() { |
|||
return this.className ? ('svg-icon ' + this.className) : 'svg-icon' |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.svg-icon { |
|||
width: 1em; |
|||
height: 1em; |
|||
vertical-align: -0.15em; |
|||
fill: currentColor; |
|||
overflow: hidden; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,44 @@ |
|||
<template> |
|||
<div> |
|||
<div id="nav"> |
|||
<RouterLink to="/example/sprite">sprite精灵图</RouterLink> |
|||
<RouterLink to="/example/svgicon">svg icon</RouterLink> |
|||
<RouterLink to="/example/globalComponent">全局组件</RouterLink> |
|||
<RouterLink to="/example/axios">axios</RouterLink> |
|||
<RouterLink to="/example/cookie">cookie</RouterLink> |
|||
<RouterLink to="/example/meta">meta</RouterLink> |
|||
<RouterLink to="/example/vuex">vuex</RouterLink> |
|||
<RouterLink to="/example/component">组件</RouterLink> |
|||
<RouterLink :to="{name:'exampleParams',params:{test:'123'}}">路由params</RouterLink> |
|||
<RouterLink :to="{path:'/example/query',query:{test:'123'}}">路由query</RouterLink> |
|||
<RouterLink to="/example/reload">刷新当前页面</RouterLink> |
|||
<RouterLink to="/example/permission/router">router鉴权</RouterLink> |
|||
<RouterLink to="/example/permission/js">js鉴权</RouterLink> |
|||
<RouterLink to="/example/user">基本操作</RouterLink> |
|||
</div> |
|||
<RouterView /> |
|||
</div> |
|||
</template> |
|||
|
|||
<style lang="scss" scoped> |
|||
#nav { |
|||
margin-bottom: 10px; |
|||
a { |
|||
text-decoration: none; |
|||
font-size: 14px; |
|||
&::after { |
|||
content: '|'; |
|||
margin: 0 10px; |
|||
font-weight: normal; |
|||
font-size: 14px; |
|||
} |
|||
&:last-child::after { |
|||
content: none; |
|||
} |
|||
&.router-link-active { |
|||
font-weight: bold; |
|||
font-size: 18px; |
|||
} |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,36 @@ |
|||
import Vue from 'vue' |
|||
import App from './App.vue' |
|||
import router from './router/index' |
|||
import store from './store/index' |
|||
import lodash from 'lodash' |
|||
import {api, axios} from './api' |
|||
import dayjs from 'dayjs' |
|||
import util from './util/index' |
|||
import meta from 'vue-meta' |
|||
import cookies from 'vue-cookies' |
|||
import Element from 'element-ui' |
|||
import 'element-ui/lib/theme-chalk/index.css'; |
|||
// 全局组件自动注册
|
|||
import '@/components/autoRegister' |
|||
|
|||
Vue.use(meta) |
|||
Vue.use(cookies) |
|||
Vue.use(util) |
|||
Vue.use(Element, {size: 'small', zIndex: 3000}) |
|||
Vue.prototype.$api = api |
|||
Vue.prototype.$axios = axios |
|||
Vue.prototype._ = lodash |
|||
Vue.prototype.$dayjs = dayjs |
|||
|
|||
Vue.config.productionTip = false |
|||
|
|||
// 自动加载 svg 图标
|
|||
const req = require.context('./assets/icons', false, /\.svg$/) |
|||
const requireAll = requireContext => requireContext.keys().map(requireContext) |
|||
requireAll(req) |
|||
|
|||
new Vue({ |
|||
router, |
|||
store, |
|||
render: h => h(App) |
|||
}).$mount('#app') |
|||
@ -0,0 +1,50 @@ |
|||
import Vue from 'vue' |
|||
import Router from 'vue-router' |
|||
import store from '@/store/index' |
|||
import flattenDeep from 'lodash/flattenDeep' |
|||
import NProgress from 'nprogress' |
|||
import 'nprogress/nprogress.css' // progress bar style
|
|||
|
|||
Vue.use(Router) |
|||
|
|||
/** |
|||
* 因为路由有优先级的概念,先定义的会先匹配,而自动注册是依据文件名的排序来遍历的 |
|||
* 所以下面这种情况,如果访问 /news/edit ,会指向到 info.vue 页面上 |
|||
* a.js /news/:id info.vue |
|||
* b.js /news/edit edit.vue |
|||
* 为避免这种情况发生,同一模块下的路由必须放在一个路由配置文件里 |
|||
* 按上面的例子,news 模块的路由,应该放到一个类似于 news.js 的文件里 |
|||
* 至于模块里的路由优先级,可以把 /news/edit 放在 /news/:id 前面,或者把 /news/:id 改成 /news/info/:id 均可 |
|||
*/ |
|||
const routes = [] |
|||
const require_module = require.context('./modules', false, /.js$/) |
|||
require_module.keys().forEach(file_name => { |
|||
routes.push(require_module(file_name).default) |
|||
}) |
|||
|
|||
const router = new Router({ |
|||
routes: flattenDeep(routes) |
|||
}) |
|||
|
|||
router.beforeEach((to, from, next) => { |
|||
NProgress.start() |
|||
if (to.meta.requireLogin) { |
|||
if (store.getters['token/isLogin']) { |
|||
next() |
|||
NProgress.done() |
|||
} else { |
|||
next({ |
|||
path: '/login', |
|||
query: { |
|||
redirect: to.fullPath |
|||
} |
|||
}) |
|||
NProgress.done() |
|||
} |
|||
} else { |
|||
next() |
|||
NProgress.done() |
|||
} |
|||
}) |
|||
|
|||
export default router |
|||
@ -0,0 +1,84 @@ |
|||
import ExampleLayout from '@/layout/example' |
|||
|
|||
export default { |
|||
path: '/example', |
|||
redirect: '/example/sprite', |
|||
component: ExampleLayout, |
|||
children: [ |
|||
{ |
|||
path: 'sprite', |
|||
component: () => |
|||
import(/* webpackChunkName: 'example' */ '@/views/example/sprite.vue') |
|||
}, |
|||
{ |
|||
path: 'svgicon', |
|||
component: () => |
|||
import(/* webpackChunkName: 'example' */ '@/views/example/svgicon.vue') |
|||
}, |
|||
{ |
|||
path: 'globalComponent', |
|||
component: () => |
|||
import(/* webpackChunkName: 'example' */ '@/views/example/global.component.vue') |
|||
}, |
|||
{ |
|||
path: 'axios', |
|||
component: () => |
|||
import(/* webpackChunkName: 'example' */ '@/views/example/axios.vue') |
|||
}, |
|||
{ |
|||
path: 'cookie', |
|||
component: () => |
|||
import(/* webpackChunkName: 'example' */ '@/views/example/cookie.vue') |
|||
}, |
|||
{ |
|||
path: 'meta', |
|||
component: () => |
|||
import(/* webpackChunkName: 'example' */ '@/views/example/meta.vue') |
|||
}, |
|||
{ |
|||
path: 'vuex', |
|||
component: () => |
|||
import(/* webpackChunkName: 'example' */ '@/views/example/vuex.vue') |
|||
}, |
|||
{ |
|||
path: 'component', |
|||
component: () => |
|||
import(/* webpackChunkName: 'example' */ '@/views/example/component.vue') |
|||
}, |
|||
{ |
|||
path: 'params/:test', |
|||
name: 'exampleParams', // 设置路由的name时,建议加上模块名,避免name和其他模块重名
|
|||
component: () => |
|||
import(/* webpackChunkName: 'example' */ '@/views/example/params.vue') |
|||
}, |
|||
{ |
|||
path: 'query', |
|||
component: () => |
|||
import(/* webpackChunkName: 'example' */ '@/views/example/query.vue') |
|||
}, |
|||
{ |
|||
path: 'reload', |
|||
component: () => |
|||
import(/* webpackChunkName: 'example' */ '@/views/example/reload.vue') |
|||
}, |
|||
{ |
|||
path: 'permission/router', |
|||
component: () => |
|||
import(/* webpackChunkName: 'example' */ '@/views/example/permission.router.vue'), |
|||
meta: { |
|||
requireLogin: true // 鉴权
|
|||
} |
|||
}, |
|||
{ |
|||
path: 'permission/js', |
|||
component: () => |
|||
import(/* webpackChunkName: 'example' */ '@/views/example/permission.js.vue') |
|||
}, |
|||
{ |
|||
path: 'user', |
|||
component: () => |
|||
import(/* webpackChunkName: 'example' */ '@/views/example/user.vue') |
|||
} |
|||
|
|||
] |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
export default [ |
|||
{ |
|||
path: '/', |
|||
component: () => import(/* webpackChunkName: 'root' */ '@/views/index.vue') |
|||
}, |
|||
{ |
|||
path: '/login', |
|||
component: () => import(/* webpackChunkName: 'root' */ '@/views/login.vue') |
|||
} |
|||
] |
|||
@ -0,0 +1,15 @@ |
|||
import Vue from 'vue' |
|||
import Vuex from 'vuex' |
|||
|
|||
Vue.use(Vuex) |
|||
|
|||
const modules = {} |
|||
const require_module = require.context('./modules', false, /.js$/) |
|||
require_module.keys().forEach(file_name => { |
|||
modules[file_name.slice(2, -3)] = require_module(file_name).default |
|||
}) |
|||
|
|||
export default new Vuex.Store({ |
|||
modules: modules, |
|||
strict: process.env.NODE_ENV !== 'production' |
|||
}) |
|||
@ -0,0 +1,44 @@ |
|||
import { |
|||
api |
|||
} from '@/api' |
|||
|
|||
const state = { |
|||
banner: [] |
|||
} |
|||
|
|||
const getters = { |
|||
bannerCount: state => { |
|||
return state.banner.length |
|||
} |
|||
} |
|||
|
|||
const actions = { |
|||
getBanner({ |
|||
commit |
|||
}) { |
|||
api.get('banner/list', { |
|||
params: { |
|||
categoryid: 1 |
|||
} |
|||
}).then(res => { |
|||
commit('setBanner', res.data.banner) |
|||
}) |
|||
} |
|||
} |
|||
|
|||
const mutations = { |
|||
setBanner(state, banner) { |
|||
state.banner = banner |
|||
}, |
|||
removeLast(state) { |
|||
state.banner.splice(state.banner.length - 1, 1) |
|||
} |
|||
} |
|||
|
|||
export default { |
|||
namespaced: true, |
|||
state, |
|||
actions, |
|||
getters, |
|||
mutations |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
/** |
|||
* 存放全局公用状态 |
|||
*/ |
|||
|
|||
const state = {} |
|||
|
|||
const getters = {} |
|||
|
|||
const actions = {} |
|||
|
|||
const mutations = {} |
|||
|
|||
export default { |
|||
namespaced: true, |
|||
state, |
|||
actions, |
|||
getters, |
|||
mutations |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
import { |
|||
api |
|||
} from '@/api' |
|||
|
|||
const state = { |
|||
token: localStorage.token, |
|||
failuretime: localStorage.failuretime |
|||
} |
|||
|
|||
const getters = { |
|||
isLogin: state => { |
|||
let retn = false |
|||
if (state.token != null) { |
|||
let unix = Date.parse(new Date()) |
|||
if (unix < state.failuretime * 1000) { |
|||
retn = true |
|||
} |
|||
} |
|||
return retn |
|||
} |
|||
} |
|||
|
|||
const actions = { |
|||
login({ |
|||
commit |
|||
}, data) { |
|||
return new Promise((resolve, reject) => { |
|||
// 模拟登录成功,写入 token 信息
|
|||
commit('setData', { |
|||
token: '1234567890', |
|||
failuretime: Date.parse(new Date()) / 1000 + 24 * 60 * 60 |
|||
}) |
|||
resolve() |
|||
|
|||
// api.post('member/login', data).then(res => {
|
|||
// commit('setData', res.data)
|
|||
// resolve(res)
|
|||
// }).catch(error => {
|
|||
// reject(error)
|
|||
// })
|
|||
}) |
|||
} |
|||
} |
|||
|
|||
const mutations = { |
|||
setData(state, data) { |
|||
localStorage.setItem('token', data.token) |
|||
localStorage.setItem('failuretime', data.failuretime) |
|||
state.token = data.token |
|||
state.failuretime = data.failuretime |
|||
} |
|||
} |
|||
|
|||
export default { |
|||
namespaced: true, |
|||
state, |
|||
actions, |
|||
getters, |
|||
mutations |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
export default { |
|||
signSecret: '916lWh2WMcbSWiHv' |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
export default { |
|||
install(Vue) { |
|||
Vue.prototype.$toLogin = function() { |
|||
this.$router.push({ |
|||
path: '/login', |
|||
query: { |
|||
redirect: this.$route.fullPath |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,84 @@ |
|||
import md5 from 'js-md5' |
|||
|
|||
export default class signMd5Utils { |
|||
/** |
|||
* json参数升序 |
|||
* @param jsonObj 发送参数 |
|||
*/ |
|||
|
|||
static sortAsc(jsonObj) { |
|||
let arr = new Array() |
|||
let num = 0 |
|||
for (let i in jsonObj) { |
|||
arr[num] = i |
|||
num++ |
|||
} |
|||
let sortArr = arr.sort() |
|||
let sortObj = {} |
|||
for (let i in sortArr) { |
|||
sortObj[sortArr[i]] = jsonObj[sortArr[i]] |
|||
} |
|||
return sortObj |
|||
} |
|||
|
|||
/** |
|||
* @param url 请求的url,应该包含请求参数(url的?后面的参数) |
|||
* @param requestParams 请求参数(POST的JSON参数) |
|||
* @returns {string} 获取签名 |
|||
*/ |
|||
static getSign(url, requestParams) { |
|||
|
|||
let urlParams = this.parseQueryString(url) |
|||
let jsonObj = this.mergeObject(urlParams, requestParams) |
|||
let requestBody = this.sortAsc(jsonObj) |
|||
return md5('916lWh2WMcbSWiHv' + JSON.stringify(requestBody)).toLowerCase() |
|||
} |
|||
|
|||
/** |
|||
* @param url 请求的url |
|||
* @returns {{}} 将url中请求参数组装成json对象(url的?后面的参数) |
|||
*/ |
|||
static parseQueryString(url) { |
|||
let urlReg = /^[^\?]+\?([\w\W]+)$/, |
|||
paramReg = /([^&=]+)=([\w\W]*?)(&|$|#)/g, |
|||
urlArray = urlReg.exec(url), |
|||
result = {} |
|||
if (urlArray && urlArray[1]) { |
|||
let paramString = urlArray[1], paramResult |
|||
while ((paramResult = paramReg.exec(paramString)) != null) { |
|||
result[paramResult[1]] = '' + paramResult[2] |
|||
} |
|||
} |
|||
return result |
|||
} |
|||
|
|||
/** |
|||
* @returns {*} 将两个对象合并成一个 |
|||
*/ |
|||
static mergeObject(objectOne, objectTwo) { |
|||
if (Object.keys(objectTwo).length > 0) { |
|||
for (let key in objectTwo) { |
|||
// eslint-disable-next-line no-prototype-builtins
|
|||
if (objectTwo.hasOwnProperty(key) === true) { |
|||
objectOne[key] = '' + objectTwo[key] |
|||
} |
|||
} |
|||
} |
|||
return objectOne |
|||
} |
|||
|
|||
static urlEncode(param, key, encode) { |
|||
if (param == null) return '' |
|||
let paramStr = '' |
|||
let t = typeof (param) |
|||
if (t == 'string' || t == 'number' || t == 'boolean') { |
|||
paramStr += '&' + key + '=' + ((encode == null || encode) ? encodeURIComponent(param) : param) |
|||
} else { |
|||
for (let i in param) { |
|||
let k = key == null ? i : key + (param instanceof Array ? '[' + i + ']' : '.' + i) |
|||
paramStr += this.urlEncode(param[i], k, encode) |
|||
} |
|||
} |
|||
return paramStr |
|||
} |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
signMd5Utils.js |
|||
<template> |
|||
<div> |
|||
<button type="button" @click="getInfo">获取数据</button> |
|||
<button type="button" @click="getTest">验签</button> |
|||
<img v-for="(item, index) in banner" :key="index" :src="item.image"> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
banner: [] |
|||
} |
|||
}, |
|||
methods: { |
|||
getTest() { |
|||
this.$api.post('/api/v1/user/add').then(res => { |
|||
console.log(res) |
|||
}) |
|||
}, |
|||
getInfo() { |
|||
this.$axios.all([ |
|||
this.$api.get('banner/list', { |
|||
params: { |
|||
categoryid: 1 |
|||
} |
|||
}), |
|||
this.$api.get('banner/list', { |
|||
params: { |
|||
categoryid: 2 |
|||
} |
|||
}) |
|||
]).then( |
|||
this.$axios.spread((acct, perms) => { |
|||
this.banner = acct.data.banner.concat( |
|||
perms.data.banner |
|||
) |
|||
}) |
|||
) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
img { |
|||
display: block; |
|||
width: 300px; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,21 @@ |
|||
<template> |
|||
<div> |
|||
<p>这是一个非全局组件,需要在页面上引用该组件才能使用</p> |
|||
<ExampleList :list="list" /> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import ExampleList from '@/components/ExampleList' |
|||
|
|||
export default { |
|||
components: { |
|||
ExampleList |
|||
}, |
|||
data() { |
|||
return { |
|||
list: ['张三', '李四', '王五'] |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
@ -0,0 +1,31 @@ |
|||
<template> |
|||
<div> |
|||
<button type="button" @click="setCookie">设置cookie</button> |
|||
<button type="button" @click="removeCookie">删除cookie</button> |
|||
<button type="button" @click="isSetCookie">判断cookie是否设置</button> |
|||
<div>a的cookie值是:{{ cookie }}</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
cookie: '' |
|||
} |
|||
}, |
|||
methods: { |
|||
setCookie() { |
|||
this.$cookies.set('a', 'abc') |
|||
this.cookie = this.$cookies.get('a') |
|||
}, |
|||
removeCookie() { |
|||
this.$cookies.remove('a', 'abc') |
|||
this.cookie = this.$cookies.get('a') |
|||
}, |
|||
isSetCookie() { |
|||
alert(this.$cookies.isKey('a')) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
@ -0,0 +1,25 @@ |
|||
<template> |
|||
<div> |
|||
<p>全局组件会自动注册</p> |
|||
<p>使用方法:</p> |
|||
<ol> |
|||
<li>全局组件统一放在 ./src/components/global/ 目录下,需要注意各个组件按文件夹区分,文件夹名称与组件名无关联</li> |
|||
<li>文件夹内至少保留一个文件名为 index 的组件入口,例如 index.vue</li> |
|||
<li>普通组件必须设置 name 并保证其唯一,自动注册会将组件的 name 设为组件名,可参考 <RouterLink to="/example/svgicon">SvgIcon</RouterLink> 组件写法</li> |
|||
<li>如果组件是通过 js 进行调用,则确保组件入口文件为 index.js,下面演示 ExampleNotice 组件,通过 js 调用并展示 Notice</li> |
|||
</ol> |
|||
<a href="javascript:;" @click="showNotice">显示Notice</a> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
methods: { |
|||
showNotice() { |
|||
this.$exampleNotice({ |
|||
content: '我是Notice!' |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
@ -0,0 +1,18 @@ |
|||
<template> |
|||
<div>注意 title 的变化</div> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
title: '我是这个页面的title噢' |
|||
} |
|||
}, |
|||
metaInfo() { |
|||
return { |
|||
title: this.title |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
@ -0,0 +1,5 @@ |
|||
<template> |
|||
<div> |
|||
<div>params:{{ $route.params.test }}</div> |
|||
</div> |
|||
</template> |
|||
@ -0,0 +1,20 @@ |
|||
<template> |
|||
<div> |
|||
<p>如果未登录,会跳转到登录页,如果已登录,则弹出用户信息。</p> |
|||
<button @click="user">点我</button> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
methods: { |
|||
user() { |
|||
if (this.$store.getters['token/isLogin']) { |
|||
alert('token信息:' + this.$store.state.token.token) |
|||
} else { |
|||
this.$toLogin() |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
@ -0,0 +1,3 @@ |
|||
<template> |
|||
<div>token信息:{{ $store.state.token.token }}</div> |
|||
</template> |
|||
@ -0,0 +1,5 @@ |
|||
<template> |
|||
<div> |
|||
<div>query:{{ $route.query.test }}</div> |
|||
</div> |
|||
</template> |
|||
@ -0,0 +1,26 @@ |
|||
<template> |
|||
<div> |
|||
<p>可以修改一下 input 框内的值,然后点击刷新按钮查看效果</p> |
|||
<p> |
|||
<input v-model="value" type="text"> |
|||
<button type="button" @click="plus">+1</button> |
|||
</p> |
|||
<button type="button" @click="reload">刷新</button> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
inject: ['reload'], |
|||
data() { |
|||
return { |
|||
value: 0 |
|||
} |
|||
}, |
|||
methods: { |
|||
plus() { |
|||
this.value += 1 |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
@ -0,0 +1,50 @@ |
|||
<template> |
|||
<div class="sprites"> |
|||
<div class="address" /> |
|||
<div class="feedback" /> |
|||
<div class="payment" /> |
|||
<div class="info"> |
|||
在 vue.config.js 里配置精灵图路径等信息,如果要新增一个精灵图目录,则先复制一份 new SpritesmithPlugin() ,修改目录名和文件名,然后重新运行 serve 任务即可。 |
|||
</div> |
|||
<img :src="logo" class="logo"> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
logo: '' |
|||
} |
|||
}, |
|||
created() { |
|||
// 在 js 里引用图片,需要用 require 的方式 |
|||
this.logo = require('../../assets/images/example.png') |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.sprites { |
|||
padding: 10px; |
|||
.address, |
|||
.feedback, |
|||
.payment { |
|||
display: inline-block; |
|||
margin-right: 10px; |
|||
} |
|||
.address { |
|||
@include example-sprite(address); |
|||
} |
|||
.feedback { |
|||
@include example-sprite(feedback); |
|||
} |
|||
.payment { |
|||
@include example-sprite(payment); |
|||
} |
|||
} |
|||
.logo { |
|||
width: 200px; |
|||
height: 200px; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,18 @@ |
|||
<template> |
|||
<div> |
|||
<p>这是两个 Svg Icon 图标</p> |
|||
<svg-icon icon-class="example" class-name="example-icon" /> |
|||
<svg-icon icon-class="example.color" class-name="example-icon" /> |
|||
<p>使用方法:</p> |
|||
<ol> |
|||
<li>上 <a href="https://www.iconfont.cn/" target="_blank">Iconfont</a> 下载需要的 svg 图标</li> |
|||
<li>将 svg 文件放入 assets/icons 目录,文件名即为 icon-class</li> |
|||
</ol> |
|||
</div> |
|||
</template> |
|||
|
|||
<style scoped> |
|||
.example-icon { |
|||
font-size: 48px; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,94 @@ |
|||
<template> |
|||
<div> |
|||
<el-table |
|||
:data="tableData"> |
|||
<el-table-column |
|||
prop="id" |
|||
label="Id" |
|||
width="180"> |
|||
</el-table-column> |
|||
<el-table-column |
|||
prop="date" |
|||
label="日期" |
|||
width="180"> |
|||
</el-table-column> |
|||
<el-table-column |
|||
prop="name" |
|||
label="姓名" |
|||
width="180"> |
|||
</el-table-column> |
|||
<el-table-column |
|||
prop="gender" |
|||
label="性别" |
|||
width="180"> |
|||
</el-table-column> |
|||
<el-table-column |
|||
label="操作" |
|||
width="100"> |
|||
<template slot-scope="scope"> |
|||
<el-button @click="handleDeleteClick(scope.row,scope)" type="text" size="small">删除</el-button> |
|||
<el-button type="text" size="small">编辑</el-button> |
|||
</template> |
|||
</el-table-column> |
|||
</el-table> |
|||
<el-pagination |
|||
@size-change="handleSizeChange" |
|||
@current-change="handleCurrentChange" |
|||
:current-page="current" |
|||
:page-sizes="[10, 20, 50, 100]" |
|||
:page-size="size" |
|||
layout="total, sizes, prev, pager, next, jumper" |
|||
:total="total"> |
|||
</el-pagination> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: 'user', |
|||
data() { |
|||
return { |
|||
tableData: [], |
|||
current: 1, |
|||
size: 10, |
|||
total: 0 |
|||
} |
|||
}, |
|||
methods: { |
|||
handleSizeChange(size) { |
|||
debugger |
|||
this.size = size |
|||
this.getData() |
|||
}, |
|||
handleCurrentChange(current) { |
|||
this.current = current |
|||
this.getData() |
|||
}, |
|||
handleDeleteClick(row, scope) { |
|||
this.$api.post(`/api/v1/user/delete?id=${row.id}`).then(res => { |
|||
this.$message({ |
|||
message: '删除成功', |
|||
type: 'success' |
|||
}) |
|||
}) |
|||
}, |
|||
getData() { |
|||
let params = {current: this.current, size: this.size} |
|||
this.$api.get('/api/v1/user/page', { |
|||
params: params |
|||
}).then(res => { |
|||
this.tableData = res.data.records |
|||
this.size = res.data.size |
|||
this.total = res.data.total |
|||
this.current = res.data.current |
|||
}) |
|||
} |
|||
}, |
|||
mounted() { |
|||
this.getData() |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
</style> |
|||
@ -0,0 +1,42 @@ |
|||
<template> |
|||
<div> |
|||
<button type="button" @click="getInfo">获取数据</button> |
|||
<button type="button" @click="removeLast">删除最后一条数据</button> |
|||
<button type="button" @click="getLength">获取数据长度</button> |
|||
<img v-for="(item, index) in banner" :key="index" :src="item.image"> |
|||
<div v-if="bannerCount">现在你可以切换路由,你会发现,切换回来后,数据还在。</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { mapGetters, mapActions, mapMutations, mapState } from 'vuex' |
|||
|
|||
export default { |
|||
computed: { |
|||
...mapState({ |
|||
banner: state => state.example.banner |
|||
}), |
|||
...mapGetters({ |
|||
bannerCount: 'example/bannerCount' |
|||
}) |
|||
}, |
|||
methods: { |
|||
...mapActions({ |
|||
getInfo: 'example/getBanner' |
|||
}), |
|||
...mapMutations({ |
|||
removeLast: 'example/removeLast' |
|||
}), |
|||
getLength() { |
|||
alert(this.bannerCount) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
img { |
|||
display: block; |
|||
width: 300px; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,5 @@ |
|||
<template> |
|||
<div> |
|||
<RouterLink to="/example">演示Demo</RouterLink> |
|||
</div> |
|||
</template> |
|||
@ -0,0 +1,28 @@ |
|||
<template> |
|||
<div> |
|||
<button @click="login">模拟登录</button> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
methods: { |
|||
login() { |
|||
this.$store.dispatch('token/login').then(() => { |
|||
// 登录成功后路由跳回 |
|||
if (this.$route.query.redirect) { |
|||
this.$router.replace({ |
|||
path: this.$route.query.redirect |
|||
}) |
|||
} else { |
|||
if (window.history.length <= 1) { |
|||
this.$router.push({ path: '/' }) |
|||
} else { |
|||
this.$router.go(-1) |
|||
} |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
@ -0,0 +1,95 @@ |
|||
const fs = require('fs') |
|||
const path = require('path') |
|||
const spritesmithPlugin = require('webpack-spritesmith') |
|||
// 基础路径 注意发布之前要先修改这里
|
|||
|
|||
const spritesmithTasks = [] |
|||
fs.readdirSync('src/assets/sprites').map(dirname => { |
|||
if (fs.statSync(`src/assets/sprites/${dirname}`).isDirectory()) { |
|||
spritesmithTasks.push( |
|||
new spritesmithPlugin({ |
|||
src: { |
|||
cwd: path.resolve(__dirname, `src/assets/sprites/${dirname}`), |
|||
glob: '*.png' |
|||
}, |
|||
target: { |
|||
image: path.resolve(__dirname, `src/assets/sprites/${dirname}.[hash].png`), |
|||
css: [ |
|||
[path.resolve(__dirname, `src/assets/sprites/_${dirname}.scss`), { |
|||
format: 'handlebars_based_template', |
|||
spritesheetName: dirname |
|||
}] |
|||
] |
|||
}, |
|||
customTemplates: { |
|||
'handlebars_based_template': path.resolve(__dirname, 'scss.template.handlebars') |
|||
}, |
|||
// 样式文件中调用雪碧图地址写法
|
|||
apiOptions: { |
|||
cssImageRef: `~${dirname}.[hash].png` |
|||
}, |
|||
spritesmithOptions: { |
|||
algorithm: 'binary-tree', |
|||
padding: 10 |
|||
} |
|||
}) |
|||
) |
|||
} |
|||
}) |
|||
|
|||
module.exports = { |
|||
publicPath: '', |
|||
lintOnSave: true, |
|||
devServer: { |
|||
open: true, |
|||
host: 'localhost', |
|||
port: '8081', |
|||
proxy: { |
|||
'/api': { |
|||
target: 'http://localhost:8888', // 要请求的地址
|
|||
ws: true, |
|||
changeOrigin: true, |
|||
pathRewrite: { |
|||
'^/api': '/api' |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
configureWebpack: { |
|||
resolve: { |
|||
modules: ['node_modules', 'assets/sprites'] |
|||
}, |
|||
plugins: [ |
|||
...spritesmithTasks |
|||
] |
|||
}, |
|||
chainWebpack: config => { |
|||
const oneOfsMap = config.module.rule('scss').oneOfs.store |
|||
oneOfsMap.forEach(item => { |
|||
item.use('sass-resources-loader') |
|||
.loader('sass-resources-loader') |
|||
.options({ |
|||
resources: [ |
|||
'./src/assets/styles/resources/*.scss', |
|||
'./src/assets/sprites/*.scss' |
|||
] |
|||
}) |
|||
.end() |
|||
}) |
|||
config.module |
|||
.rule('svg') |
|||
.exclude.add(path.join(__dirname, 'src/assets/icons')) |
|||
.end() |
|||
config.module |
|||
.rule('icons') |
|||
.test(/\.svg$/) |
|||
.include.add(path.join(__dirname, 'src/assets/icons')) |
|||
.end() |
|||
.use('svg-sprite-loader') |
|||
.loader('svg-sprite-loader') |
|||
.options({ |
|||
symbolId: 'icon-[name]' |
|||
}) |
|||
.end() |
|||
} |
|||
} |
|||