YY 3 years ago
parent
commit
1bba6c5fe5

+ 88 - 0
src/components/HelloWorld.vue

@@ -0,0 +1,88 @@
+<template>
+  <div class="hello">
+    <h1>{{ msg }}</h1>
+    <p>
+      For a guide and recipes on how to configure / customize this project,<br />
+      check out the
+      <a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
+    </p>
+    <h3>Installed CLI Plugins</h3>
+    <ul>
+      <li>
+        <a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a>
+      </li>
+      <li>
+        <a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router" target="_blank" rel="noopener">router</a>
+      </li>
+      <li>
+        <a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-vuex" target="_blank" rel="noopener">vuex</a>
+      </li>
+      <li>
+        <a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a>
+      </li>
+    </ul>
+    <h3>Essential Links</h3>
+    <ul>
+      <li>
+        <a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a>
+      </li>
+      <li>
+        <a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a>
+      </li>
+      <li>
+        <a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a>
+      </li>
+      <li>
+        <a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a>
+      </li>
+      <li>
+        <a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a>
+      </li>
+    </ul>
+    <h3>Ecosystem</h3>
+    <ul>
+      <li>
+        <a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a>
+      </li>
+      <li>
+        <a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a>
+      </li>
+      <li>
+        <a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a>
+      </li>
+      <li>
+        <a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a>
+      </li>
+      <li>
+        <a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a>
+      </li>
+    </ul>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'HelloWorld',
+  props: {
+    msg: String,
+  },
+};
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped lang="less">
+h3 {
+  margin: 40px 0 0;
+}
+ul {
+  list-style-type: none;
+  padding: 0;
+}
+li {
+  display: inline-block;
+  margin: 0 10px;
+}
+a {
+  color: #42b983;
+}
+</style>

+ 69 - 0
src/components/c-select.vue

