【项目-喵内0∞0记账-meowney-08】功能扩展包 DLC-页面会话保存sessionStorage + 路由守卫

大纲

[toc]


Vue文档中的路由守卫

路由守卫:主要用来通过 跳转取消 的方式,操控导航逻辑

  • 全局路由守卫
    • 全局 前置守卫 router.beforeEach(to, from, next) => {...})
    • 全局 解析守卫 router.beforeResolve(to, from, next) => {...})
    • 全局 后置钩子 router.afterEach(to, from,) => {...})
      • 后置钩子不接受 next 函数
  • 单个路由,路由独享守卫
    • 在路由配置./src/router/index.ts(js)上直接定义路由列表中每个路由对象中的属性beforeEnter: (to, from,) => {...},
    • 路由独享的守卫 beforeEnter
    • 与全局前置守卫的方法参数是一样的
  • 组件级
    • 在组件内直接定义
    • 组件内守卫 配置属性
      • beforeRouteEnter(to, from, next) {...}
      • beforeRouteUpdate(to, from,) {...}
      • beforeRouteLeave(to, from,) {...}
  • 完整的导航解析流程

导航解析流程

  • ① 触发导航 ->
  • beforeRouteLeave(失活的组件内调用)->
  • ③ 全局beforeEach ->
  • beforeRouteUpdate (复用的组件内调用)->
  • beforeEnter(路由配置里调用)->
  • ⑥ 异步路由组件 ->
  • beforeRouteEnter(被激活的组件里调用)->
  • ⑧ 全局beforeResolve -> 确认导航 ->
  • ⑨ 全局afterEach -> 触发 DOM 更新
  • ⑩ 调用回调函数,beforeRouteEnter守卫,传给 next({}) 中的回调函数
    • 创建好的组件实例vm作为回调函数的参数传入 next({vm=>(...)})

url查询参数的改变并 不会触发 进入/离开的 导航守卫

  • 使用watch: {...} 观察$route(to, from) {...} 对象来应对这些变化
  • 使用 beforeRouteUpdate 的组件内守卫

sessionStorage 页面会话保存

需要暂存页面数据的组件

  • Money.vue
    • ./store/modules/moneySessionStore.ts
  • Tags.vue
  • FormItem.vue
  • Tabs.vue
  • Numpad.vue
    • NumpadOutput.vue
    • NumpadButton.vue

修改Money.vue

  • 为了暂存用户输入的数据,当用户切换页面,触发导航守卫钩子,将数据存到sessionStorage
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
<script lang="ts">
// 路由 import {NavigationGuardNext, Route} from 'vue-router';
// 框架组件 import ...
// 页面模块组件 import ...
// 数据 import ...

@Component({
  components: {HeaderBar, Tabs, FormItem, Tags, Numpad, DateGetter},
  beforeRouteEnter(to: Route, from: Route, next: NavigationGuardNext): void {
    next(vm => {
      // 通过 `vm` 访问组件实例 代替this
      vm.$store.commit('loadMoneySessionStore'); // 读取 session 数据
    });
  },
  beforeRouteLeave(to: Route, from: Route, next: NavigationGuardNext): void {
    this.$store.commit('saveMoneySessionStore');
    next();
  }
})

export default class Money extends Vue {
  // data
  record: RecordItem = {
    tags: [],
    tips: '',
    type: '-',
    amount: 0,
    createdAt: dayjs((new Date()).toISOString()).format('YYYY-MM-DD'),
  };

  sessionSelectedTags ? = this.$store.state.moneySessionStore.tagsStore;
  recordTypeList = recordTypeList;
  checkoutResult = false;
  emptyTags = false;

  // computed
  get recordList() {
    return this.$store.state.recordStore.recordList as RecordItem[];
  }

  // methods
  updatePickedTags(selectedTags: Tag[]) {
    this.emptyTags = false;
    // 记录 选中的标签
    this.record.tags = selectedTags;
    // 页面暂存 session selectedTags
    this.$store.commit('updateTagsStore', selectedTags);
  }

  deselectTags(deselect: boolean) {/*...*/}
  checkoutRecord() {/*...*/}
  alertInform(caseName: 'case1' | 'case2' | 'case3') {/*...*/}
  saveRecord() {/*...*/}
  reset() {/*...*/}
  submit() {/*...*/}

}
</script>

