Browse Source

feat: Add example-page demo

kailong321200875 3 years ago
parent
commit
1492f9119a

+ 1 - 0
package.json

@@ -37,6 +37,7 @@
     "element-plus": "2.0.2",
     "intro.js": "^5.0.0",
     "lodash-es": "^4.17.21",
+    "mitt": "^3.0.0",
     "mockjs": "^1.1.0",
     "nprogress": "^0.2.0",
     "pinia": "^2.0.11",

+ 13 - 0
pnpm-lock.yaml

@@ -38,6 +38,7 @@ specifiers:
   less: ^4.1.2
   lint-staged: ^12.3.4
   lodash-es: ^4.17.21
+  mitt: ^3.0.0
   mockjs: ^1.1.0
   nprogress: ^0.2.0
   pinia: ^2.0.11
@@ -86,6 +87,7 @@ dependencies:
   element-plus: registry.npmmirror.com/element-plus/2.0.2_1a412d14def5ff5ca1122000e4bee666
   intro.js: registry.npmmirror.com/intro.js/5.0.0
   lodash-es: registry.nlark.com/lodash-es/4.17.21
+  mitt: registry.npmmirror.com/mitt/3.0.0
   mockjs: registry.npmmirror.com/mockjs/1.1.0
   nprogress: registry.npmmirror.com/nprogress/0.2.0
   pinia: registry.npmmirror.com/pinia/2.0.11_typescript@4.5.5+vue@3.2.31
@@ -10127,6 +10129,17 @@ packages:
     version: 1.2.5
     dev: true
 
