组件结构

html

  1. <template>
  2. <!-- 在Vue2.x template 内必须有且仅有一个根节点 -->
  3. <div>
  4. <!-- 内容... -->
  5. </div>
  6. </template>

TypeScript

  1. <script lang='ts'>
  2. import { Component, Vue } from 'vue-property-decorator'
  3. @Component({
  4. name: 'ElementName', //组件名称
  5. components: {} //此组件使用其他组件的注册地点
  6. })
  7. export default class extends Vue {
  8. // ...
  9. }
  10. </script>

css/scss

  1. <style lang='scss' scoped> /*scoped 防止组件样式污染全局*/
  2. /* ... */
  3. .a{
  4. .b{
  5. .c{
  6. color:red;
  7. }
  8. }
  9. }
  10. </style>

vue指令(官网)

https://cn.vuejs.org/v2/api/#%E6%8C%87%E4%BB%A4

数据

成员变量 和 get set 函数

  1. <template>
  2. <!-- 在Vue2.x template 内必须有且仅有一个根节点 -->
  3. <div>
  4. <!-- 内容... -->
  5. name:{{name}}
  6. {{greeting}}
  7. <input v-model="greeting" />
  8. </div>
  9. </template>
  10. <script lang='ts'>
  11. import { Component, Vue } from 'vue-property-decorator'
  12. @Component({
  13. name: 'ElementName', //组件名称
  14. components: {} //此组件使用其他组件的注册地点
  15. })
  16. export default class extends Vue {
  17. // 成员变量
  18. // [修饰符] 成员变量[:类型] = 值
  19. // 成员变量 有初始化值的时候会被监控其值的变化
  20. private name:string = "Guquanlengyue"
  21. // get set 函数名可以一致 一般成对出现
  22. // get 函数:get <函数名>(){}
  23. // 函数必须有返回值
  24. // 函数名直接当变量来使用
  25. // 在调用取值的时候触发函数,返回函数结果
  26. // 若函数内部使用的变量发生改变 会触发函数,维护函数结果最新
  27. get greeting(){
  28. return "helloworld,"+this.name
  29. }
  30. // set 函数:set <函数名>(){}
  31. // 在尝试给greeting赋值的时候触发
  32. // 尝试赋的值会通过函数参数传入
  33. set greeting(name:string){
  34. this.name = `${name}!`;
  35. }
  36. }
  37. </script>

@Watch 监听

  1. <template>
  2. <!-- 在Vue2.x template 内必须有且仅有一个根节点 -->
  3. <div>
  4. </div>
  5. </template>
  6. <script lang='ts'>
  7. import { Component, Vue, Watch } from 'vue-property-decorator'
  8. @Component({
  9. name: 'ElementName', //组件名称
  10. components: {} //此组件使用其他组件的注册地点
  11. })
  12. export default class extends Vue {
  13. private name:string = "Guquanlengyue"
  14. // @Watch(path:string,option:{ immediate?: true, deep?: true }|undefined) 监听装饰器
  15. // path: 成员变量名or get函数名
  16. // immediate: 是否在Vue class 初始化完成的时候立即执行被装饰的函数
  17. // deep: 是否深度监听path指定对象的所有子对象
  18. // 若deep:false 只有当对象的引用地址改变才会触发被装饰的函数
  19. // 若deep:true 对象的子对象引用地址改变就会触发被装饰函数
  20. @Watch("name",{ immediate: true, deep: true })
  21. private doSomething(newValue,oldValue){
  22. // do something ...
  23. }
  24. }
  25. </script>

函数

生命周期与生命周期函数

Vue2.x(vue-property-decorator) - 图1

自定义组件

组件调用

  1. <template>
  2. <!-- 在Vue2.x template 内必须有且仅有一个根节点 -->
  3. <div>
  4. <!--<组件名 [[参数]...]>[组件插槽填充]</组件名>-->
  5. </div>
  6. </template>
  7. <script lang='ts'>
  8. import <组件名> from "path/to/component"
  9. import { Component, Vue, Watch } from 'vue-property-decorator'
  10. @Component({
  11. name: 'ElementName', //组件名称
  12. components: {[<组件名>...]} //此组件使用其他组件的注册地点
  13. })
  14. export default class extends Vue {
  15. }
  16. </script>

组件接收参数

  1. <script lang='ts'>
  2. import { Component, Vue, Prop } from 'vue-property-decorator'
  3. @Component({
  4. name: 'ElementName', //组件名称
  5. components: {} //此组件使用其他组件的注册地点
  6. })
  7. export default class extends Vue {
  8. // required: 是否一定需要这个参数, 若required:true, 调用组件不给该参数赋值是,会报错
  9. // type: 指定参数类型 不能使用自定义类型
  10. // default: 参数默认值
  11. @Prop({ required: true, type: String, default: 'helloworld' }) propName?: string
  12. @Prop({ required: true, type: Array, default: ()=>{return ['list1','list2']} }) propList?: List<string>
  13. @Prop({ required: true, type: Object, default: ()=>{return {name:'xiaoming',age:'6'}} }) propName?: {name:string,age:string}
  14. }
  15. </script>

