Pārlūkot izejas kodu

feat: 添加form支持. formId, formName, formAction

LosMessi 2 gadi atpakaļ
vecāks
revīzija
1eb3bf2db6

+ 1 - 0
README.md

@@ -8,6 +8,7 @@ yarn install
 ### Compiles and hot-reloads for development
 ```
 yarn serve
+yarn wind
 ```
 
 ### Compiles and minifies for production

+ 2 - 0
package.json

@@ -11,10 +11,12 @@
   "dependencies": {
     "axios": "^0.26.1",
     "core-js": "^3.6.5",
+    "dayjs": "^1.11.2",
     "js-md5": "^0.7.3",
     "less": "^4.1.2",
     "less-loader": "7.3.0",
     "tailwindcss": "^3.0.23",
+    "vant": "^2.12.47",
     "vue": "^2.6.11",
     "vue-preview-image-mobile": "^0.1.1",
     "vue-router": "3.5.3",

+ 25 - 10
src/components/base.js

@@ -2,27 +2,42 @@
 export default {
   name: 'v-base',
   props: {
-    options: Object,
+    options: {
+      type: Object,
+      default: () => {
+        return {};
+      },
+    },
   },
   methods: {
     click(link) {
-      let isLink = ['string', 'number'].indexOf(typeof link) == -1;
+      /* let isLink = ['string', 'number'].indexOf(typeof link) == -1;
       if(!isLink || !link || link=="0" ) return;
       const event = link;
       link = this.options.link;
       if(link) {
         event.stopPropagation();
       }
-      if(!link || link =="0") return;
+      if(!link || link =="0") return; */
+
+      let event;
+      if (!['string', 'number'].includes(typeof link)) {
+        event = link;
+        link = this.options.link;
+      }
+
+      if (!link || link === '0') return;
+      event?.stopPropagation();
+
       this.$router.push({
         name: 'page',
         params: {
-          id: link
-        }
-      })
+          id: link,
+        },
+      });
     },
-    getComponent(node, def){
+    getComponent(node, def) {
       return node.type ? `v-${node.type}` : def ? `v-${def}` : 'div';
-    }
-  }
-}
+    },
+  },
+};

+ 38 - 0
src/components/button.vue

@@ -0,0 +1,38 @@
+<template>
+  <div @click="handleClick" class="button text-center mb-4 mx-auto last:mb-0" :class="{ disable: disable }" :style="options.style">
+    {{ options.text }}
+  </div>
+</template>
+<script>
+import Base from './base';
+
+export default {
+  name: 'v-button',
+  extends: Base,
+  props: {
+    disable: Boolean,
+  },
+  methods: {
+    handleClick($event){
+      if(this.disable) return;
+      this.$emit('click', $event)
+    }
+  }
+}
+</script>
+<style lang="less" scoped>
+.button {
+  display: inline-block;
+  padding: 0.5rem 1rem;
+  font-size: 1rem;
+  line-height: 1;
+  border-radius: 1rem;
+  background: #fe441f;
+  border: 1px solid rgba(255, 255, 255, 0.75);
+  color: #fff;
+
+  &.disable {
+    background: rgb(190, 190, 190);
+  }
+}
+</style>

+ 69 - 0
src/components/form/datetime.vue

@@ -0,0 +1,69 @@
+
+<template>
+  <div class="form-datetime mb-4 last:mb-0 flex" :style="options.style">
+    <v-label :options="options"></v-label>
+    <div class="form-value flex-1">
+      <div class="datetime" @click="show = true">{{ value | format }}</div>
+    </div>
+    <van-popup v-model="show" position="bottom">
+      <van-datetime-picker v-model="currentDate" :type="options.mode || 'datetime'" @cancel="show = false" @confirm="confirmDate" title="选择完整时间" />
+    </van-popup>
+  </div>
+</template>
+<script>
+import Base from '../base';
+import dayjs from 'dayjs';
+import DateTimePicker from 'vant/lib/datetime-picker';
+import 'vant/lib/datetime-picker/style';
+import Popup from 'vant/lib/popup';
+import 'vant/lib/popup/style';
+import vLabel from './label';
+
+export default {
+  name: 'v-form-datetime',
+  extends: Base,
+  components: {
+    'van-datetime-picker': DateTimePicker,
+    'van-popup': Popup,
+    vLabel,
+  },
+  data(){
+    return {
+      show: false,
+      currentDate: new Date(),
+    }
+  },
+  props: {
+    value: Date,
+  },
+  filters: {
+    format(dataTime){
+      if(!dataTime) return '';
+      return dayjs(dataTime).format('YYYY-MM-DD HH:mm');
+    }
+  },
+  methods: {
+    handleChange(value){
+      this.$emit('update:value', value);
+    },
+    confirmDate(value){
+      this.handleChange(value);
+      this.show = false;
+    }
+  }
+}
+</script>
+<style lang="less" scoped>
+.form-datetime {
+  .datetime {
+    padding: 0.5rem;
+    font-size: 1rem;
+    line-height: 1;
+    background: rgba(255, 255, 255, 0.5);
+    border-radius: 0.4rem;
+    min-height: 2rem;
+    box-sizing: border-box;
+    text-align: left;
+  }
+}
+</style>