@@ -0,0 +1,69 @@
+<template>
+  <div id="c-select">
+    <van-cell :title="label" @click="show = true">
+      <span v-if="mval" style="color: #000">{{ mval }}</span>
+      <span v-else>{{ `请选择${label}` }}</span>
+    </van-cell>
+    <van-popup v-model="show" show-toolbar position="bottom">
+      <van-picker v-if="type === 'select'" :value-key="valueKey" :title="label" show-toolbar :columns="list" @confirm="onConfirm" @cancel="show = false" />
+      <van-checkbox-group v-model="multi" v-else>
+        <van-picker :title="label" :show-toolbar="true" :columns="list" @confirm="onConfirm" @cancel="show = false">
+          <template #option="item">
+            <van-checkbox :name="item">{{ item }}</van-checkbox>
+          </template>
+        </van-picker>
+      </van-checkbox-group>
+    </van-popup>
+  </div>
+</template>
+
+<script>
+const _ = require('lodash');
+export default {
+  name: 'c-select',
+  props: {
+    label: { type: String },
+    mval: { type: String },
+    list: { type: Array, default: () => [] },
+    type: { type: String },
+    valueKey: { type: String },
+  },
+  model: {
+    prop: 'mval',
+    event: 'change',
+  },
+  components: {},
+  data: function () {
+    return {
+      show: false,
+      selectList: [],
+      multi: [],
+    };
+  },
+  created() {},
+  methods: {
+    onConfirm(value) {
+      if (this.type === 'select') this.$emit('change', value);
+      else {
+        const str = this.multi.join(',');
+        this.$emit('change', str);
+      }
+      this.show = false;
+    },
+  },
+  computed: {
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+/deep/.van-cell__value {
+  text-align: left;
+}
+</style>

+ 74 - 0
src/components/footers.vue

@@ -0,0 +1,74 @@
+<template>
+  <div id="footers">
+    <van-grid :column-num="list.length" clickable>
+      <van-grid-item v-for="(i, index) in list" :key="`menu-${index}`" :text="i.label" :to="i.router" />
+    </van-grid>
+  </div>
+</template>
+
+<script>
+import { mapState, mapGetters, mapMutations, createNamespacedHelpers } from 'vuex';
+const { mapActions: column } = createNamespacedHelpers('column');
+export default {
+  name: 'footers',
+  props: {},
+  components: {},
+  data: function () {
+    return {
+      list: [],
+    };
+  },
+  mounted() {
+    this.initMenu();
+  },
+  methods: {
+    ...column(['query']),
+    ...mapMutations(['setMenu']),
+    async initMenu() {
+      let columns = sessionStorage.getItem('columns');
+      if (!columns) {
+        let res = await this.getColumns();
+        columns = res;
+      }
+      columns = JSON.parse(columns);
+      this.setMenu(columns);
+      this.$set(this, `list`, this.getMenu(this.keyWord));
+    },
+    async getColumns() {
+      const res = await this.query();
+      if (this.$checkRes(res)) {
+        sessionStorage.setItem('columns', JSON.stringify(res.data));
+        return JSON.stringify(res.data);
+      }
+    },
+  },
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    ...mapGetters(['getMenu']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+    keyWord() {
+      return this.$route.meta.key;
+    },
+  },
+  watch: {
+    $route: {
+      handler(to, form) {
+        this.initMenu();
+      },
+      deep: true,
+      immediate: true,
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+/deep/.van-grid-item__content {
+  padding: 11px 8px;
+}
+</style>

+ 17 - 0
src/components/vForm.md

@@ -0,0 +1,17 @@
+# vForm
+
+|属性名|数据类型|默认值|是否必要|描述|
+|:--:|:--:|:--:|:--:|:--:|
+|v-model|Object|`-`|true|存放值的地方(不知道咋解释好,┓( ´∀` )┏)|
+|fields|Array|`[]`|`-`|字段|
+
+### fields
+|属性名|数据类型|默认值|是否必要|描述|
+|:--:|:--:|:--:|:--:|:--:|
+|label|String|`-`|false|字段中文/XX文|
+|model|String|`-`|true|字段在数据库的key,不写就别用了,(╯‵□′)╯︵┻━┻|
+|rules|Array|`[]`|false|输入类型的验证,validator写法,不懂百度|
+|required|Boolean|`-`|false|是否必填/选|
+|type|String|`-`|false|本字段类型:input,select,date....|
+|selectKey|String|`-`|=>|若type为select类型,必填,表示在选择的时候,看哪个字段的内容|
+|selectProp|String|`-`|=>|若type为select类型,表示在选择的时候,选择哪个字段,若没有值,则选择这个object|

+ 103 - 0
src/components/vForm.vue

@@ -0,0 +1,103 @@
+<template>
+  <div id="vForm">
+    <van-form @submit="onSubmit">
+      <template v-for="(f, fi) in fields">
+        <template v-if="!f.type || f.type === 'input'">
+          <van-field
+            :key="`${fi}.${f.model}`"
+            v-model="value[f.model]"
+            :label="f.label"
+            :placeholder="`${f.label}`"
+            :rules="f.rules ? f.rules : []"
+            :required="f.required"
+          />
+        </template>
+        <template v-if="f.type === 'date'">
+          <v-date :key="`${fi}.${f.model}`" v-model="value[f.model]" :label="f.label" :required="f.required"></v-date>
+        </template>
+        <template v-if="f.type === 'select'">
+          <v-select
+            :key="`${fi}.${f.model}`"
+            v-model="value[f.model]"
+            :label="f.label"
+            :list="getSelectList(f.model)"
+            :labelKey="f.selectKey"
+            :prop="f.selectProp"
+            :required="f.required"
+          ></v-select>
+        </template>
+        <template v-if="f.type === 'textarea'">
+          <van-field
+            :key="`${fi}.${f.model}`"
+            v-model="value[f.model]"
+            :label="f.label"
+            rows="2"
+            autosize
+            type="textarea"
+            :placeholder="`请输入${f.label}`"
+            :required="f.required"
+          />
+        </template>
+      </template>
+      <div style="margin: 16px">
+        <van-button round block type="info" native-type="submit">提交</van-button>
+      </div>
+    </van-form>
+  </div>
+</template>
+
+<script>
+const _ = require('lodash');
+import vDate from '@/components/vdate.vue';
+import vSelect from '@/components/vselect.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'vForm',
+  props: {
+    value: { type: Object, required: true },
+    fields: { type: Array, default: () => [] },
+  },
+
+  components: { vDate, vSelect },
+  data: function () {
+    return {};
+  },
+  created() {},
+  methods: {
+    onSubmit() {
+      // 进入submit后,只能说明,有rules的是按要求输入的,其余的select,date还未验证,需要此处验证
+      const needCheck = this.fields.filter((f) => f.required);
+      const checkResult = this.checkRequired(needCheck);
+      if (!checkResult) return;
+      this.$emit('submit', _.cloneDeep(this.value));
+    },
+    checkRequired(checkList) {
+      let res = true;
+      for (const field of checkList) {
+        const { model, label } = field;
+        if (!this.value[model]) {
+          this.$toast({ type: 'fail', message: `请完善${label}项` });
+          res = false;
+          break;
+        }
+      }
+      return res;
+    },
+    getSelectList(model) {
+      const selectList = _.get(this.$attrs, `${model}List`, []);
+      return selectList;
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 61 - 0
src/components/vcheckbox.vue

@@ -0,0 +1,61 @@
+<template>
+  <div id="vcheckbox">
+    <van-field :value="display" :label="label" :placeholder="`请选择${label}`" readonly @click="show = true" />
+    <van-popup v-model="show" show-toolbar position="bottom">
+      <van-picker :title="label" show-toolbar :value-key="labelKey" :columns="list" @confirm="onConfirm" @cancel="show = false" />
+    </van-popup>
+  </div>
+</template>
+
+<script>
+const _ = require('lodash');
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'vcheckbox',
+  props: {
+    label: { type: String }, //字段中文
+    mval: { type: String }, //v-model
+    list: { type: Array }, //选择的列表
+    labelKey: { type: String, default: 'name' }, //如果选项为object,选择哪个字段作为显示的选项
+    prop: { type: String }, // 如果选项为object,选择哪个字段作为v-model的值,没有的话就把该项作为值给v-model
+  },
+  model: {
+    prop: 'mval',
+    event: 'change',
+  },
+  components: {},
+  data: function () {
+    return {
+      show: false,
+    };
+  },
+  created() {},
+  methods: {
+    onConfirm(value) {
+      if (this.prop) this.$emit('change', _.get(value, this.prop));
+      else this.$emit('change', value);
+      this.show = false;
+    },
+  },
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+    display() {
+      let value = '';
+      if (this.mval) value = this.mval;
+      if (this.prop) {
+        const r = this.list.find((f) => f[this.prop] === value);
+        if (r) value = r[this.labelKey];
+      }
+      return value;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 67 - 0
src/components/vdate.vue

@@ -0,0 +1,67 @@
+<template>
+  <div id="vdate">
+    <van-field :value="mval" :label="label" :placeholder="`请选择${label}`" readonly @click="show = true" :required="required" />
+    <van-popup v-model="show" show-toolbar position="bottom">
+      <van-datetime-picker
+        :value="cdate"
+        type="date"
+        :title="`请选择${label}`"
+        :min-date="minDate"
+        :max-date="maxDate"
+        @cancel="show = false"
+        @confirm="toConfirm"
+      />
+    </van-popup>
+  </div>
+</template>
+
+<script>
+const _ = require('lodash');
+const moment = require('moment');
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'vdate',
+  props: {
+    label: { type: String }, //字段中文
+    mval: { type: String }, //v-model
+    required: { type: Boolean, default: false },
+  },
+  model: {
+    prop: 'mval',
+    event: 'change',
+  },
+  components: {},
+  data: function () {
+    return {
+      show: false,
+      minDate: new Date(1990, 0, 1),
+      maxDate: new Date(),
+    };
+  },
+  created() {},
+  methods: {
+    toConfirm(val) {
+      this.$emit('change', moment(val).format('YYYY-MM-DD'));
+      this.show = false;
+    },
+  },
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+    cdate() {
+      let date = '';
+      if (this.mval) {
+        date = new Date(this.mval);
+      }
+      return date;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 61 - 0
src/components/vselect copy.vue

@@ -0,0 +1,61 @@
+<template>
+  <div id="vselect">
+    <van-field :value="display" :label="label" :placeholder="`请选择${label}`" readonly @click="show = true" />
+    <van-popup v-model="show" show-toolbar position="bottom">
+      <van-picker :title="label" show-toolbar :value-key="labelKey" :columns="list" @confirm="onConfirm" @cancel="show = false" />
+    </van-popup>
+  </div>
+</template>
+
+<script>
+const _ = require('lodash');
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'vselect',
+  props: {
+    label: { type: String }, //字段中文
+    mval: { type: String }, //v-model
+    list: { type: Array }, //选择的列表
+    labelKey: { type: String, default: 'name' }, //如果选项为object,选择哪个字段作为显示的选项
+    prop: { type: String }, // 如果选项为object,选择哪个字段作为v-model的值,没有的话就把该项作为值给v-model
+  },
+  model: {
+    prop: 'mval',
+    event: 'change',
+  },
+  components: {},
+  data: function () {
+    return {
+      show: false,
+    };
+  },
+  created() {},
+  methods: {
+    onConfirm(value) {
+      if (this.prop) this.$emit('change', _.get(value, this.prop));
+      else this.$emit('change', value);
+      this.show = false;
+    },
+  },
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+    display() {
+      let value = '';
+      if (this.mval) value = this.mval;
+      if (this.prop) {
+        const r = this.list.find((f) => f[this.prop] === value);
+        if (r) value = r[this.labelKey];
+      }
+      return value;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 62 - 0
src/components/vselect.vue

@@ -0,0 +1,62 @@
+<template>
+  <div id="vselect">
+    <van-field :value="display" :label="label" :placeholder="`请选择${label}`" readonly @click="show = true" :required="required" />
+    <van-popup v-model="show" show-toolbar position="bottom">
+      <van-picker :title="label" show-toolbar :value-key="labelKey" :columns="list" @confirm="onConfirm" @cancel="show = false" />
+    </van-popup>
+  </div>
+</template>
+
+<script>
+const _ = require('lodash');
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'vselect',
+  props: {
+    label: { type: String }, //字段中文
+    mval: { type: String }, //v-model
+    list: { type: Array }, //选择的列表
+    labelKey: { type: String, default: 'name' }, //如果选项为object,选择哪个字段作为显示的选项
+    prop: { type: String }, // 如果选项为object,选择哪个字段作为v-model的值,没有的话就把该项作为值给v-model
+    required: { type: Boolean, default: false },
+  },
+  model: {
+    prop: 'mval',
+    event: 'change',
+  },
+  components: {},
+  data: function () {
+    return {
+      show: false,
+    };
+  },
+  created() {},
+  methods: {
+    onConfirm(value) {
+      if (this.prop) this.$emit('change', _.get(value, this.prop));
+      else this.$emit('change', value);
+      this.show = false;
+    },
+  },
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+    display() {
+      let value = '';
+      if (this.mval) value = this.mval;
+      if (this.prop) {
+        const r = this.list.find((f) => f[this.prop] === value);
+        if (r) value = r[this.labelKey];
+      }
+      return value;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 55 - 0
src/components/vupload.vue

@@ -0,0 +1,55 @@
+<template>
+  <div id="vupload">
+    <van-uploader :fileList="fileList" :after-read="upload" accept="*" @delete="toDelete" />
+  </div>
+</template>
+
+<script>
+const _ = require('lodash');
+import axios from 'axios';
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'vupload',
+  props: {
+    fileList: { type: Array },
+    url: { type: String, required: true },
+  },
+  model: {
+    prop: 'fileList',
+    event: 'change',
+  },
+  components: {},
+  data: function () {
+    return {};
+  },
+  created() {},
+  methods: {
+    async upload({ file }) {
+      let formdata = new FormData();
+      formdata.append('file', file, file.name);
+      const res = await axios.post(this.url, formdata, { headers: { 'Content-Type': 'multipart/form-data' } });
+      if (res.status === 200 && res.data.errcode == 0) {
+        const { id, name, uri } = res.data;
+        const obj = { name, url: uri };
+        this.fileList.push(obj);
+      }
+    },
+    toDelete(file) {
+      console.log(file);
+      const index = this.fileList.findIndex((f) => _.isEqual(f, file));
+      if (index > -1) this.fileList.splice(index, 1);
+    },
+  },
+  computed: {
+    ...mapState(['user', 'menuParams']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 88 - 0
src/layout/common/admin-frame.vue

@@ -0,0 +1,88 @@
+<template>
+  <div id="admin-frame">
+    <van-row>
+      <van-col span="24" class="main" :style="{ height: client.height + 'px' }">
+        <van-col span="24" class="top" style="height: 40px" v-if="useTop">
+          <top :topType="topType" @search="search" :leftArrow="leftArrow" @back="back"></top>
+        </van-col>
+        <van-col span="24" class="info" :style="{ height: getHeight() }"><slot name="info"></slot></van-col>
+        <van-col span="24" class="page" style="height: 40px" v-if="usePage"><page :limit="limit" :total="total" @search="search"></page></van-col>
+      </van-col>
+    </van-row>
+  </div>
+</template>
+
+<script>
+import top from './top.vue';
+import page from './page.vue';
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'admin-frame',
+  props: {
+    // 头部信息
+    useTop: { type: Boolean, default: () => true },
+    topType: { type: String, default: () => '1' },
+    leftArrow: { type: Boolean, default: () => true },
+    // 分页
+    usePage: { type: Boolean, default: () => true },
+    limit: { type: Number, default: () => 10 },
+    total: { type: Number, default: () => 0 },
+  },
+  components: {
+    top,
+    page,
+  },
+  data: function () {
+    return {
+      client: {},
+    };
+  },
+  created() {},
+  methods: {
+    back() {
+      this.$emit('back');
+    },
+    search(skip) {
+      this.$emit('search', skip);
+    },
+    // 计算高度
+    getHeight() {
+      let top = 0;
+      let page = 0;
+      if (this.useTop) top = 40;
+      if (this.usePage) page = 40;
+      return this.client.height - top - page + 'px';
+    },
+  },
+  mounted() {
+    let client = {
+      height: document.documentElement.clientHeight || document.body.clientHeight,
+      width: document.documentElement.clientWidth || document.body.clientWidth,
+    };
+    this.$set(this, `client`, client);
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.main {
+  .info {
+    overflow-x: hidden;
+    overflow-y: auto;
+    background-color: #f9f9f9;
+  }
+}
+</style>

+ 42 - 0
src/layout/common/footers.vue

@@ -0,0 +1,42 @@
+<template>
+  <div id="footers">
+    <div class="footers">
+      <van-grid :column-num="list.length" clickable>
+        <van-grid-item v-for="(i, index) in list" :key="`menu-${index}`" :text="i.label" :to="i.router" />
+      </van-grid>
+    </div>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'footers',
+  props: {
+    list: { type: Array, default: () => [] },
+  },
+  components: {},
+  data: function () {
+    return {};
+  },
+  created() {},
+  methods: {},
+  computed: {
+    ...mapState(['user']),
+    pageTitle() {
+      return `${this.$route.meta.title}`;
+    },
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.footers {
+  position: fixed;
+  bottom: 0px;
+  width: 100%;
+}
+</style>

+ 47 - 0
src/layout/common/page.vue

@@ -0,0 +1,47 @@
+<template>
+  <div id="page">
+    <el-row>
+      <el-col :span="24" class="main">
+        <van-pagination v-model="currentPage" @change="changePage" :total-items="total" :items-per-page="limit" :show-page-size="5" force-ellipses />
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'page',
+  props: {
+    total: { type: Number },
+    limit: { type: Number, default: () => 6 },
+  },
+  components: {},
+  data: function () {
+    return {
+      currentPage: 1,
+    };
+  },
+  created() {},
+  methods: {
+    changePage(page) {
+      this.$emit('search', { skip: (page - 1) * this.limit });
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped></style>

+ 7 - 0
src/layout/common/top.md

@@ -0,0 +1,7 @@
+<!-- 头部类型 -->
+# topType
+## topType=="1" 带有查询,查询方法:search
+
+## topType=="2" 带有标题和返回,返回方法:back
+
+## topType=="3" 带有返回和查询,返回方法:back,查询方法:search

+ 127 - 0
src/layout/common/top.vue

@@ -0,0 +1,127 @@
+<template>
+  <div id="top">
+    <el-col :span="24" class="one" v-if="topType == '1'">
+      <van-search v-model="searchName" placeholder="请输入信息标题" @search="search" />
+    </el-col>
+    <el-col :span="24" class="two" v-else-if="topType == '2'">
+      <van-nav-bar :title="this.$route.meta.title" :left-arrow="leftArrow" @click-left="upBack">
+        <template #left>
+          <span v-if="leftArrow">
+            <van-icon name="arrow-left" />
+            <span style="color: #409eff">返回</span>
+          </span>
+        </template>
+      </van-nav-bar>
+    </el-col>
+    <el-col :span="24" class="thr" v-else-if="topType == '3'">
+      <el-col :span="4" class="back" @click.native="upBack"> <van-icon name="arrow-left" />返回 </el-col>
+      <el-col :span="20" class="search">
+        <van-search v-model="searchName" placeholder="请输入信息" @search="search" />
+      </el-col>
+    </el-col>
+    <el-col :span="24" class="four" v-else-if="topType == '4'">
+      <el-col :span="4" class="back" @click.native="upBack"> <van-icon name="arrow-left" />返回 </el-col>
+      <el-col :span="16" class="title">
+        {{ $route.meta.title }}
+      </el-col>
+      <el-col :span="4" class="add">
+        <van-button icon="plus" size="small" type="info" round @click="add" />
+      </el-col>
+    </el-col>
+  </div>
+</template>
+
+<script>
+import { mapState, createNamespacedHelpers } from 'vuex';
+export default {
+  name: 'top',
+  props: {
+    topType: { type: String, default: () => '1' },
+    // 只有类型为2时,有用
+    leftArrow: { type: Boolean, default: () => true },
+  },
+  components: {},
+  data: function () {
+    return {
+      searchName: '',
+    };
+  },
+  created() {},
+  methods: {
+    // 查询
+    search() {
+      this.$emit('search', { searchName: this.searchName });
+    },
+    // 返回
+    upBack() {
+      this.$emit('back');
+    },
+    // 添加
+    add() {
+      this.$emit('add');
+    },
+  },
+  computed: {
+    ...mapState(['user']),
+  },
+  metaInfo() {
+    return { title: this.$route.meta.title };
+  },
+  watch: {
+    test: {
+      deep: true,
+      immediate: true,
+      handler(val) {},
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.one {
+  /deep/.van-search {
+    padding: 3px 5px;
+  }
+}
+.two {
+  /deep/.van-nav-bar__content {
+    height: 40px;
+  }
+  /deep/.van-icon {
+    top: 2px;
+  }
+}
+.thr {
+  .back {
+    color: #409eff;
+    padding: 8px 0;
+    text-align: center;
+    .van-icon {
+      top: 3px;
+    }
+  }
+  .search {
+    /deep/.van-search {
+      padding: 2px 5px 2px 0px;
+    }
+  }
+}
+.four {
+  .back {
+    color: #409eff;
+    padding: 8px 0;
+    text-align: center;
+    .van-icon {
+      top: 3px;
+    }
+  }
+  .title {
+    text-align: center;
+    padding: 9px 0;
+  }
+  .add {
+    text-align: center;
+    padding: 4px 0;
+  }
+}
+</style>

+ 1 - 1
src/views/index.vue

@@ -9,7 +9,7 @@
 </template>
 
 <script>
-import adminFrame from '@/layout/common/admin-frame.vue';
+import adminFrame from '@frame/src/components/mobile-frame/mobile-main.vue';
 import { mapState, createNamespacedHelpers } from 'vuex';
 const { mapActions: personal } = createNamespacedHelpers('personal');
 export default {