<template>
  <Layout class-prefix="layout" class="layout-content">
    <HeaderBar :header-title="'记账'"
               :hasIcon="false">
    </HeaderBar>
    <Tags @update:selectedTags="updatePickedTags"
          :sessionSelectedTags="sessionSelectedTags"
          :is-deselect-tags="emptyTags"
          class="tags"/>
    <FormItem class="form-item"
              field-name="备注"
              placeholder="在这里输入备注"
              type="text"
              :inputValue.sync="record.tips"/>
    <FormItem class="form-item"
              field-name="日期"
              placeholder="在这里选择日期"
              type="date"
              :inputValue.sync="record.createdAt"/>
    <div class="datePicker">
      <date-getter></date-getter>
    </div>
    <Tabs :data-source="recordTypeList"
          :type.sync="record.type"
          class="fuckAnt-tabs"/>
    <Numpad :amount.sync="record.amount"
            @submit="submit"
            @update:deselectTags="deselectTags"
            @checkZero="alertInform('case3')"
            :is-reset="checkoutResult"/>
  </Layout>
</template>
  • 分别需要保存数据: 标签tagsStore、备注tipsStore、日期dateStore、收支类型typeStore、金额amountStore
  • 关闭页面,自动清除sessionStorage数据

./store/modules/moneySessionStore.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
const moneySessionStore = {
  namespace: true,
  state() {
    return {
      tagsStore: [],
      tipsStore: '',
      dateStore: '',
      typeStore: '-',
      amountStore: '',
    };
  },
  mutations: {
    loadMoneySessionStore(state: MoneySessionStore) {
      state.tagsStore = JSON.parse(window.sessionStorage.getItem('tagsStore') ?? '[]');
      state.tipsStore = JSON.parse(window.sessionStorage.getItem('tipsStore') ?? '[]');
      state.dateStore = JSON.parse(window.sessionStorage.getItem('dateStore') ?? '[]');
      state.typeStore = JSON.parse(window.sessionStorage.getItem('typeStore') ?? '[]');
      state.amountStore = JSON.parse(window.sessionStorage.getItem('amountStore') ?? '[]');
    },
    saveMoneySessionStore(state: MoneySessionStore) {
      window.sessionStorage.setItem('tagsStore', JSON.stringify(state.tagsStore));
      window.sessionStorage.setItem('tipsStore', JSON.stringify(state.tipsStore));
      window.sessionStorage.setItem('dateStore', JSON.stringify(state.dateStore));
      window.sessionStorage.setItem('typeStore', JSON.stringify(state.typeStore));
      window.sessionStorage.setItem('amountStore', JSON.stringify(state.amountStore));
    },
    updateTagsStore(state: MoneySessionStore, tagsList: Tag[]) {
      state.tagsStore = tagsList;
    },
    updateTipsStore(state: MoneySessionStore, newValue: string) {
      state.tipsStore = newValue;
    },
    updateDateStore(state: MoneySessionStore, newValue: string) {
      state.dateStore = newValue;
    },
    updateTypeStore(state: MoneySessionStore, type: string) {
      state.typeStore = type;
    },
    updateMoneyStore(state: MoneySessionStore, output: string) {
      state.amountStore = output;
    },
    resetMoneySessionStore(state: MoneySessionStore) {
      state.tagsStore = [];
      state.tipsStore = {};
      state.dateStore = {};
      state.typeStore = {};
      state.amountStore = {};
    }
  }
};

export default moneySessionStore;

类型声明custom.d.ts

1
2
3
4
5
6
7
8
9
//...
type MoneySessionStore = {
  tagsStore: Tag[];
  tipsStore: {};
  dateStore: {};
  typeStore: {};
  amountStore: {};
}
//...

修改Tags.vue

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<script lang="ts">
import {Component, Prop, Watch,} from 'vue-property-decorator';
import {mixins} from 'vue-class-component';
import tagHelper from '@/mixins/tagHelper.ts';

@Component
export default class Tags extends mixins(tagHelper) {
  @Prop(Boolean) isDeselectTags!: boolean;
  @Prop() sessionSelectedTags?: Tag[];

  selectedTags: Tag[] = [];

  get tagsList() {
    return this.$store.state.tagStore.tagsList;
  }