+  registry.npmmirror.com/mitt/3.0.0:
+    resolution:
+      {
+        integrity: sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==,
+        registry: https://registry.npm.taobao.org/,
+        tarball: https://registry.npmmirror.com/mitt/-/mitt-3.0.0.tgz
+      }
+    name: mitt
+    version: 3.0.0
+    dev: false
+
   registry.npmmirror.com/mixin-deep/1.3.2:
     resolution:
       {

+ 1 - 1
src/components/TabMenu/src/helper.ts

@@ -27,7 +27,7 @@ export const filterMenusPath = (
   for (const v of routes) {
     let data: Nullable<AppRouteRecordRaw> = null
     const meta = (v.meta ?? {}) as RouteMeta
-    if (!meta.hidden) {
+    if (!meta.hidden || meta.showMainRoute) {
       const allParentPaht = getAllParentPath<AppRouteRecordRaw>(allRoutes, v.path)
 
       const fullPath = isUrl(v.path) ? v.path : allParentPaht.join('/')

+ 23 - 0
src/hooks/web/useEmitt.ts

@@ -0,0 +1,23 @@
+import mitt from 'mitt'
+import { onBeforeUnmount } from 'vue'
+
+interface Option {
+  name: string // 事件名称
+  callback: Fn // 回调
+}
+
+const emitter = mitt()
+
+export const useEmitt = (option?: Option) => {
+  if (option) {
+    emitter.on(option.name, option.callback)
+
+    onBeforeUnmount(() => {
+      emitter.off(option.name)
+    })
+  }
+
+  return {
+    emitter
+  }
+}

+ 5 - 1
src/locales/en.ts

@@ -115,7 +115,11 @@ export default {
     imageViewer: 'Image viewer',
     descriptions: 'Descriptions',
     example: 'Example',
-    exampleDialog: 'Example dialog'
+    exampleDialog: 'Example dialog',
+    examplePage: 'Example page',
+    exampleAdd: 'Example page - add',
+    exampleEdit: 'Example page - edit',
+    exampleDetail: 'Example page - detail'
   },
   analysis: {
     newUser: 'New user',

+ 5 - 1
src/locales/zh-CN.ts

@@ -115,7 +115,11 @@ export default {
     imageViewer: '图片预览',
     descriptions: '描述',
     example: '综合示例',
-    exampleDialog: '综合示例 - 弹窗'
+    exampleDialog: '综合示例 - 弹窗',
+    examplePage: '综合示例 - 页面',
+    exampleAdd: '综合示例 - 新增',
+    exampleEdit: '综合示例 - 编辑',
+    exampleDetail: '综合示例 - 详情'
   },
   analysis: {
     newUser: '新增用户',

+ 47 - 0
src/router/index.ts

@@ -352,6 +352,53 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
         meta: {
           title: t('router.exampleDialog')
         }
+      },
+      {
+        path: 'example-page',
+        component: () => import('@/views/Example/Page/ExamplePage.vue'),
+        name: 'ExamplePage',
+        meta: {
+          title: t('router.examplePage')
+        }
+      },
+      {
+        path: 'example-add',
+        component: () => import('@/views/Example/Page/ExampleAdd.vue'),
+        name: 'ExampleAdd',
+        meta: {
+          title: t('router.exampleAdd'),
+          noTagsView: true,
+          noCache: true,
+          hidden: true,
+          showMainRoute: true,
+          activeMenu: '/example/example-page'
+        }
+      },
+      {
+        path: 'example-edit',
+        component: () => import('@/views/Example/Page/ExampleEdit.vue'),
+        name: 'ExampleEdit',
+        meta: {
+          title: t('router.exampleEdit'),
+          noTagsView: true,
+          noCache: true,
+          hidden: true,
+          showMainRoute: true,
+          activeMenu: '/example/example-page'
+        }
+      },
+      {
+        path: 'example-detail',
+        component: () => import('@/views/Example/Page/ExampleDetail.vue'),
+        name: 'ExampleDetail',
+        meta: {
+          title: t('router.exampleDetail'),
+          noTagsView: true,
+          noCache: true,
+          hidden: true,
+          showMainRoute: true,
+          activeMenu: '/example/example-page'
+        }
       }
     ]
   }

+ 3 - 33
src/views/Example/Dialog/components/Detail.vue

@@ -1,6 +1,5 @@
 <script setup lang="ts">
-import { getTableDetApi } from '@/api/table'
-import { PropType, watch, ref, reactive } from 'vue'
+import { PropType, reactive } from 'vue'
 import type { TableData } from '@/api/table/types'
 import { Descriptions } from '@/components/Descriptions'
 import { useI18n } from '@/hooks/web/useI18n'
@@ -8,42 +7,13 @@ import { ElTag } from 'element-plus'
 
 const { t } = useI18n()
 
-const props = defineProps({
+defineProps({
   currentRow: {
     type: Object as PropType<Nullable<TableData>>,
     default: () => null
   }
 })
 
-const currentRow = ref<Nullable<TableData>>(null)
-
-const loading = ref(false)
-
-const getTableDet = async () => {
-  loading.value = true
-  const res = await getTableDetApi({
-    params: {
-      id: props.currentRow?.id as string
-    }
-  }).finally(() => {
-    loading.value = false
-  })
-  if (res) {
-    currentRow.value = res.data
-  }
-}
-
-watch(
-  () => props.currentRow,
-  () => {
-    getTableDet()
-  },
-  {
-    deep: true,
-    immediate: true
-  }
-)
-
 const schema = reactive<DescriptionsSchema[]>([
   {
     field: 'title',
@@ -75,7 +45,7 @@ const schema = reactive<DescriptionsSchema[]>([
 </script>
 
 <template>
-  <Descriptions v-loading="loading" :schema="schema" :data="currentRow || {}">
+  <Descriptions :schema="schema" :data="currentRow || {}">
     <template #importance="{ row }: { row: TableData }">
       <ElTag :type="row.importance === 1 ? 'success' : row.importance === 2 ? 'warning' : 'danger'">
         {{

+ 54 - 0
src/views/Example/Page/ExampleAdd.vue

@@ -0,0 +1,54 @@
+<script setup lang="ts">
+import Write from './components/Write.vue'
+import { ContentWrap } from '@/components/ContentWrap'
+import { ref, unref } from 'vue'
+import { ElButton } from 'element-plus'
+import { useI18n } from '@/hooks/web/useI18n'
+import { useRouter } from 'vue-router'
+import { saveTableApi } from '@/api/table'
+import { TableData } from '@/api/table/types'
+import { useEmitt } from '@/hooks/web/useEmitt'
+
+const { emitter } = useEmitt()
+
+const { push } = useRouter()
+
+const { t } = useI18n()
+
+const writeRef = ref<ComponentRef<typeof Write>>()
+
+const loading = ref(false)
+
+const save = async () => {
+  const write = unref(writeRef)
+  const validate = await write?.elFormRef?.validate()?.catch(() => {})
+  if (validate) {
+    loading.value = true
+    const data = (await write?.getFormData()) as TableData
+    const res = await saveTableApi({
+      data
+    })
+      .catch(() => {})
+      .finally(() => {
+        loading.value = false
+      })
+    if (res) {
+      emitter.emit('getList', 'add')
+      push('/example/example-page')
+    }
+  }
+}
+</script>
+
+<template>
+  <ContentWrap :title="t('exampleDemo.add')">
+    <Write ref="writeRef" />
+
+    <div class="text-center">
+      <ElButton type="primary" :loading="loading" @click="save">
+        {{ t('exampleDemo.save') }}
+      </ElButton>
+      <ElButton @click="push('/example/example-page')">{{ t('dialogDemo.close') }}</ElButton>
+    </div>
+  </ContentWrap>
+</template>

+ 41 - 0
src/views/Example/Page/ExampleDetail.vue

@@ -0,0 +1,41 @@
+<script setup lang="ts">
+import Detail from './components/Detail.vue'
+import { ContentWrap } from '@/components/ContentWrap'
+import { ref } from 'vue'
+import { ElButton } from 'element-plus'
+import { useI18n } from '@/hooks/web/useI18n'
+import { useRouter, useRoute } from 'vue-router'
+import { getTableDetApi } from '@/api/table'
+import { TableData } from '@/api/table/types'
+
+const { push } = useRouter()
+
+const { query } = useRoute()
+
+const { t } = useI18n()
+
+const currentRow = ref<Nullable<TableData>>(null)
+
+const getTableDet = async () => {
+  const res = await getTableDetApi({
+    params: {
+      id: query.id as string
+    }
+  })
+  if (res) {
+    currentRow.value = res.data
+  }
+}
+
+getTableDet()
+</script>
+
+<template>
+  <ContentWrap :title="t('exampleDemo.detail')">
+    <Detail :current-row="currentRow" />
+
+    <div class="text-center">
+      <ElButton @click="push('/example/example-page')">{{ t('dialogDemo.close') }}</ElButton>
+    </div>
+  </ContentWrap>
+</template>

+ 71 - 0
src/views/Example/Page/ExampleEdit.vue

@@ -0,0 +1,71 @@
+<script setup lang="ts">
+import Write from './components/Write.vue'
+import { ContentWrap } from '@/components/ContentWrap'
+import { ref, unref } from 'vue'
+import { ElButton } from 'element-plus'
+import { useI18n } from '@/hooks/web/useI18n'
+import { useRouter, useRoute } from 'vue-router'
+import { saveTableApi, getTableDetApi } from '@/api/table'
+import { TableData } from '@/api/table/types'
+import { useEmitt } from '@/hooks/web/useEmitt'
+
+const { emitter } = useEmitt()
+
+const { push } = useRouter()
+
+const { query } = useRoute()
+
+const { t } = useI18n()
+
+const currentRow = ref<Nullable<TableData>>(null)
+
+const getTableDet = async () => {
+  const res = await getTableDetApi({
+    params: {
+      id: query.id as string
+    }
+  })
+  if (res) {
+    currentRow.value = res.data
+  }
+}
+
+getTableDet()
+
+const writeRef = ref<ComponentRef<typeof Write>>()
+
+const loading = ref(false)
+
+const save = async () => {
+  const write = unref(writeRef)
+  const validate = await write?.elFormRef?.validate()?.catch(() => {})
+  if (validate) {
+    loading.value = true
+    const data = (await write?.getFormData()) as TableData
+    const res = await saveTableApi({
+      data
+    })
+      .catch(() => {})
+      .finally(() => {
+        loading.value = false
+      })
+    if (res) {
+      emitter.emit('getList', 'edit')
+      push('/example/example-page')
+    }
+  }
+}
+</script>
+
+<template>
+  <ContentWrap :title="t('exampleDemo.edit')">
+    <Write ref="writeRef" :current-row="currentRow" />
+
+    <div class="text-center">
+      <ElButton type="primary" :loading="loading" @click="save">
+        {{ t('exampleDemo.save') }}
+      </ElButton>
+      <ElButton @click="push('/example/example-page')">{{ t('dialogDemo.close') }}</ElButton>
+    </div>
+  </ContentWrap>
+</template>

+ 166 - 0
src/views/Example/Page/ExamplePage.vue

@@ -0,0 +1,166 @@
+<script setup lang="ts">
+import { ContentWrap } from '@/components/ContentWrap'
+import { Search } from '@/components/Search'
+import { useI18n } from '@/hooks/web/useI18n'
+import { ElButton, ElTag } from 'element-plus'
+import { Table } from '@/components/Table'
+import { getTableListApi, delTableListApi } from '@/api/table'
+import { useTable } from '@/hooks/web/useTable'
+import { TableData } from '@/api/table/types'
+import { h, reactive, ref } from 'vue'
+import { useRouter } from 'vue-router'
+import { useEmitt } from '@/hooks/web/useEmitt'
+
+defineOptions({
+  name: 'ExamplePage'
+})
+
+const { push } = useRouter()
+
+const { register, tableObject, methods } = useTable<
+  {
+    total: number
+    list: TableData[]
+  },
+  TableData
+>({
+  getListApi: getTableListApi,
+  delListApi: delTableListApi,
+  response: {
+    list: 'list',
+    total: 'total'
+  }
+})
+
+const { getList, setSearchParmas } = methods
+
+getList()
+
+useEmitt({
+  name: 'getList',
+  callback: (type: string) => {
+    if (type === 'add') {
+      tableObject.currentPage = 1
+    }
+    getList()
+  }
+})
+
+const { t } = useI18n()
+
+const searchData: FormSchema[] = [
+  {
+    label: t('exampleDemo.title'),
+    value: '',
+    component: 'Input',
+    field: 'title'
+  }
+]
+
+const columns = reactive<TableColumn[]>([
+  {
+    field: 'index',
+    label: t('tableDemo.index'),
+    type: 'index'
+  },
+  {
+    field: 'title',
+    label: t('tableDemo.title')
+  },
+  {
+    field: 'author',
+    label: t('tableDemo.author')
+  },
+  {
+    field: 'display_time',
+    label: t('tableDemo.displayTime')
+  },
+  {
+    field: 'importance',
+    label: t('tableDemo.importance'),
+    formatter: (_: Recordable, __: TableColumn, cellValue: number) => {
+      return h(
+        ElTag,
+        {
+          type: cellValue === 1 ? 'success' : cellValue === 2 ? 'warning' : 'danger'
+        },
+        () =>
+          cellValue === 1
+            ? t('tableDemo.important')
+            : cellValue === 2
+            ? t('tableDemo.good')
+            : t('tableDemo.commonly')
+      )
+    }
+  },
+  {
+    field: 'pageviews',
+    label: t('tableDemo.pageviews')
+  },
+  {
+    field: 'action',
+    width: '260px',
+    label: t('tableDemo.action')
+  }
+])
+
+const AddAction = () => {
+  push('/example/example-add')
+}
+
+const delLoading = ref(false)
+
+const delData = async (row: TableData | null, multiple: boolean) => {
+  tableObject.currentRow = row
+  const { delList, getSelections } = methods
+  const selections = await getSelections()
+  delLoading.value = true
+  await delList(
+    multiple ? selections.map((v) => v.id) : [tableObject.currentRow?.id as string],
+    multiple
+  ).finally(() => {
+    delLoading.value = false
+  })
+}
+
+const action = (row: TableData, type: string) => {
+  push(`/example/example-${type}?id=${row.id}`)
+}
+</script>
+
+<template>
+  <ContentWrap>
+    <Search :schema="searchData" @search="setSearchParmas" @reset="setSearchParmas" />
+
+    <div class="mb-10px">
+      <ElButton type="primary" @click="AddAction">{{ t('exampleDemo.add') }}</ElButton>
+      <ElButton :loading="delLoading" type="danger" @click="delData(null, true)">
+        {{ t('exampleDemo.del') }}
+      </ElButton>
+    </div>
+
+    <Table
+      v-model:pageSize="tableObject.pageSize"
+      v-model:currentPage="tableObject.currentPage"
+      :columns="columns"
+      :data="tableObject.tableList"
+      :loading="tableObject.loading"
+      :pagination="{
+        total: tableObject.total
+      }"
+      @register="register"
+    >
+      <template #action="{ row }">
+        <ElButton type="primary" @click="action(row, 'edit')">
+          {{ t('exampleDemo.edit') }}
+        </ElButton>
+        <ElButton type="success" @click="action(row, 'detail')">
+          {{ t('exampleDemo.detail') }}
+        </ElButton>
+        <ElButton type="danger" @click="delData(row, false)">
+          {{ t('exampleDemo.del') }}
+        </ElButton>
+      </template>
+    </Table>
+  </ContentWrap>
+</template>

+ 65 - 0
src/views/Example/Page/components/Detail.vue

@@ -0,0 +1,65 @@
+<script setup lang="ts">
+import { PropType, reactive } from 'vue'
+import type { TableData } from '@/api/table/types'
+import { Descriptions } from '@/components/Descriptions'
+import { useI18n } from '@/hooks/web/useI18n'
+import { ElTag } from 'element-plus'
+
+const { t } = useI18n()
+
+defineProps({
+  currentRow: {
+    type: Object as PropType<Nullable<TableData>>,
+    default: () => null
+  }
+})
+
+const schema = reactive<DescriptionsSchema[]>([
+  {
+    field: 'title',
+    label: t('exampleDemo.title'),
+    span: 24
+  },
+  {
+    field: 'author',
+    label: t('exampleDemo.author')
+  },
+  {
+    field: 'display_time',
+    label: t('exampleDemo.displayTime')
+  },
+  {
+    field: 'importance',
+    label: t('exampleDemo.importance')
+  },
+  {
+    field: 'pageviews',
+    label: t('exampleDemo.pageviews')
+  },
+  {
+    field: 'content',
+    label: t('exampleDemo.content'),
+    span: 24
+  }
+])
+</script>
+
+<template>
+  <Descriptions :schema="schema" :data="currentRow || {}">
+    <template #importance="{ row }: { row: TableData }">
+      <ElTag :type="row.importance === 1 ? 'success' : row.importance === 2 ? 'warning' : 'danger'">
+        {{
+          row.importance === 1
+            ? t('tableDemo.important')
+            : row.importance === 2
+            ? t('tableDemo.good')
+            : t('tableDemo.commonly')
+        }}
+      </ElTag>
+    </template>
+
+    <template #content="{ row }: { row: TableData }">
+      <div v-html="row.content"></div>
+    </template>
+  </Descriptions>
+</template>

+ 144 - 0
src/views/Example/Page/components/Write.vue

@@ -0,0 +1,144 @@
+<script setup lang="ts">
+import { Form } from '@/components/Form'
+import { useForm } from '@/hooks/web/useForm'
+import { PropType, reactive, watch } from 'vue'
+import { TableData } from '@/api/table/types'
+import { useI18n } from '@/hooks/web/useI18n'
+import { required } from '@/utils/formRules'
+import { IDomEditor } from '@wangeditor/editor'
+
+const props = defineProps({
+  currentRow: {
+    type: Object as PropType<Nullable<TableData>>,
+    default: () => null
+  }
+})
+
+const { t } = useI18n()
+
+const schema = reactive<FormSchema[]>([
+  {
+    field: 'title',
+    label: t('exampleDemo.title'),
+    component: 'Input',
+    formItemProps: {
+      rules: [required]
+    },
+    colProps: {
+      span: 24
+    }
+  },
+  {
+    field: 'author',
+    label: t('exampleDemo.author'),
+    component: 'Input',
+    formItemProps: {
+      rules: [required]
+    }
+  },
+  {
+    field: 'display_time',
+    label: t('exampleDemo.displayTime'),
+    component: 'DatePicker',
+    componentProps: {
+      type: 'datetime',
+      valueFormat: 'YYYY-MM-DD HH:mm:ss'
+    },
+    formItemProps: {
+      rules: [required]
+    }
+  },
+  {
+    field: 'importance',
+    label: t('exampleDemo.importance'),
+    component: 'Select',
+    formItemProps: {
+      rules: [required]
+    },
+    componentProps: {
+      options: [
+        {
+          label: '重要',
+          value: 3
+        },
+        {
+          label: '良好',
+          value: 2
+        },
+        {
+          label: '一般',
+          value: 1
+        }
+      ]
+    }
+  },
+  {
+    field: 'pageviews',
+    label: t('exampleDemo.pageviews'),
+    component: 'InputNumber',
+    value: 0,
+    formItemProps: {
+      rules: [required]
+    }
+  },
+  {
+    field: 'content',
+    component: 'Editor',
+    colProps: {
+      span: 24
+    },
+    componentProps: {
+      defaultHtml: '',
+      onChange: (edit: IDomEditor) => {
+        const { setValues } = methods
+        setValues({
+          content: edit.getHtml()
+        })
+      }
+    },
+    label: t('exampleDemo.content')
+  }
+])
+
+const rules = reactive({
+  title: [required],
+  author: [required],
+  importance: [required],
+  pageviews: [required],
+  display_time: [required],
+  content: [required]
+})
+
+const { register, methods, elFormRef } = useForm({
+  schema
+})
+
+watch(
+  () => props.currentRow,
+  (currentRow) => {
+    if (!currentRow) return
+    const { setValues, setSchema } = methods
+    setValues(currentRow)
+    setSchema([
+      {
+        field: 'content',
+        path: 'componentProps.defaultHtml',
+        value: currentRow.content
+      }
+    ])
+  },
+  {
+    deep: true,
+    immediate: true
+  }
+)
+
+defineExpose({
+  elFormRef,
+  getFormData: methods.getFormData
+})
+</script>
+
+<template>
+  <Form :rules="rules" @register="register" />
+</template>