+ 20 - 0
src/components/form/index.js

@@ -0,0 +1,20 @@
+import Form from './index.vue';
+import FormInput from './input.vue';
+import FormRadio from './radio.vue';
+import FormDatetime from './datetime.vue';
+
+const componets = {
+  Form,
+  FormInput,
+  FormRadio,
+  FormDatetime,
+};
+
+export default {
+  install(Vue) {
+    Object.keys(componets).forEach(key => {
+      const component = componets[key];
+      Vue.component(component.name, component);
+    });
+  },
+};

+ 96 - 0
src/components/form/index.vue

@@ -0,0 +1,96 @@
+
+<template>
+  <div class="form mb-4 last:mb-0" :style="options.style">
+    <div class="mb-4">
+      <component :is="getComponent(item)" :key="index" :options="item" :value.sync="formData[item.field]" v-for="(item, index) in options.children"></component>
+    </div>
+    <v-button :disable="success" :options="buttonOptions" @click="submit"></v-button>
+  </div>
+</template>
+<script>
+import Base from '../base';
+import Dialog from 'vant/lib/dialog';
+import 'vant/lib/dialog/style';
+import { fetch } from '@/api/page.js'
+
+export default {
+  name: 'v-form',
+  extends: Base,
+  data(){
+    return {
+      formData: {},
+      loading: false,
+      success: false,
+    }
+  },
+  computed: {
+    buttonOptions() {
+      const { submit = {} } = this.options || {};
+      return {
+        text: '提交',
+        ...submit,
+      }
+    }
+  },
+  created(){
+    const data = {};
+    this.options.children.forEach(item => {
+      data[item.field] = undefined;
+    });
+    this.formData = data;
+  },
+  methods: {
+    submit(){
+      let errmsg;
+      this.options.children.find(item => {
+        if(!errmsg && item.required && !this.formData[item.field]) {
+          errmsg = item.label;
+        }
+      })
+      if(errmsg) {
+        return Dialog.alert({
+          message: `请填写${errmsg}`,
+        })
+      }
+
+      if(this.loading) return;
+      this.loading = true;
+      const { formId, formName, formAction } = this.options;
+
+      fetch(formAction || '', { formId, formName, formAction, data: this.formData }).then(() => {
+        this.loading = false;
+        this.success = true;
+        Dialog.alert({
+          message: '提交成功',
+        })
+      }).catch((err) => {
+        console.log("err", err)
+        this.loading = false;
+        Dialog.alert({
+          message: err.message,
+        })
+      })
+    },
+    getComponent(node){
+      return node.type ? `v-${node.type}` : 'div';
+    },
+    handleValue(key, value){
+      this.formData[key] = value;
+    }
+  }
+}
+</script>
+<style lang="less" scoped>
+.form {
+  background: rgba(255, 255, 255, 0.25);
+  padding: 0.75rem 1rem;
+  border: 1px solid rgba(255, 255, 255, 0.55);
+  border-radius: 0.5rem;
+  /deep/ .form-label {
+    width: 5rem;
+    padding: 0.5rem 0;
+    font-size: 1rem;
+    line-height: 1;
+  }
+}
+</style>

+ 41 - 0
src/components/form/input.vue

@@ -0,0 +1,41 @@
+
+<template>
+  <div class="form-input mb-4 last:mb-0 flex" :style="options.style">
+    <v-label :options="options"></v-label>
+    <div class="form-value flex-1">
+      <input class="input w-full" :value="options.value" @input="handleChange" />
+    </div>
+  </div>
+</template>
+<script>
+import Base from '../base';
+import vLabel from './label';
+
+export default {
+  name: 'v-form-input',
+  extends: Base,
+  components: {
+    vLabel
+  },
+  props: {
+    value: String,
+  },
+  methods: {
+    handleChange(event){
+      console.log('update:value', event.target.value);
+      this.$emit('update:value', event.target.value);
+    }
+  }
+}
+</script>
+<style lang="less" scoped>
+.form-input {
+  .input {
+    padding: 0.5rem;
+    font-size: 1rem;
+    line-height: 1;
+    background: rgba(255, 255, 255, 0.5);
+    border-radius: 0.4rem;
+  }
+}
+</style>