  renderSessionTags() {
    this.selectedTags = this.sessionSelectedTags || [];
    // session 数据 循环推入 selectedTags 将样式染到页面
    this.sessionSelectedTags?.forEach((tag) => {
      // 将 selected 样式 加到 selectedTags 中的 tag 上
      (this.$refs[tag.name] as Array<HTMLLIElement>)[0].className = 'selected';
    });
  }

  // 将点击的标签 推入数组/从数组中删除 并将 标签列表 发布给父组件
  toggle(tag: Tag) {
    // 得到 本次 点击标签下标 在 被选中标签列表下标的位置
    const index = this.selectedTags.map(itemTag => itemTag.id) // 得到被选中标签下标数组 []
      .indexOf(tag.id);

    // 下标是否存在于 被选中标签列表中
    if (index >= 0) {
      // 存在就 去除 标签列表中 下标对应的那项
      this.selectedTags.splice(index, 1);
      // 并去除 对应样式
      (this.$refs[tag.name] as Array<HTMLLIElement>)[0].className = '';
    } else {
      // 不存在就将标签推入列表
      this.selectedTags.push(tag);
    }
    // 将选中的标签 列表 传给父组件
    this.$emit('update:selectedTags', this.selectedTags);
  }

  // 是否取消选取标签
  @Watch('isDeselectTags')
  deselectTag() {/*...*/}

  // hooks
  created() {
    // 读取所有标签
    this.$store.commit('fetchTags');
  }

  mounted() {
    // 读取session信息 渲染页面
    this.renderSessionTags();
  }

}
</script>

<template>
  <section class="tags">
    <ul class="current">
      <li v-for="tag in tagsList"
          :ref="tag.name"
          :key="tag.id"
          @click="toggle(tag)"
          :class="{selected: (selectedTags.indexOf(tag) >= 0)}">
        {{ tag.name }}
      </li>
    </ul>
    <div class="new">
      <button @click="createTag">添加新标签</button>
    </div>
  </section>
</template>
  • mounted阶段读取 session 信息,之后渲染页面
  • 页面数据改变,写入相应的session 数据

修改FormItem.vue

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<script lang="ts">
import Vue from 'vue';
import {Component, Prop} from 'vue-property-decorator';
import dayjs from 'dayjs';

@Component
export default class FormItem extends Vue {
  @Prop({default: ''}) inputValue!: string;
  @Prop({required: true}) fieldName!: string;
  @Prop({default: ''}) placeholder?: string;
  @Prop() type?: string;

  oninputValueChanged(newValue: string) {
    this.$emit('update:inputValue', newValue);
    if (this.type === 'date') {
      this.$store.commit('updateDateStore', newValue);
    } else if (this.type === 'text') {
      this.$store.commit('updateTipsStore', newValue);
    }
  }

  dateFormat(isoString: string) {/*...*/}

  renderSessionInfo() {
    if (this.type === 'text') {
      this.oninputValueChanged(this.$store.state.moneySessionStore.tipsStore);
    } else if (this.type === 'date') {
      if (this.$store.state.moneySessionStore.dateStore?.length === 0) {
        this.$store.commit('updateDateStore', this.dateFormat((new Date()).toISOString()));
      }
      this.oninputValueChanged(this.$store.state.moneySessionStore.dateStore);
    }
  }

  // hooks
  mounted() {
    // 读取session信息 渲染页面
    this.renderSessionInfo();
  }

}
</script>

<template>
  <section class="form-wrapper">
    <label class="form-item">
      <span class="name">{{ fieldName }}</span>
      <template v-if="type === 'date'">
        <input :type="type"
               :placeholder="placeholder"
               :value="dateFormat(inputValue)"
               @change="oninputValueChanged($event.target.value)"/>
      </template>
      <template v-else>
        <input :type="type || 'text'"
               :placeholder="placeholder"
               :value="inputValue"
               @input="oninputValueChanged($event.target.value)"/>
      </template>
    </label>
  </section>
</template>
  • mounted阶段读取 session 信息,之后渲染页面
  • 页面数据改变,写入相应的session 数据

修改Tabs.vue

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<script lang="ts">
import Vue from 'vue';
import {Component, Prop} from 'vue-property-decorator';