组件抛出结果

子组件

  1. <template>
  2. <!-- 在Vue2.x template 内必须有且仅有一个根节点 -->
  3. <div>
  4. <div @click='handleClick'> ...假装有内容 </div>
  5. </div>
  6. </template>
  7. <script lang='ts'>
  8. import { Component, Vue, Prop } from 'vue-property-decorator'
  9. @Component({
  10. name: 'chird', //组件名称
  11. components: {} //此组件使用其他组件的注册地点
  12. })
  13. export default class extends Vue {
  14. handleClick(){
  15. // $emit('抛出的事件名', 可以带上需要的参数)
  16. this.$emit('eventName', [someArg[,someArgs]])
  17. }
  18. }
  19. </script>

父组件

  1. <template>
  2. <!-- 在Vue2.x template 内必须有且仅有一个根节点 -->
  3. <div>
  4. <chird @eventName='handleChirdEvent'> ...假装有内容 </chird>
  5. </div>
  6. </template>
  7. <script lang='ts'>
  8. import { Component, Vue, Prop } from 'vue-property-decorator'
  9. import chird from './chird.vue'
  10. @Component({
  11. name: 'parent', //组件名称
  12. components: {chird} //此组件使用其他组件的注册地点
  13. })
  14. export default class extends Vue {
  15. // 在父组件中捕获子组件抛出的事件
  16. handleChirdEvent(args){
  17. // ....
  18. }
  19. }
  20. </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

@src/ 目录的别名

与服务端进行数据交互

1、新建API

  1. // @/typings/modules/bannerts
  2. /**
  3. * banner信息
  4. */
  5. export interface IBanner {
  6. /** 名称 */
  7. name?: string
  8. /** 图片地址 */
  9. pic?: string
  10. /** 消息跳转值 */
  11. jumpVal?: string
  12. /** 排序 */
  13. sort?: number
  14. /** 跳转类型 */
  15. jumpType?: number
  16. /** 栏目(activity活动index首页顶部) */
  17. column?: string
  18. /** Banner有效开始时间 */
  19. startTime?: string
  20. /** Banner有效结束时间 */
  21. endTime?: string
  22. /** 是否上架(1是0否) */
  23. isUse?: number
  24. /** 内容 */
  25. content?: string
  26. /** 记录ID */
  27. id?: string
  28. /** 创建时间 */
  29. createDate?: string
  30. }
  1. // @/api/banner.ts
  2. import { IBanner } from '@/typings/modules/banner'
  3. import request from '@/utils/request'
  4. /**
  5. * 加载列表
  6. * @param {Number} pageno 当前页
  7. * @param {Number} pagesize 每页大小
  8. */
  9. export async function apiQueryBaseList(pageno: Number, pagesize: Number) {
  10. const { data } = await request.get<HttpResponse>(
  11. '/api/ca/cabinet/v1/banner/findlist', {
  12. params: { pageno, pagesize } // aprams:{参数名:参数值[,参数名:参数值]}
  13. })
  14. return data
  15. }
  16. /**
  17. * 创建/更新
  18. * @param {Object} params 对象信息
  19. */
  20. export async function apiSaveOrUpdate(params: IBanner) {
  21. const { data } = await request.post<HttpResponse>(
  22. '/api/ca/cabinet/v1/banner/saveorupdate',
  23. params
  24. )
  25. return data
  26. }

2、使用API