+ 28 - 0
src/components/form/label.vue

@@ -0,0 +1,28 @@
+<template>
+  <div class="form-label flex-0 text-right mr-4">
+    <label :class="{ required: options.required }">{{ options.label }}</label>
+  </div>
+</template>
+<script>
+import Base from '../base';
+
+export default {
+  extends: Base,
+}
+</script>
+<style lang="less" scoped>
+.form-label {
+  position: relative;
+  .required {
+    &::after {
+      content: '*';
+      position: absolute;
+      color: #fe441f;
+      right: -0.5rem;
+      top: 0.5rem;
+      font-size: 1rem;
+      font-weight: bold;
+    }
+  }
+}
+</style>

+ 68 - 0
src/components/form/radio.vue

@@ -0,0 +1,68 @@
+
+<template>
+  <div class="form-radio mb-4 last:mb-0 flex" :style="options.style">
+    <v-label :options="options"></v-label>
+    <div class="form-value flex-1">
+      <div
+        class="form-radio-item mb-2 last:mb-0 flex"
+        :class="{ selected: isSelected(item) }"
+        @click="handleChange(item)"
+        :key="index"
+        v-for="(item, index) in options.options"
+      >
+        <div class="dot"></div>
+        <div class="text">{{ item }}</div>
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+import Base from '../base';
+import vLabel from './label';
+
+export default {
+  name: 'v-form-radio',
+  extends: Base,
+  components: {
+    vLabel,
+  },
+  props: {
+    value: String,
+  },
+  methods: {
+    handleChange(value){
+      console.log('xxx', value);
+      this.$emit('update:value', value);
+    },
+    isSelected(item){
+      return item === this.value;
+    }
+  }
+}
+</script>
+<style lang="less" scoped>
+.form-radio {
+  .form-radio-item {
+    padding: 0.5rem;
+    font-size: 1rem;
+    line-height: 1;
+    border-radius: 0.4rem;
+    .dot {
+      width: 1.2rem;
+      height: 1.2rem;
+      margin-right: 0.25rem;
+      margin-top: -0.1rem;
+      box-sizing: border-box;
+      border: 2px solid #fff;
+      background: transparent;
+      border-radius: 1.2rem;
+    }
+    &.selected {
+      .dot {
+        border: 2px solid #fe441f;
+        background: #fe441f;
+      }
+    }
+  }
+}
+</style>

+ 1 - 1
src/components/image.vue

@@ -5,7 +5,6 @@
       <span :style="options.marksStyle" v-html="options.marks"></span>
     </div>
   </div>
-  
   <div v-else class="image mb-6 last:mb-0 mx-auto">
     <img class="mb-1" :style="options.style" :src="options.image" @click="handleClick" />
     <v-text :options="descOptions"></v-text>
@@ -32,6 +31,7 @@ export default {
   methods: {
     handleClick(){
       const { image, preview } = this.options;
+      console.log('1');
       this.click();
       if(!preview) return;
       EventBus.$emit('preview', image);

+ 9 - 5
src/components/index.js

@@ -9,6 +9,8 @@ import Video from './video.vue';
 import Tabs from './tabs.vue';
 import TableTexts from './tableTexts.vue';
 import Wrapper from './wrapper.vue';
+import Button from './button.vue';
+import Form from './form';
 
 const componets = {
   View,
@@ -22,13 +24,15 @@ const componets = {
   Tabs,
   TableTexts,
   Wrapper,
-}
+  Button,
+};
 
 export default {
-  install(Vue){
+  install(Vue) {
     Object.keys(componets).forEach(key => {
       const component = componets[key];
       Vue.component(component.name, component);
-    })
-  }
-}
+      Vue.use(Form);
+    });
+  },
+};

+ 5 - 3
src/components/tableTexts.vue

