组件结构
html
<template>
<!-- 在Vue2.x template 内必须有且仅有一个根节点 -->
<div>
<!-- 内容... -->
</div>
</template>
TypeScript
<script lang='ts'>
import { Component, Vue } from 'vue-property-decorator'
@Component({
name: 'ElementName', //组件名称
components: {} //此组件使用其他组件的注册地点
})
export default class extends Vue {
// ...
}
</script>
css/scss
<style lang='scss' scoped> /*scoped 防止组件样式污染全局*/
/* ... */
.a{
.b{
.c{
color:red;
}
}
}
</style>
vue指令(官网)
https://cn.vuejs.org/v2/api/#%E6%8C%87%E4%BB%A4
数据
成员变量 和 get set 函数
<template>
<!-- 在Vue2.x template 内必须有且仅有一个根节点 -->
<div>
<!-- 内容... -->
name:{{name}}
{{greeting}}
<input v-model="greeting" />
</div>
</template>
<script lang='ts'>
import { Component, Vue } from 'vue-property-decorator'
@Component({
name: 'ElementName', //组件名称
components: {} //此组件使用其他组件的注册地点
})
export default class extends Vue {
// 成员变量
// [修饰符] 成员变量[:类型] = 值
// 成员变量 有初始化值的时候会被监控其值的变化
private name:string = "Guquanlengyue"
// get set 函数名可以一致 一般成对出现
// get 函数:get <函数名>(){}
// 函数必须有返回值
// 函数名直接当变量来使用
// 在调用取值的时候触发函数,返回函数结果
// 若函数内部使用的变量发生改变 会触发函数,维护函数结果最新
get greeting(){
return "helloworld,"+this.name
}
// set 函数:set <函数名>(){}
// 在尝试给greeting赋值的时候触发
// 尝试赋的值会通过函数参数传入
set greeting(name:string){
this.name = `${name}!`;
}
}
</script>
@Watch 监听
<template>
<!-- 在Vue2.x template 内必须有且仅有一个根节点 -->
<div>
</div>
</template>
<script lang='ts'>
import { Component, Vue, Watch } from 'vue-property-decorator'
@Component({
name: 'ElementName', //组件名称
components: {} //此组件使用其他组件的注册地点
})
export default class extends Vue {
private name:string = "Guquanlengyue"
// @Watch(path:string,option:{ immediate?: true, deep?: true }|undefined) 监听装饰器
// path: 成员变量名or get函数名
// immediate: 是否在Vue class 初始化完成的时候立即执行被装饰的函数
// deep: 是否深度监听path指定对象的所有子对象
// 若deep:false 只有当对象的引用地址改变才会触发被装饰的函数
// 若deep:true 对象的子对象引用地址改变就会触发被装饰函数
@Watch("name",{ immediate: true, deep: true })
private doSomething(newValue,oldValue){
// do something ...
}
}
</script>
函数
生命周期与生命周期函数
自定义组件
组件调用
<template>
<!-- 在Vue2.x template 内必须有且仅有一个根节点 -->
<div>
<!--<组件名 [[参数]...]>[组件插槽填充]</组件名>-->
</div>
</template>
<script lang='ts'>
import <组件名> from "path/to/component"
import { Component, Vue, Watch } from 'vue-property-decorator'
@Component({
name: 'ElementName', //组件名称
components: {[<组件名>...]} //此组件使用其他组件的注册地点
})
export default class extends Vue {
}
</script>
组件接收参数
<script lang='ts'>
import { Component, Vue, Prop } from 'vue-property-decorator'
@Component({
name: 'ElementName', //组件名称
components: {} //此组件使用其他组件的注册地点
})
export default class extends Vue {
// required: 是否一定需要这个参数, 若required:true, 调用组件不给该参数赋值是,会报错
// type: 指定参数类型 不能使用自定义类型
// default: 参数默认值
@Prop({ required: true, type: String, default: 'helloworld' }) propName?: string
@Prop({ required: true, type: Array, default: ()=>{return ['list1','list2']} }) propList?: List<string>
@Prop({ required: true, type: Object, default: ()=>{return {name:'xiaoming',age:'6'}} }) propName?: {name:string,age:string}
}
</script>
组件抛出结果
子组件
<template>
<!-- 在Vue2.x template 内必须有且仅有一个根节点 -->
<div>
<div @click='handleClick'> ...假装有内容 </div>
</div>
</template>
<script lang='ts'>
import { Component, Vue, Prop } from 'vue-property-decorator'
@Component({
name: 'chird', //组件名称
components: {} //此组件使用其他组件的注册地点
})
export default class extends Vue {
handleClick(){
// $emit('抛出的事件名', 可以带上需要的参数)
this.$emit('eventName', [someArg[,someArgs]])
}
}
</script>
父组件
<template>
<!-- 在Vue2.x template 内必须有且仅有一个根节点 -->
<div>
<chird @eventName='handleChirdEvent'> ...假装有内容 </chird>
</div>
</template>
<script lang='ts'>
import { Component, Vue, Prop } from 'vue-property-decorator'
import chird from './chird.vue'
@Component({
name: 'parent', //组件名称
components: {chird} //此组件使用其他组件的注册地点
})
export default class extends Vue {
// 在父组件中捕获子组件抛出的事件
handleChirdEvent(args){
// ....
}
}
</script>
项目文件结构
sadais-cabinet-console ├── LICENSE // 最外层都是配置相关的文件 ├── README-zh.md ├── README.md ├── babel.config.js
├── cypress.json
├── gulpfile.js
├── jest.config.js
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
│ ├── check-token.js
│ ├── favicon.ico
│ ├── favicon.png
│ ├── img
│ ├── index.html
│ ├── manifest.json
│ └── robots.txt
├── src // 平时开发关注最多的文件
│ ├── App.vue
│ ├── api // 与服务端请求数据的api文件夹
│ ├── typings // 类型文件 配合api使用
│ ├── assets // 静态文件存放文件夹
│ ├── components // 公用组件文件夹
│ ├── consts // 基础常量文件夹
│ ├── directives
│ ├── filters // 自定义模板过滤器文件夹
│ ├── icons
│ ├── lang // 国际化翻译相关
│ ├── layout // 全局布局文件夹
│ ├── main.ts
│ ├── mixin
│ ├── permission.ts
│ ├── pwa
│ ├── router // 路由文件
│ ├── settings.ts
│ ├── shims-tsx.d.ts
│ ├── shims.d.ts
│ ├── store // 常用存储在storage里的数据
│ ├── styles
│ ├── utils // 工具类文件夹
│ └── views // 各页面存放文件夹
├── tests
│ └── unit
├── tsconfig.json
└── vue.config.js
与服务端进行数据交互
1、新建API
// @/typings/modules/bannerts
/**
* banner信息
*/
export interface IBanner {
/** 名称 */
name?: string
/** 图片地址 */
pic?: string
/** 消息跳转值 */
jumpVal?: string
/** 排序 */
sort?: number
/** 跳转类型 */
jumpType?: number
/** 栏目(activity活动index首页顶部) */
column?: string
/** Banner有效开始时间 */
startTime?: string
/** Banner有效结束时间 */
endTime?: string
/** 是否上架(1是0否) */
isUse?: number
/** 内容 */
content?: string
/** 记录ID */
id?: string
/** 创建时间 */
createDate?: string
}
// @/api/banner.ts
import { IBanner } from '@/typings/modules/banner'
import request from '@/utils/request'
/**
* 加载列表
* @param {Number} pageno 当前页
* @param {Number} pagesize 每页大小
*/
export async function apiQueryBaseList(pageno: Number, pagesize: Number) {
const { data } = await request.get<HttpResponse>(
'/api/ca/cabinet/v1/banner/findlist', {
params: { pageno, pagesize } // aprams:{参数名:参数值[,参数名:参数值]}
})
return data
}
/**
* 创建/更新
* @param {Object} params 对象信息
*/
export async function apiSaveOrUpdate(params: IBanner) {
const { data } = await request.post<HttpResponse>(
'/api/ca/cabinet/v1/banner/saveorupdate',
params
)
return data
}
2、使用API
需要临时缓存数据状态
// @/store/modules/banner.ts
import { apiDelete, apiQueryList } from '@/api/banner'
import consts from '@/consts'
import store from '@/store'
import { SearchCondition, IBanner, DefaultBanner } from '@/typings/modules/banner'
import dayjs from 'dayjs'
import { Notification } from 'element-ui'
import { Action, getModule, Module, Mutation, VuexModule } from 'vuex-module-decorators'
import cloneDeep from 'lodash/cloneDeep'
@Module({ dynamic: true, store, name: 'banner', namespaced: true })
class Banner extends VuexModule implements IBannerState {
@Mutation
public CHANGE_STATE(payload: { key: string; value: any }) {
const { key, value } = payload
const _this = this as any
if (Object.prototype.hasOwnProperty.call(_this, key)) {
_this[key] = value
}
}
@Action
public async handleQuery(condition?: SearchCondition) {
condition = cloneDeep(condition)
...
this.CHANGE_STATE({ key: 'loading', value: true })
const params = {
pageNo: condition.pageNo,
pageSize: condition.pageSize,
...
}
const { data, head: { ret, msg } = { ret: 1, msg: '列表加载失败' } } = await apiQueryList(
params
)
if (ret === consts.RET_CODE.SUCCESS) {
this.CHANGE_STATE({ key: 'searchResult', value: data })
}
this.CHANGE_STATE({ key: 'loading', value: false })
}
}
// @/views/banner/list.vue
<script lang='ts'>
import { Component, Vue } from 'vue-property-decorator'
import { formatJson } from '@/utils'
import { exportJson2Excel } from '@/utils/excel'
import Pagination from '@/components/Pagination/index.vue' // secondary package based on el-pagination
import { IBanner, SearchCondition } from '@/typings/modules/banner'
import { BannerModule } from '@/store/modules/banner'
@Component({
name: 'BannerList',
components: {
Pagination
}
})
export default class extends Vue {
// ...
// 当这个页面创建时
created() {
// 如果 BannerModule 对象缓存中有数据, 则跳过加载数据
if (BannerModule.searchResult.result.length) return
this.handleQuery()
}
private handleQuery(page?: { pageNo: number; pageSize: number }) {
if (page) {
// 重置页码
page.pageNo && (this.form.pageNo = page.pageNo)
page.pageSize && (this.form.pageSize = page.pageSize)
}
BannerModule.handleQuery(this.form)
}
}
</script>
不需要缓存数据状态
// @/views/banner/list.vue
<script lang='ts'>
import { Component, Vue } from 'vue-property-decorator'
import { formatJson } from '@/utils'
import { exportJson2Excel } from '@/utils/excel'
import Pagination from '@/components/Pagination/index.vue' // secondary package based on el-pagination
import { IBanner, SearchCondition } from '@/typings/modules/banner'
import { apiDelete, apiQueryList } from '@/api/banner'
@Component({
name: 'BannerList',
components: {
Pagination
}
})
export default class extends Vue {
// ...
// 当这个页面创建时
created() {
this.handleQuery()
}
private handleQuery(page?: { pageNo: number; pageSize: number }) {
if (page) {
// 重置页码
page.pageNo && (this.form.pageNo = page.pageNo)
page.pageSize && (this.form.pageSize = page.pageSize)
}
const { data, head: { ret, msg } = { ret: 1, msg: '列表加载失败' } } = await apiQueryList(
params
)
// ...
}
}
</script>
路由
目录结构
router
├── index.ts
└── modules
├── banner.ts
├── cabinet-product.ts
├── cabinet.ts
├── distribution.ts
├── hire-record.ts
├── order.ts
├── permission.ts
├── product.ts
├── statistic.ts
├── system-config.ts
└── user-info.ts
svg组件生成
- 将svg图片文件放到
@/assets/img/
- 控制台内项目根目录下运行命令
npm run svg
- svg组件将会生成到
@/assets/components
目录下侧边栏配置
路由文件内容与页面信息关系
当且仅当 只有一个可显示子菜单时, 直接使用子菜单的名字和目标路由 ```typescript // 当设置 true 的时候该路由不会在侧边栏出现 如401,login等页面,或者如一些编辑页面/edit/1 hidden: true // (默认 false)
//当设置 noRedirect 的时候该路由在面包屑导航中不可被点击 redirect: ‘noRedirect’
// 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式—如组件页面
// 只有一个时,会将那个子路由当做根路由显示在侧边栏—如引导页面
// 若你想不管路由下面的 children 声明的个数都显示你的根路由
// 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由
alwaysShow: true
component: ()=>import(‘permission/index’), // 使用的组件
name: ‘router-name’ // 设定路由的名字,一定要填写不然使用
// 当路由设置了该属性,则会高亮相对应的侧边栏。 // 这在某些场景非常有用,比如:一个文章的列表页路由为:/article/list // 点击文章进入文章详情页,这时候路由为/article/1,但你想在侧边栏高亮文章列表的路由,就可以进行如下设置 activeMenu: ‘/article/list’ }
<a name="JWeaD"></a>
### 正式使用这个路由
![image.png](https://cdn.nlark.com/yuque/0/2021/png/1346953/1610614104461-6199e405-d063-4e1e-95a0-b601138551a0.png#align=left&display=inline&height=1019&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1019&originWidth=1186&size=197860&status=done&style=none&width=1186)<br />必须要在 `@/router/index.ts`内引入并要添加在 `asyncRoutes` 内才能在侧边栏正式生效. 且 `asyncRoutes` 与侧边栏循序有关.
<a name="LFnSb"></a>
### 页面跳转与参数携带
```typescript
private handleEdit(row?: IBanner) {
// 使用组件方式编辑
this.showEdit = true
let query = {}
if (row && row.id) {
query = {
id: row.id
}
}
this.$router.push({ path: '/banner/edit', query })
// this.$router.push({name:'BannerEdit',query})
}