需要临时缓存数据状态

  1. // @/store/modules/banner.ts
  2. import { apiDelete, apiQueryList } from '@/api/banner'
  3. import consts from '@/consts'
  4. import store from '@/store'
  5. import { SearchCondition, IBanner, DefaultBanner } from '@/typings/modules/banner'
  6. import dayjs from 'dayjs'
  7. import { Notification } from 'element-ui'
  8. import { Action, getModule, Module, Mutation, VuexModule } from 'vuex-module-decorators'
  9. import cloneDeep from 'lodash/cloneDeep'
  10. @Module({ dynamic: true, store, name: 'banner', namespaced: true })
  11. class Banner extends VuexModule implements IBannerState {
  12. @Mutation
  13. public CHANGE_STATE(payload: { key: string; value: any }) {
  14. const { key, value } = payload
  15. const _this = this as any
  16. if (Object.prototype.hasOwnProperty.call(_this, key)) {
  17. _this[key] = value
  18. }
  19. }
  20. @Action
  21. public async handleQuery(condition?: SearchCondition) {
  22. condition = cloneDeep(condition)
  23. ...
  24. this.CHANGE_STATE({ key: 'loading', value: true })
  25. const params = {
  26. pageNo: condition.pageNo,
  27. pageSize: condition.pageSize,
  28. ...
  29. }
  30. const { data, head: { ret, msg } = { ret: 1, msg: '列表加载失败' } } = await apiQueryList(
  31. params
  32. )
  33. if (ret === consts.RET_CODE.SUCCESS) {
  34. this.CHANGE_STATE({ key: 'searchResult', value: data })
  35. }
  36. this.CHANGE_STATE({ key: 'loading', value: false })
  37. }
  38. }
  1. // @/views/banner/list.vue
  2. <script lang='ts'>
  3. import { Component, Vue } from 'vue-property-decorator'
  4. import { formatJson } from '@/utils'
  5. import { exportJson2Excel } from '@/utils/excel'
  6. import Pagination from '@/components/Pagination/index.vue' // secondary package based on el-pagination
  7. import { IBanner, SearchCondition } from '@/typings/modules/banner'
  8. import { BannerModule } from '@/store/modules/banner'
  9. @Component({
  10. name: 'BannerList',
  11. components: {
  12. Pagination
  13. }
  14. })
  15. export default class extends Vue {
  16. // ...
  17. // 当这个页面创建时
  18. created() {
  19. // 如果 BannerModule 对象缓存中有数据, 则跳过加载数据
  20. if (BannerModule.searchResult.result.length) return
  21. this.handleQuery()
  22. }
  23. private handleQuery(page?: { pageNo: number; pageSize: number }) {
  24. if (page) {
  25. // 重置页码
  26. page.pageNo && (this.form.pageNo = page.pageNo)
  27. page.pageSize && (this.form.pageSize = page.pageSize)
  28. }
  29. BannerModule.handleQuery(this.form)
  30. }
  31. }
  32. </script>

不需要缓存数据状态

  1. // @/views/banner/list.vue
  2. <script lang='ts'>
  3. import { Component, Vue } from 'vue-property-decorator'
  4. import { formatJson } from '@/utils'
  5. import { exportJson2Excel } from '@/utils/excel'
  6. import Pagination from '@/components/Pagination/index.vue' // secondary package based on el-pagination
  7. import { IBanner, SearchCondition } from '@/typings/modules/banner'
  8. import { apiDelete, apiQueryList } from '@/api/banner'
  9. @Component({
  10. name: 'BannerList',
  11. components: {
  12. Pagination
  13. }
  14. })
  15. export default class extends Vue {
  16. // ...
  17. // 当这个页面创建时
  18. created() {
  19. this.handleQuery()
  20. }
  21. private handleQuery(page?: { pageNo: number; pageSize: number }) {
  22. if (page) {
  23. // 重置页码
  24. page.pageNo && (this.form.pageNo = page.pageNo)
  25. page.pageSize && (this.form.pageSize = page.pageSize)
  26. }
  27. const { data, head: { ret, msg } = { ret: 1, msg: '列表加载失败' } } = await apiQueryList(
  28. params
  29. )
  30. // ...
  31. }
  32. }
  33. </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组件生成

  1. 将svg图片文件放到 @/assets/img/
  2. 控制台内项目根目录下运行命令 npm run svg
  3. svg组件将会生成到@/assets/components 目录下

    侧边栏配置

    image.png
    路由文件内容与页面信息关系
    当且仅当 只有一个可显示子菜单时, 直接使用子菜单的名字和目标路由 ```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’ // 设定路由的名字,一定要填写不然使用时会出现各种问题 meta: { roles: [‘admin’, ‘editor’] // 设置该路由进入的权限,支持多个权限叠加 title: ‘title’ // 设置该路由在侧边栏和面包屑中展示的名字 icon: ‘svg-name’ // 设置该路由的图标,支持 svg-class,也支持 el-icon-x element-ui 的 icon noCache: true // 如果设置为true,则不会被 缓存(默认 false) breadcrumb: false // 如果设置为false,则不会在breadcrumb面包屑中显示(默认 true) affix: true // 如果设置为true,它则会固定在tags-view中(默认 false)

// 当路由设置了该属性,则会高亮相对应的侧边栏。 // 这在某些场景非常有用,比如:一个文章的列表页路由为:/article/list // 点击文章进入文章详情页,这时候路由为/article/1,但你想在侧边栏高亮文章列表的路由,就可以进行如下设置 activeMenu: ‘/article/list’ }

  1. <a name="JWeaD"></a>
  2. ### 正式使用这个路由
  3. ![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` 与侧边栏循序有关.
  4. <a name="LFnSb"></a>
  5. ### 页面跳转与参数携带
  6. ```typescript
  7. private handleEdit(row?: IBanner) {
  8. // 使用组件方式编辑
  9. this.showEdit = true
  10. let query = {}
  11. if (row && row.id) {
  12. query = {
  13. id: row.id
  14. }
  15. }
  16. this.$router.push({ path: '/banner/edit', query })
  17. // this.$router.push({name:'BannerEdit',query})
  18. }