@@ -21,9 +21,11 @@ export default {
     },
   },
   methods: {
-    getDesc( desc="" ){
-      let table =  desc.split(";").map( (item)=> {return item.split("|")} );
-      console.log("table", table);
+    getDesc( desc = '' ){
+      if(Array.isArray(desc)) return desc;
+      let table =  desc.split(';').map((item) => {
+        return item.split('|')
+      });
       return table;
     },
     getCellStyle(index){

+ 144 - 65
src/data/pages.json

@@ -7,45 +7,57 @@
       "background": "https://ndzx.hqedust.com/file/image/backgroud.png"
     },
     "main": {
-      "nodes": [
-        {
-          "type": "slider",
-          "children": [
-            {
-              "image": "https://ndzx.hqedust.com/file/image/slider1.jpg"
-            },
-            {
-              "image": "https://ndzx.hqedust.com/file/image/slider2.jpg"
-            },
-            {
-              "image": "https://ndzx.hqedust.com/file/image/slider3.jpg"
-            }
-          ]
-        },
-        {
-          "type": "matrix",
-          "children": [
-            {
-              "image": "https://img2.wmnetwork.cc/w/202110/03/0_20211003160107_IdLRhgwt.jpg",
-              "link": "1"
-            },
-            {
-              "image": "https://img2.wmnetwork.cc/w/202110/03/0_20211003160107_PKQUMRWy.jpg",
-              "link": "2"
-            },
-            {
-              "image": "http://img2.wmnetwork.cc/w/202110/20/0_20211020131911_71IyBZga.jpg",
-              "link": "3"
-            },
-            {
-              "image": "http://img2.wmnetwork.cc/w/202110/26/0_20211026155958_0t2qHQCk.png",
-              "link": "4"
-            }
-          ]
-        }
-      ]
+      "background": "https://ndzx.hqedust.com/file/image/bg.jpg",
+      "logo": "https://ndzx.hqedust.com/file/image/logo.png",
+      "paperId": 2,
+      "style": " font-family:STKaiti;",
+      "title": "全国乡村振兴理论研讨会暨《摆脱贫困》学习座谈会"
     },
     "pages": [
+      {
+        "id": "main",
+        "nodes": [
+          {
+            "type": "slider",
+            "children": [
+              {
+                "image": "https://ndzx.hqedust.com/file/image/slider1.jpg"
+              },
+              {
+                "image": "https://ndzx.hqedust.com/file/image/slider2.jpg"
+              },
+              {
+                "image": "https://ndzx.hqedust.com/file/image/slider3.jpg"
+              }
+            ]
+          },
+          {
+            "type": "matrix",
+            "children": [
+              {
+                "image": "https://img2.wmnetwork.cc/w/202110/03/0_20211003160107_IdLRhgwt.jpg",
+                "link": "1"
+              },
+              {
+                "image": "https://img2.wmnetwork.cc/w/202110/03/0_20211003160107_PKQUMRWy.jpg",
+                "link": "2"
+              },
+              {
+                "image": "http://img2.wmnetwork.cc/w/202110/20/0_20211020131911_71IyBZga.jpg",
+                "link": "3"
+              },
+              {
+                "image": "http://img2.wmnetwork.cc/w/202110/26/0_20211026155958_0t2qHQCk.png",
+                "link": "4"
+              },
+              {
+                "image": "http://img2.wmnetwork.cc/w/202110/26/0_20211026155958_0t2qHQCk.png",
+                "link": "11"
+              }
+            ]
+          }
+        ]
+      },
       {
         "id": "1",
         "nodes": [
@@ -79,25 +91,25 @@
           {
             "type": "image",
             "image": "https://img2.wmnetwork.cc/w/202104/08/0_20210408233155_4X1vGlar.jpg",
-            "desc": "良渚古城遗址公园",
+            "text": "良渚古城遗址公园",
             "preview": true
           },
           {
             "type": "image",
             "image": "https://img2.wmnetwork.cc/w/202104/08/0_20210408233155_4X1vGlar.jpg",
-            "desc": "良渚古城遗址公园",
+            "text": "良渚古城遗址公园",
             "preview": true
           },
           {
             "type": "image",
             "image": "https://img2.wmnetwork.cc/w/202104/08/0_20210408233155_4X1vGlar.jpg",
-            "desc": "良渚古城遗址公园",
+            "text": "良渚古城遗址公园",
             "preview": true
           },
           {
             "type": "image",
             "image": "https://img2.wmnetwork.cc/w/202104/08/0_20210408233155_4X1vGlar.jpg",
-            "desc": "良渚古城遗址公园",
+            "text": "良渚古城遗址公园",
             "preview": true
           }
         ]
@@ -137,6 +149,7 @@
         "nodes": [
           {
             "type": "image",
+            "image": "http://img2.wmnetwork.cc/w/202110/26/0_20211026171708_4FNko8zd.png",
             "text": "http://img2.wmnetwork.cc/w/202110/26/0_20211026171930_aT4M7uVO.png",
             "link": "9"
           },
@@ -220,40 +233,60 @@
         "nodes": [
           {
             "type": "image",
-            "text": "http://img2.wmnetwork.cc/w/202104/08/0_20210408183136_07OemuPW.png"
+            "image": "http://img2.wmnetwork.cc/w/202104/08/0_20210408183136_07OemuPW.png"
           },
           {
             "type": "tabs",
             "children": [
               {
+                "type": "table-texts",
+                "text": "考察人员名单",
+                "desc": [
+                  ["xxx", "xx负责人 xxx负责人"],
+                  ["xxx", "xx负责人 xxx负责人"],
+                  ["xxx", "xx负责人 xxx负责人"],
+                  ["xxx", "xx负责人 xxx负责人"],
+                  ["xxx", "xx负责人 xxx负责人"],
+                  ["xxx", "xx负责人 xxx负责人"]
+                ],
                 "title": "考察人员名单",
                 "nodes": [
                   {
-                    "type": "tableTexts",
-                    "texts": [
-                      ["梁xx", "xx负责人 xxx负责人"],
-                      ["梁xx", "xx负责人 xxx负责人"],
-                      ["梁xx", "xx负责人 xxx负责人"],
-                      ["梁xx", "xx负责人 xxx负责人"],
-                      ["梁xx", "xx负责人 xxx负责人"],
-                      ["xx", "xx负责人 xxx负责人"]
+                    "type": "table-texts",
+                    "desc": [
+                      ["xxx", "xx负责人 xxx负责人"],
+                      ["xxx", "xx负责人 xxx负责人"],
+                      ["xxx", "xx负责人 xxx负责人"],
+                      ["xxx", "xx负责人 xxx负责人"],
+                      ["xxx", "xx负责人 xxx负责人"],
+                      ["xxx", "xx负责人 xxx负责人"]
                     ],
                     "widths": ["100px", ""]
                   }
                 ]
               },
               {
+                "type": "table-texts",
+                "text": "考察人员名单",
+                "desc": [
+                  ["xxx", "xx负责人 xxx负责人"],
+                  ["xxx", "xx负责人 xxx负责人"],
+                  ["xxx", "xx负责人 xxx负责人"],
+                  ["xxx", "xx负责人 xxx负责人"],
+                  ["xxx", "xx负责人 xxx负责人"],
+                  ["xxx", "xx负责人 xxx负责人"]
+                ],
                 "title": "陪同名单",
                 "nodes": [
                   {
-                    "type": "tableTexts",
-                    "texts": [
-                      ["梁xx", "xx负责人 xxx负责人"],
-                      ["梁xx", "xx负责人 xxx负责人"],
-                      ["梁xx", "xx负责人 xxx负责人"],
-                      ["梁xx", "xx负责人 xxx负责人"],
-                      ["梁xx", "xx负责人 xxx负责人"],
-                      ["梁xx", "xx负责人 xxx负责人"]
+                    "type": "table-texts",
+                    "desc": [
+                      ["xxx1", "xx负责人 xxx负责人"],
+                      ["xxx1", "xx负责人 xxx负责人"],
+                      ["xxx1", "xx负责人 xxx负责人"],
+                      ["xxx1", "xx负责人 xxx负责人"],
+                      ["xxx1", "xx负责人 xxx负责人"],
+                      ["xxx1", "xx负责人 xxx负责人"]
                     ],
                     "widths": ["100px", ""]
                   }
@@ -271,16 +304,16 @@
             "text": "行程安排"
           },
           {
-            "type": "blodText",
+            "type": "bold-text",
             "text": "10月27日(星期三)"
           },
           {
-            "type": "blodText",
+            "type": "bold-text",
             "text": "上午"
           },
           {
-            "type": "tableTexts",
-            "texts": [
+            "type": "table-texts",
+            "desc": [
               ["08:45", "出发余杭区"],
               ["09:30", "考察梦想小镇"],
               ["08:45", "出发余杭区"],
@@ -293,15 +326,61 @@
             "widths": ["100px", ""]
           },
           {
-            "type": "blodText",
+            "type": "bold-text",
             "text": "中午"
           },
           {
-            "type": "tableTexts",
-            "texts": [["12:00", "午餐"]],
+            "type": "table-texts",
+            "desc": [["12:00", "午餐"]],
             "widths": ["100px", ""]
           }
         ]
+      },
+      {
+        "id": "11",
+        "nodes": [
+          {
+            "type": "form",
+            "children": [
+              {
+                "label": "姓名",
+                "field": "name",
+                "type": "form-input",
+                "required": true
+              },
+              {
+                "label": "手机",
+                "field": "phone",
+                "type": "form-input",
+                "required": true
+              },
+              {
+                "label": "来程方式",
+                "field": "come-type",
+                "type": "form-radio",
+                "options": ["动车", "飞机"],
+                "required": true
+              },
+              {
+                "label": "来程车次或航班",
+                "field": "come-no",
+                "type": "form-input",
+                "required": true
+              },
+              {
+                "label": "到达时间",
+                "field": "come-time",
+                "type": "form-datetime",
+                "mode": "datetime",
+                "required": true
+              }
+            ],
+            "submit": {},
+            "formId": "579773f089e7bbf1b50a43e2c83645b3",
+            "formName": "xxx表单",
+            "formAction": "post"
+          }
+        ]
       }
     ]
   }

+ 5 - 1
src/router/index.js

@@ -13,10 +13,14 @@ export default new VueRouter({
       redirect: '/page/679773f089e7bbf1b50a43e2c83645be/main',
     },
     {
-      path: '/page/:uuid/:id', 
+      path: '/page/:uuid/:id',
       component: Page,
       name: 'page',
     },
+    {
+      path: '/demo',
+      component: () => import('../views/demo.vue'),
+    },
     {
       path: '*', component: NotFound,
     }

+ 42 - 18
src/tailwind.css

@@ -183,7 +183,7 @@ input,
 optgroup,
 select,
 textarea {
-  font-family: STXinwei;
+  font-family: inherit;
   /* 1 */
   font-size: 100%;
   /* 1 */
@@ -476,10 +476,6 @@ Ensure the default browser behavior of the `hidden` attribute.
   position: relative;
 }
 
-.m-4 {
-  margin: 1rem;
-}
-
 .mx-auto {
   margin-left: auto;
   margin-right: auto;
@@ -505,10 +501,26 @@ Ensure the default browser behavior of the `hidden` attribute.
   margin-top: 0.5rem;
 }
 
+.mr-4 {
+  margin-right: 1rem;
+}
+
+.mb-2 {
+  margin-bottom: 0.5rem;
+}
+
+.inline-block {
+  display: inline-block;
+}
+
 .flex {
   display: flex;
 }
 
+.table {
+  display: table;
+}
+
 .grid {
   display: grid;
 }
@@ -537,6 +549,10 @@ Ensure the default browser behavior of the `hidden` attribute.
   flex: 1 1 auto;
 }
 
+.flex-1 {
+  flex: 1 1 0%;
+}
+
 .grid-cols-1 {
   grid-template-columns: repeat(1, minmax(0, 1fr));
 }
@@ -573,10 +589,6 @@ Ensure the default browser behavior of the `hidden` attribute.
   grid-template-columns: repeat(9, minmax(0, 1fr));
 }
 
-.grid-cols-none {
-  grid-template-columns: none;
-}
-
 .grid-cols-10 {
   grid-template-columns: repeat(10, minmax(0, 1fr));
 }
@@ -589,6 +601,10 @@ Ensure the default browser behavior of the `hidden` attribute.
   grid-template-columns: repeat(12, minmax(0, 1fr));
 }
 
+.grid-cols-none {
+  grid-template-columns: none;
+}
+
 .flex-col {
   flex-direction: column;
 }
@@ -613,10 +629,23 @@ Ensure the default browser behavior of the `hidden` attribute.
   border-radius: 0.5rem;
 }
 
+.rounded-md {
+  border-radius: 0.375rem;
+}
+
 .border {
   border-width: 1px;
 }
 
+.border-2 {
+  border-width: 2px;
+}
+
+.border-red-700 {
+  --tw-border-opacity: 1;
+  border-color: rgb(185 28 28 / var(--tw-border-opacity));
+}
+
 .object-cover {
   -o-object-fit: cover;
      object-fit: cover;
@@ -636,15 +665,14 @@ Ensure the default browser behavior of the `hidden` attribute.
   padding-bottom: 0.25rem;
 }
 
-.py-4 {
-  padding-top: 1rem;
-  padding-bottom: 1rem;
-}
-
 .text-center {
   text-align: center;
 }
 
+.text-right {
+  text-align: right;
+}
+
 .text-sm {
   font-size: 0.875rem;
   line-height: 1.25rem;
@@ -655,10 +683,6 @@ Ensure the default browser behavior of the `hidden` attribute.
   color: rgb(255 255 255 / var(--tw-text-opacity));
 }
 
-.filter {
-  filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
-}
-
 .last\:mb-0:last-child {
   margin-bottom: 0px;
 }

+ 19 - 0
src/views/demo.vue

@@ -0,0 +1,19 @@
+<template>
+  <div></div>
+</template>
+<script>
+import pages from '../data/pages.json';
+
+export default {
+  created(){
+    const _pages = {};
+    pages.data.pages.forEach(page => {
+      _pages[page.id] = page;
+    });
+    pages.data.pages = _pages;
+    this.$store.dispatch('setPage', pages.data).then(() => {
+      this.$router.push('/page/demo/main');
+    })
+  }
+}
+</script>

+ 7 - 1
src/views/page.vue

@@ -52,6 +52,7 @@ export default {
     },
     $route(){
       this.getPageInfo();
+      setTimeout(() => window.scrollTo(0, 0), 0);
     }
   },
   created(){
@@ -81,13 +82,18 @@ export default {
     },
     init(){
       let {uuid} = this.$route.params;
+      const { data } = this.$store.state.pages;
+      if(data) {
+        return this.getPageInfo();
+      }
+
       if(this.loading) return;
       this.loading = true;
       fetch("paper.getpaper", {uuid}).then((res) => {
         this.loading = false;
         this.$store.dispatch('setPage', res);
         res.title && (document.title = res.title);
-        this.getPageInfo()
+        this.getPageInfo();
       }).catch((err) => {
         console.log("err", err)
         this.loading = false;

+ 69 - 1
yarn.lock

@@ -892,6 +892,13 @@
     "@babel/types" "^7.4.4"
     esutils "^2.0.2"
 
+"@babel/runtime@7.x":
+  version "7.18.0"
+  resolved "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.18.0.tgz#6d77142a19cb6088f0af662af1ada37a604d34ae"
+  integrity sha512-YMQvx/6nKEaucl0MY56mwIG483xk8SDNdlUwb2Ts6FUpr7fm85DxEmsY18LXBNhcTz6tO6JwZV8w1W06v8UKeg==
+  dependencies:
+    regenerator-runtime "^0.13.4"
+
 "@babel/runtime@^7.11.0", "@babel/runtime@^7.8.4":
   version "7.17.7"
   resolved "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.17.7.tgz#a5f3328dc41ff39d803f311cfe17703418cf9825"
@@ -1025,6 +1032,11 @@
     "@nodelib/fs.scandir" "2.1.5"
     fastq "^1.6.0"
 
+"@popperjs/core@^2.9.2":
+  version "2.11.5"
+  resolved "https://registry.npmmirror.com/@popperjs/core/-/core-2.11.5.tgz#db5a11bf66bdab39569719555b0f76e138d7bd64"
+  integrity sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==
+
 "@soda/friendly-errors-webpack-plugin@^1.7.1":
   version "1.8.1"
   resolved "https://registry.npmmirror.com/@soda/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-1.8.1.tgz#4d4fbb1108993aaa362116247c3d18188a2c6c85"
@@ -1078,7 +1090,19 @@
   resolved "https://registry.npmmirror.com/@types/q/-/q-1.5.5.tgz#75a2a8e7d8ab4b230414505d92335d1dcb53a6df"
   integrity sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ==
 
-"@vue/babel-helper-vue-jsx-merge-props@^1.2.1":
+"@vant/icons@^1.7.1":
+  version "1.8.0"
+  resolved "https://registry.npmmirror.com/@vant/icons/-/icons-1.8.0.tgz#36b13f2e628b533f6523a93a168cf02f07056674"
+  integrity sha512-sKfEUo2/CkQFuERxvkuF6mGQZDKu3IQdj5rV9Fm0weJXtchDSSQ+zt8qPCNUEhh9Y8shy5PzxbvAfOOkCwlCXg==
+
+"@vant/popperjs@^1.1.0":
+  version "1.2.0"
+  resolved "https://registry.npmmirror.com/@vant/popperjs/-/popperjs-1.2.0.tgz#9b56e0a5e66ce9bef8d2f3f5b4cb540cd2143260"
+  integrity sha512-YdtVBcNCsZqCBu7vUvpTm9MG4V+Hz5UmCr7UZlNXoYXZbDD238ncqNStw+gQVWySeCY5yAQVOT6deJN5poVe8g==
+  dependencies:
+    "@popperjs/core" "^2.9.2"
+
+"@vue/babel-helper-vue-jsx-merge-props@^1.0.0", "@vue/babel-helper-vue-jsx-merge-props@^1.2.1":
   version "1.2.1"
   resolved "https://registry.npmmirror.com/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.2.1.tgz#31624a7a505fb14da1d58023725a4c5f270e6a81"
   integrity sha512-QOi5OW45e2R20VygMSNhyQHvpdUwQZqGPc748JLGCYEy+yp8fNFNdbNIGAgZmi9e+2JHPd6i6idRuqivyicIkA==
@@ -2994,6 +3018,11 @@ dashdash@^1.12.0:
   dependencies:
     assert-plus "^1.0.0"
 
+dayjs@^1.11.2:
+  version "1.11.2"
+  resolved "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.2.tgz#fa0f5223ef0d6724b3d8327134890cfe3d72fbe5"
+  integrity sha512-F4LXf1OeU9hrSYRPTTj/6FbO4HTjPKXvEIC1P2kcnFurViINCVk3ZV0xAS3XVx9MkMsXbbqlK6hjseaYbgKEHw==
+
 de-indent@^1.0.2:
   version "1.0.2"
   resolved "https://registry.npmmirror.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
@@ -3052,6 +3081,11 @@ deepmerge@^1.5.2:
   resolved "https://registry.npmmirror.com/deepmerge/-/deepmerge-1.5.2.tgz#10499d868844cdad4fee0842df8c7f6f0c95a753"
   integrity sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ==
 
+deepmerge@^4.2.2:
+  version "4.2.2"
+  resolved "https://registry.npmmirror.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
+  integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
+
 default-gateway@^4.2.0:
   version "4.2.0"
   resolved "https://registry.npmmirror.com/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b"
@@ -3960,6 +3994,11 @@ flatted@^2.0.0:
   resolved "https://registry.npmmirror.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138"
   integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==
 
+flatted@^3.0.5:
+  version "3.2.5"
+  resolved "https://registry.npmmirror.com/flatted/-/flatted-3.2.5.tgz#76c8584f4fc843db64702a6bd04ab7a8bd666da3"
+  integrity sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==
+
 flush-write-stream@^1.0.0:
   version "1.1.1"
   resolved "https://registry.npmmirror.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8"
@@ -5058,6 +5097,11 @@ jest-worker@^25.4.0:
     merge-stream "^2.0.0"
     supports-color "^7.0.0"
 
+js-md5@^0.7.3:
+  version "0.7.3"
+  resolved "https://registry.npmmirror.com/js-md5/-/js-md5-0.7.3.tgz#b4f2fbb0b327455f598d6727e38ec272cd09c3f2"
+  integrity sha512-ZC41vPSTLKGwIRjqDh8DfXoCrdQIyBgspJVPXHBGu4nZlAEvG3nf+jO9avM9RmLiGakg7vz974ms99nEV0tmTQ==
+
 js-message@1.0.7:
   version "1.0.7"
   resolved "https://registry.npmmirror.com/js-message/-/js-message-1.0.7.tgz#fbddd053c7a47021871bb8b2c95397cc17c20e47"
@@ -8385,6 +8429,17 @@ validate-npm-package-license@^3.0.1:
     spdx-correct "^3.0.0"
     spdx-expression-parse "^3.0.0"
 
+vant@^2.12.47:
+  version "2.12.47"
+  resolved "https://registry.npmmirror.com/vant/-/vant-2.12.47.tgz#7179f6120c4224b08431c31ba3928271a5ce4e39"
+  integrity sha512-D9QgxirzWuSJbLqU+TGgXRZ88OTmGDEtwxDrBr2JuLCZyijrYzc5KIrErd4c1MJoZsmYupqydyLqotoBkbQDjQ==
+  dependencies:
+    "@babel/runtime" "7.x"
+    "@vant/icons" "^1.7.1"
+    "@vant/popperjs" "^1.1.0"
+    "@vue/babel-helper-vue-jsx-merge-props" "^1.0.0"
+    vue-lazyload "1.2.3"
+
 vary@~1.1.2:
   version "1.1.2"
   resolved "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
@@ -8427,6 +8482,11 @@ vue-hot-reload-api@^2.3.0:
   resolved "https://registry.npmmirror.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz#532955cc1eb208a3d990b3a9f9a70574657e08f2"
   integrity sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==
 
+vue-lazyload@1.2.3:
+  version "1.2.3"
+  resolved "https://registry.npmmirror.com/vue-lazyload/-/vue-lazyload-1.2.3.tgz#901f9ec15c7e6ca78781a2bae4a343686bdedb2c"
+  integrity sha512-DC0ZwxanbRhx79tlA3zY5OYJkH8FYp3WBAnAJbrcuoS8eye1P73rcgAZhyxFSPUluJUTelMB+i/+VkNU/qVm7g==
+
 vue-loader@^15.9.2:
   version "15.9.8"
   resolved "https://registry.npmmirror.com/vue-loader/-/vue-loader-15.9.8.tgz#4b0f602afaf66a996be1e534fb9609dc4ab10e61"
@@ -8479,6 +8539,14 @@ vue@^2.6.11:
   resolved "https://registry.npmmirror.com/vue/-/vue-2.6.14.tgz#e51aa5250250d569a3fbad3a8a5a687d6036e235"
   integrity sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ==
 
+vuex-persist@^3.1.3:
+  version "3.1.3"
+  resolved "https://registry.npmmirror.com/vuex-persist/-/vuex-persist-3.1.3.tgz#518c722a2ca3026bcee5732f99d24f75cee0f3b6"
+  integrity sha512-QWOpP4SxmJDC5Y1+0+Yl/F4n7z27syd1St/oP+IYCGe0X0GFio0Zan6kngZFufdIhJm+5dFGDo3VG5kdkCGeRQ==
+  dependencies:
+    deepmerge "^4.2.2"
+    flatted "^3.0.5"
+
 vuex@3.6.2:
   version "3.6.2"
   resolved "https://registry.npmmirror.com/vuex/-/vuex-3.6.2.tgz#236bc086a870c3ae79946f107f16de59d5895e71"