@Component
export default class Tabs extends Vue {
  @Prop({required: true, type: Array}) dataSource!: DataSource[];
  @Prop(String) type!: string;
  @Prop(String) classPrefix?: string;
  @Prop({type: String, default: '64px'}) tabsHeight!: string;

  liClass(item: DataSource) {
    return {...};
  }

  select(item: DataSource) {
    this.$emit('update:type', item.type);
    this.$store.commit('updateTypeStore', item.type);
  }

  mounted() {
    // 读取session信息 渲染页面
    if (this.$store.state.moneySessionStore.typeStore.length === 0) {
      this.$store.commit('updateTypeStore', '-');
    }
    this.$emit('update:type', this.$store.state.moneySessionStore.typeStore);
  }
}
</script>

<template>
  <ul class="tabs" :class="{[classPrefix + '-tabs']: classPrefix}">
    <li v-for="item in dataSource"
        :key="item.type"
        :class="liClass(item)"
        @click="select(item)"
        class="tabs-item"
        :style="{height: tabsHeight}">
      {{ item.text }}
    </li>
  </ul>
</template>
  • mounted阶段读取 session 信息,之后渲染页面
  • 页面数据改变,写入相应的session 数据

Numpad.vue

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<script lang="ts">
// import ...

@Component({
  components: {NumpadOutput, NumpadButton}
})
export default class Numpad extends mixins(SearchLight, OperateNumpad) {
  // 默认当前初始下标
  currentIndex = -1;
  // 默认绑定事件
  eventName = 'click';
  // 客户端设备信息
  clientType = getClientType();

  // 数字键盘文字图标数据
  numPadText = [/*...*/];

  // 返回当前选中按钮的下标
  markButton(e: UIEvent) {/*...*/}

  // 判断客户端尺寸 返回对应的事件类型
  get clientEvent(): string {/*...*/}

  // 给不同的按钮绑定对应事件的处理函数
  handleButtonFn(e: TapEvent) {/*...*/}

  // 计算属性 传给子组件 NumpadOutput.vue
  getSessionOutput(output: string) {
    this.output = output.toString();
  }

}
</script>

<template>
  <section class="numpad">
    <numpad-output :output="output"
                   @update:output="getSessionOutput"/>
    <div class="buttons"
         @[clientEvent]="markButton($event); handleButtonFn($event)"
         @mousemove="showSearchlight"
         :style="searchlightStyle">
      <numpad-button
        v-for="(item, index) in numPadText"
        :data-bundle-event="item.bundleEvent"
        :button-index="index"
        :currentIndex="currentIndex"
        :key="item.id"
        :class="{ok: item.id === 'ok', zero: item.id === 'zero'}"
        :button-text="item.text">
        <Icon v-if="['num', 'dot'].indexOf(item.name) === -1"
              :name="item.id"/>
      </numpad-button>
    </div>
  </section>
</template>
  • NumpadOutput.vue
  • NumpadButton.vue

NumpadOutput.vue

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<script lang="ts">
import Vue from 'vue';
import {Component, Prop} from 'vue-property-decorator';

@Component
export default class NumpadOutput extends Vue {
  @Prop() output!: string;

  // computed // 格式化显示金额逻辑
  get localOutput() {
    // 分别 存 整数部分(integer part) 和小数部分(decimal part)
    const outPutInteger = Math.trunc(Number(this.output)).toString();
    const [outPutDecimal = '.00'] = this.output.match(/\.\d{1,2}/g) || '';
    return ${outPutInteger.replace(/(\d)(?=(?:\d{4})+$)/g, '$1,')}${outPutDecimal}`;
  }

  renderSessionMoneyOutput() {
    this.$emit('update:output', this.$store.state.moneySessionStore.amountStore);
  }

  mounted() {
    // 读取session信息 渲染页面
    this.renderSessionMoneyOutput();
  }

}
</script>

<template>
  <div class="output">
    {{ localOutput }}
  </div>
</template>
  • mounted阶段读取 session 信息,之后渲染页面
  • 页面数据改变,写入相应的session 数据

参考


跳转页面 路由守卫钩子 暂存页面数据

组件内路由守卫 使用vue-property-decorator 类组件 TS装饰器

Money.vue添加组件内的路由守卫

Money.vue

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
<script lang="ts">
// 框架组件
import ...
import {NavigationGuardNext, Route} from 'vue-router';
// 页面模块组件
import ...
// 数据
import ...

@Component({
  components: {HeaderBar, Tabs, FormItem, Tags, Numpad, DateGetter},
  beforeRouteEnter(to: Route, from: Route, next: NavigationGuardNext): void {
    console.log('beforeRouteEnter');
    next(vm => {
      // 通过 `vm` 访问组件实例 代替this
      vm.$store.commit('loadMoneySessionStore');
    });
    next()
  },
  beforeRouteLeave(to: Route, from: Route, next: NavigationGuardNext): void {
    console.log('beforeRouteLeave');
    this.$store.commit('saveMoneySessionStore');
    next();
  }
})

export default class Money extends Vue {
  // data
  record: RecordItem = {
    tags: [],
    tips: '',
    type: '-',
    amount: 0,
    createdAt: new Date().toISOString(),
  };
  recordTypeList = recordTypeList;
  checkoutResult = false;
  emptyTags = false;

  // computed
  get recordList() {/*...*/}

  // methods...
  deselectTags(deselect: boolean) {/*...*/}
  checkoutRecord() {/*...*/}
  alertInform(caseName: 'case1' | 'case2' | 'case3') {/*...*/}
  saveRecord() {/*...*/}
  reset() {/*...*/}
  submit() {/*...*/}
}
</script>

<template>
  <Layout class-prefix="layout" class="layout-content">
    <HeaderBar :header-title="'记账'"
               :hasIcon="false">
    </HeaderBar>
    <Tags @update:selectedTags="pickTags"
          :is-deselect-tags="emptyTags"
          class="tags"/>
    <FormItem class="form-item"
              field-name="备注"
              placeholder="在这里输入备注"
              type="text"
              :inputValue.sync="record.tips"/>
    <div class="creatAt">
      <FormItem class="form-item"
                field-name="日期"
                placeholder="在这里选择日期"
                type="date"
                :inputValue.sync="record.createdAt"/>
    </div>
    <div class="datePicker">
      <date-getter></date-getter>
    </div>
    <Tabs :data-source="recordTypeList"
          :type.sync="record.type"
          class="fuckAnt-tabs"/>
    <Numpad :amount.sync="record.amount"
            @submit="submit"
            @update:deselectTags="deselectTags"
            @checkZero="alertInform('case3')"
            :is-reset="checkoutResult"/>
  </Layout>
</template>
  • 组件内的路由守卫放在@Component选项中,分别有
    • beforeRouteEnter | beforeRouteLeave | beforeRouteUpate
    • TS所需参数和类型为beforeRouteEnter(to: Route, from: Route, next: NavigationGuardNext): void
    • 类型由import {NavigationGuardNext, Route} from 'vue-router';得到
  • beforeRouteEnter
    • 在渲染该组件的对应路由被 confirm 前调用
    • 不!能!获取组件实例 this
      • beforeRouteEnter 守卫 不能 访问 this
      • 可由next(vm=>{})vm的代替
      • 即通过传一个回调vm=>{}next来访问组件实例
      • 在导航被确认的时候执行回调,并且把组件实例vm作为回调方法的参数代替this
    • 因为当守卫执行前,组件实例还没被创建
      • 守卫在导航确认前被调用,因此即将登场的新组件还没被创建
  • beforeRouteLeave
    • 在当前路由改变,但是该组件被复用时调用
    • 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候
    • 由于会渲染同样的 Foo 组件,因此组件实例会被复用
    • 而这个钩子就会在这个情况下被调用
    • 可以访问组件实例 this
  • beforeRouteUpate
    • 导航离开该组件的对应路由时调用
    • 可以访问组件实例 this

参考


使用vuex-ts-decorators TS Class 组件 状态管理

Money.vue

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
<script lang="ts">
// 框架组件
import {Component, Vue} from 'vue-property-decorator';
import {NavigationGuardNext, Route} from 'vue-router';
// 页面模块组件
import HeaderBar from '@/components/HeaderBar.vue';
import Tags from '@/components/Money/Tags.vue';
import FormItem from '@/components/Money/FormItem.vue';
import Tabs from '@/components/Tabs.vue';
import Numpad from '@/components/Money/Numpad.vue';
// 工具函数
import dateFormat from '@/lib/dateFormat.ts';
// 数据
import recordTypeList from '@/constants/recordTypeList.ts';

@Component({
  components: {HeaderBar, Tabs, FormItem, Tags, Numpad},
  beforeRouteEnter(to: Route, from: Route, next: NavigationGuardNext): void {
    next(vm => {
      // 通过 `vm` 访问组件实例 代替this
      vm.$store.commit('loadMoneySessionStore'); // 读取 session 数据
    });
  },
  beforeRouteLeave(to: Route, from: Route, next: NavigationGuardNext): void {
    this.$store.commit('saveMoneySessionStore');
    next();
  }
})

export default class Money extends Vue {
  // data
  record: RecordItem = {
    tags: [],
    tips: '',
    type: '-',
    amount: 0,
    createdAt: dateFormat(new Date().toISOString()),
  };

  sessionSelectedTags ? = this.$store.state.moneySessionStore.tagsStore;
  recordTypeList = recordTypeList;
  checkoutResult = false;
  emptyTags = false;

  // methods
  updatePickedTags(selectedTags: Tag[]) {
    this.emptyTags = false;
    // 记录 选中的标签
    this.record.tags = selectedTags;
    // 页面暂存 session selectedTags // 作为子组件外部数据传给子组件
    this.$store.commit('updateTagsStore', selectedTags);
  }

  deselectTags(deselect: boolean) {
    if (deselect) {
      this.record.tags = [];
      this.emptyTags = true;
    }
  }

  checkoutRecord() {
    // 检查记录 是否存在
    this.checkoutResult = true;
    if (!this.record.tags || this.record.tags.length === 0) {
      this.alertInform('case2');
      this.checkoutResult = false;
      this.deselectTags(false);
    }
    return this.checkoutResult;
  }

  alertInform(caseName: 'case1' | 'case2' | 'case3') {
    const maps = {
      case1: '已保存',
      case2: '请至少选择一个标签',
      case3: '金额为零,不计入'
    };
    window.alert(maps[caseName]);
    this.reset();
  }

  saveRecord() {
    this.$store.commit('createRecord', this.record);
  }

  reset() {
    this.record.tips = '';
    this.record.tags = [];
    this.record.type = '-';
    this.record.amount = 0;
    this.record.createdAt = dateFormat(new Date().toISOString());
    this.$store.commit('resetMoneySessionStore');
  }

  rerender() {
    this.deselectTags(true);
    this.reset();
  }

  submit() {
    // 检查记录是否存在
    if (!this.checkoutRecord()) {
      return;
    }
    this.saveRecord();
    if (this.$store.state.recordStore.createRecordError === null) {
      this.alertInform('case1');
    }
    // 提交后 重置页面
    this.rerender();
  }

}
</script>

<template>
  <Layout class-prefix="layout" class="layout-content">
    <HeaderBar :header-title="'记账'"
               :hasIcon="false">
    </HeaderBar>
    <Tags @update:selectedTags="updatePickedTags"
          :sessionSelectedTags="sessionSelectedTags"
          :is-deselect-tags="emptyTags"
          class="tags"/>
    <FormItem class="form-item"
              field-name="备注"
              placeholder="在这里输入备注"
              type="text"
              :inputValue.sync="record.tips"/>
    <FormItem class="form-item"
              field-name="日期"
              placeholder="在这里选择日期"
              type="date"
              :inputValue.sync="record.createdAt"/>
    <Tabs :data-source="recordTypeList"
          :type.sync="record.type"
          class="fuckAnt-tabs"/>
    <Numpad :amount.sync="record.amount"
            @submit="submit"
            @update:deselectTags="deselectTags"
            @checkZero="alertInform('case3')"
            @update:rerender="rerender"
            :is-reset="checkoutResult"/>
  </Layout>
</template>

<style lang="scss" scoped>
::v-deep {
  .layout-content {
    display: flex;
    flex-direction: column;
  }

  .headerBar {
    &::before {
      content: '';
      display: inline;
      width: 24px;
      height: 24px;
    }
  }

  .fuckAnt-tabs {
    margin-bottom: 0;
  }
}

.form-item {
  padding: 2px 0;
}

</style>




参考文章

相关文章


  • 作者: Joel
  • 文章链接:
  • 版权声明
  • 非自由转载-非商用-非衍生-保持署名