瀏覽代碼

feat: 用户列表重构

kailong321200875 1 年之前
父節點
當前提交
755cea0990

+ 105 - 0
mock/department/index.ts

@@ -0,0 +1,105 @@
+import config from '@/config/axios/config'
+import { MockMethod } from 'vite-plugin-mock'
+import { toAnyString } from '@/utils'
+import Mock from 'mockjs'
+
+const { code } = config
+
+const departmentList: any = []
+
+const citys = ['厦门总公司', '北京分公司', '上海分公司', '福州分公司', '深圳分公司', '杭州分公司']
+
+for (let i = 0; i < 5; i++) {
+  departmentList.push({
+    // 部门名称
+    departmentName: citys[i],
+    id: toAnyString(),
+    children: [
+      {
+        // 部门名称
+        departmentName: '研发部',
+        id: toAnyString()
+      },
+      {
+        // 部门名称
+        departmentName: '产品部',
+        id: toAnyString()
+      },
+      {
+        // 部门名称
+        departmentName: '运营部',
+        id: toAnyString()
+      },
+      {
+        // 部门名称
+        departmentName: '市场部',
+        id: toAnyString()
+      },
+      {
+        // 部门名称
+        departmentName: '销售部',
+        id: toAnyString()
+      },
+      {
+        // 部门名称
+        departmentName: '客服部',
+        id: toAnyString()
+      }
+    ]
+  })
+}
+
+export default [
+  // 列表接口
+  {
+    url: '/department/list',
+    method: 'get',
+    response: () => {
+      return {
+        data: {
+          code: code,
+          data: {
+            list: departmentList
+          }
+        }
+      }
+    }
+  },
+  {
+    url: '/department/users',
+    method: 'get',
+    timeout: 1000,
+    response: ({ query }) => {
+      const { pageSize } = query
+      // 根据pageSize来创建数据
+      const mockList: any = []
+      for (let i = 0; i < pageSize; i++) {
+        mockList.push(
+          Mock.mock({
+            // 用户名
+            username: '@cname',
+            // 账号
+            account: '@first',
+            // 邮箱
+            email: '@EMAIL',
+            // 创建时间
+            createTime: '@datetime',
+            // 角色
+            role: '@first',
+            // 用户id
+            id: toAnyString()
+          })
+        )
+      }
+      return {
+        data: {
+          code: code,
+          data: {
+            total: 100,
+            list: mockList
+          }
+        }
+      }
+    }
+  }
+] as MockMethod[]

+ 10 - 0
src/api/department/index.ts

@@ -0,0 +1,10 @@
+import request from '@/config/axios'
+import { DepartmentListResponse, DepartmentUserParams, DepartmentUserResponse } from './types'
+
+export const getDepartmentApi = () => {
+  return request.get<DepartmentListResponse>({ url: '/department/list' })
+}
+
+export const getUserByIdApi = (params: DepartmentUserParams) => {
+  return request.get<DepartmentUserResponse>({ url: '/department/users', params })
+}

+ 31 - 0
src/api/department/types.ts

@@ -0,0 +1,31 @@
+export interface DepartmentItem {
+  id: string
+  departmentName: string
+  children?: DepartmentItem[]
+}
+
+export interface DepartmentListResponse {
+  list: DepartmentItem[]
+}
+
+export interface DepartmentUserParams {
+  pageSize: number
+  pageIndex: number
+  id: string
+  username?: string
+  account?: string
+}
+
+export interface DepartmentUserItem {
+  id: string
+  username: string
+  account: string
+  email: string
+  createTime: string
+  role: string
+}
+
+export interface DepartmentUserResponse {
+  list: DepartmentUserItem[]
+  total: number
+}

+ 1 - 1
src/components/Form/src/Form.vue

@@ -255,7 +255,7 @@ export default defineComponent({
     const renderFormItem = (item: FormSchema) => {
       // 如果有optionApi,优先使用optionApi
       if (item.optionApi) {
-        // 内部自动调用接口,不影响其渲染
+        // 内部自动调用接口,不影响其渲染
         getOptions(item.optionApi, item)
       }
       const formItemSlots: Recordable = {

+ 0 - 3
src/components/Sticky/index.ts

@@ -1,3 +0,0 @@
-import Sticky from './src/Sticky.vue'
-
-export { Sticky }

+ 0 - 141
src/components/Sticky/src/Sticky.vue

@@ -1,141 +0,0 @@
-<script setup lang="ts">
-import { propTypes } from '@/utils/propTypes'
-import { ref, onMounted, onActivated, shallowRef } from 'vue'
-import { useEventListener, useWindowSize, isClient } from '@vueuse/core'
-import type { CSSProperties } from 'vue'
-const props = defineProps({
-  // 距离顶部或者底部的距离(单位px)
-  offset: propTypes.number.def(0),
-  // 设置元素的堆叠顺序
-  zIndex: propTypes.number.def(999),
-  // 设置指定的class
-  className: propTypes.string.def(''),
-  // 定位方式,默认为(top),表示距离顶部位置,可以设置为top或者bottom
-  position: {
-    type: String,
-    validator: function (value: string) {
-      return ['top', 'bottom'].indexOf(value) !== -1
-    },
-    default: 'top'
-  }
-})
-const width = ref('auto' as string)
-const height = ref('auto' as string)
-const isSticky = ref(false)
-const refSticky = shallowRef<HTMLElement>()
-const scrollContainer = shallowRef<HTMLElement | Window>()
-const { height: windowHeight } = useWindowSize()
-onMounted(() => {
-  height.value = refSticky.value?.getBoundingClientRect().height + 'px'
-
-  scrollContainer.value = getScrollContainer(refSticky.value!, true)
-  useEventListener(scrollContainer, 'scroll', handleScroll)
-  useEventListener('resize', handleReize)
-  handleScroll()
-})
-onActivated(() => {
-  handleScroll()
-})
-
-const camelize = (str: string): string => {
-  return str.replace(/-(\w)/g, (_, c) => (c ? c.toUpperCase() : ''))
-}
-
-const getStyle = (element: HTMLElement, styleName: keyof CSSProperties): string => {
-  if (!isClient || !element || !styleName) return ''
-
-  let key = camelize(styleName)
-  if (key === 'float') key = 'cssFloat'
-  try {
-    const style = element.style[styleName]
-    if (style) return style
-    const computed = document.defaultView?.getComputedStyle(element, '')
-    return computed ? computed[styleName] : ''
-  } catch {
-    return element.style[styleName]
-  }
-}
-const isScroll = (el: HTMLElement, isVertical?: boolean): boolean => {
-  if (!isClient) return false
-  const key = (
-    {
-      undefined: 'overflow',
-      true: 'overflow-y',
-      false: 'overflow-x'
-    } as const
-  )[String(isVertical)]!
-  const overflow = getStyle(el, key)
-  return ['scroll', 'auto', 'overlay'].some((s) => overflow.includes(s))
-}
-
-const getScrollContainer = (
-  el: HTMLElement,
-  isVertical: boolean
-): Window | HTMLElement | undefined => {
-  if (!isClient) return
-  let parent = el
-  while (parent) {
-    if ([window, document, document.documentElement].includes(parent)) return window
-    if (isScroll(parent, isVertical)) return parent
-    parent = parent.parentNode as HTMLElement
-  }
-  return parent
-}
-
-const handleScroll = () => {
-  width.value = refSticky.value!.getBoundingClientRect().width! + 'px'
-  if (props.position === 'top') {
-    const offsetTop = refSticky.value?.getBoundingClientRect().top
-    if (offsetTop !== undefined && offsetTop < props.offset) {
-      sticky()
-      return
-    }
-    reset()
-  } else {
-    const offsetBottom = refSticky.value?.getBoundingClientRect().bottom
-
-    if (offsetBottom !== undefined && offsetBottom > windowHeight.value - props.offset) {
-      sticky()
-      return
-    }
-    reset()
-  }
-}
-const handleReize = () => {
-  if (isSticky.value && refSticky.value) {
-    width.value = refSticky.value.getBoundingClientRect().width + 'px'
-  }
-}
-const sticky = () => {
-  if (isSticky.value) {
-    return
-  }
-  isSticky.value = true
-}
-const reset = () => {
-  if (!isSticky.value) {
-    return
-  }
-  width.value = 'auto'
-  isSticky.value = false
-}
-</script>
-<template>
-  <div :style="{ height: height, zIndex: zIndex }" ref="refSticky">
-    <div
-      :class="className"
-      :style="{
-        top: position === 'top' ? offset + 'px' : '',
-        bottom: position !== 'top' ? offset + 'px' : '',
-        zIndex: zIndex,
-        position: isSticky ? 'fixed' : 'static',
-        width: width,
-        height: height
-      }"
-    >
-      <slot>
-        <div>sticky</div>
-      </slot>
-    </div>
-  </div>
-</template>

+ 6 - 5
src/components/TagsView/src/TagsView.vue

@@ -75,7 +75,7 @@ const closeAllTags = () => {
   toLastView()
 }
 
-// 关闭其
+// 关闭其
 const closeOthersTags = () => {
   tagsViewStore.delOthersViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
 }
@@ -482,7 +482,8 @@ watch(
 
   &__tool {
     position: relative;
-    &:before {
+
+    &::before {
       position: absolute;
       top: 1px;
       left: 0;
@@ -493,14 +494,14 @@ watch(
     }
 
     &--first {
-      &:before {
+      &::before {
         position: absolute;
         top: 1px;
         left: 0;
         width: 100%;
         height: calc(~'100% - 1px');
-        border-left: none;
         border-right: 1px solid var(--el-border-color);
+        border-left: none;
         content: '';
       }
     }
@@ -553,7 +554,7 @@ watch(
   .@{prefix-cls} {
     &__tool {
       &--first {
-        &:after {
+        &::after {
           display: none;
         }
       }

+ 8 - 1
src/locales/en.ts

@@ -487,7 +487,14 @@ export default {
     role: 'Role',
     remark: 'Remark',
     remarkMessage1: 'Back end control routing permission',
-    remarkMessage2: 'Front end control routing permission'
+    remarkMessage2: 'Front end control routing permission',
+    // 部门列表
+    departmentList: 'Department list',
+    // 搜索部门
+    searchDepartment: 'Search department',
+    account: 'Account',
+    email: 'Email',
+    createTime: 'Create time'
   },
   inputPasswordDemo: {
     title: 'InputPassword',

+ 9 - 3
src/locales/zh-CN.ts

@@ -17,7 +17,7 @@ export default {
     closeTab: '关闭标签页',
     closeTheLeftTab: '关闭左侧标签页',
     closeTheRightTab: '关闭右侧标签页',
-    closeOther: '关闭其标签页',
+    closeOther: '关闭其标签页',
     closeAll: '关闭全部标签页',
     prevLabel: '上一步',
     nextLabel: '下一步',
@@ -106,7 +106,7 @@ export default {
     register: '注册',
     checkPassword: '确认密码',
     login: '登录',
-    otherLogin: '其登录方式',
+    otherLogin: '其登录方式',
     remember: '记住我',
     hasUser: '已有账号?去登录',
     forgetPassword: '忘记密码',
@@ -480,7 +480,13 @@ export default {
     role: '角色',
     remark: '备注',
     remarkMessage1: '后端控制路由权限',
-    remarkMessage2: '前端控制路由权限'
+    remarkMessage2: '前端控制路由权限',
+    // 部门列表
+    departmentList: '部门列表',
+    searchDepartment: '搜索部门',
+    account: '账号',
+    email: '邮箱',
+    createTime: '创建时间'
   },
   inputPasswordDemo: {
     title: '密码输入框',

+ 37 - 45
src/router/index.ts

@@ -311,14 +311,6 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
           title: t('router.inputPassword')
         }
       }
-      // {
-      //   path: 'sticky',
-      //   component: () => import('@/views/Components/Sticky.vue'),
-      //   name: 'Sticky',
-      //   meta: {
-      //     title: t('router.sticky')
-      //   }
-      // }
     ]
   },
   {
@@ -339,15 +331,15 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
         meta: {
           title: 'useWatermark'
         }
-      },
-      {
-        path: 'useCrudSchemas',
-        component: () => import('@/views/hooks/useCrudSchemas.vue'),
-        name: 'UseCrudSchemas',
-        meta: {
-          title: 'useCrudSchemas'
-        }
       }
+      // {
+      //   path: 'useCrudSchemas',
+      //   component: () => import('@/views/hooks/useCrudSchemas.vue'),
+      //   name: 'UseCrudSchemas',
+      //   meta: {
+      //     title: 'useCrudSchemas'
+      //   }
+      // }
     ]
   },
   {
@@ -513,36 +505,36 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
         }
       }
     ]
+  },
+  {
+    path: '/authorization',
+    component: Layout,
+    redirect: '/authorization/user',
+    name: 'Authorization',
+    meta: {
+      title: t('router.authorization'),
+      icon: 'eos-icons:role-binding',
+      alwaysShow: true
+    },
+    children: [
+      {
+        path: 'user',
+        component: () => import('@/views/Authorization/User.vue'),
+        name: 'User',
+        meta: {
+          title: t('router.user')
+        }
+      },
+      {
+        path: 'role',
+        component: () => import('@/views/Authorization/Role.vue'),
+        name: 'Role',
+        meta: {
+          title: t('router.role')
+        }
+      }
+    ]
   }
-  // {
-  //   path: '/authorization',
-  //   component: Layout,
-  //   redirect: '/authorization/user',
-  //   name: 'Authorization',
-  //   meta: {
-  //     title: t('router.authorization'),
-  //     icon: 'eos-icons:role-binding',
-  //     alwaysShow: true
-  //   },
-  //   children: [
-  //     {
-  //       path: 'user',
-  //       component: () => import('@/views/Authorization/User.vue'),
-  //       name: 'User',
-  //       meta: {
-  //         title: t('router.user')
-  //       }
-  //     },
-  //     {
-  //       path: 'role',
-  //       component: () => import('@/views/Authorization/Role.vue'),
-  //       name: 'Role',
-  //       meta: {
-  //         title: t('router.role')
-  //       }
-  //     }
-  //   ]
-  // }
 ]
 
 const router = createRouter({

+ 2 - 2
src/store/modules/app.ts

@@ -37,7 +37,7 @@ interface AppState {
 export const useAppStore = defineStore('app', {
   state: (): AppState => {
     return {
-      userInfo: 'userInfo', // 登录信息存储字段-建议每个项目换一个字段,避免与其项目冲突
+      userInfo: 'userInfo', // 登录信息存储字段-建议每个项目换一个字段,避免与其项目冲突
       sizeMap: ['default', 'large', 'small'],
       mobile: false, // 是否是移动端
       title: import.meta.env.VITE_APP_TITLE, // 标题
@@ -225,7 +225,7 @@ export const useAppStore = defineStore('app', {
     },
     setLayout(layout: LayoutType) {
       if (this.mobile && layout !== 'classic') {
-        ElMessage.warning('移动端模式下不支持切换其布局')
+        ElMessage.warning('移动端模式下不支持切换其布局')
         return
       }
       this.layout = layout

+ 2 - 2
src/store/modules/tagsView.ts

@@ -87,12 +87,12 @@ export const useTagsViewStore = defineStore('tagsView', {
       // const affixTags = this.visitedViews.filter((tag) => tag.meta.affix)
       this.visitedViews = []
     },
-    // 删除其
+    // 删除其
     delOthersViews(view: RouteLocationNormalizedLoaded) {
       this.delOthersVisitedViews(view)
       this.addCachedView()
     },
-    // 删除其tag
+    // 删除其tag
     delOthersVisitedViews(view: RouteLocationNormalizedLoaded) {
       this.visitedViews = this.visitedViews.filter((v) => {
         return v?.meta?.affix || v.path === view.path

+ 143 - 52
src/views/Authorization/User.vue

@@ -1,20 +1,35 @@
-<script setup lang="ts">
+<script setup lang="tsx">
 import { ContentWrap } from '@/components/ContentWrap'
 import { useI18n } from '@/hooks/web/useI18n'
-import { Table } from '@/components/Table'
-import { getUserListApi } from '@/api/login'
-import { UserType } from '@/api/login/types'
-import { ref, h } from 'vue'
-import { ElButton } from 'element-plus'
-import { TableColumn, TableSlotDefault } from '@/types/table'
-
-interface Params {
-  pageIndex?: number
-  pageSize?: number
-}
+import { Table, TableColumn } from '@/components/Table'
+import { ref, unref, nextTick, watch } from 'vue'
+import { ElButton, ElTree, ElInput, ElDivider } from 'element-plus'
+import { getDepartmentApi, getUserByIdApi } from '@/api/department'
+import type { DepartmentItem, DepartmentUserItem } from '@/api/department/types'
+import { useTable } from '@/hooks/web/useTable'
+import { Search } from '@/components/Search'
+import { FormSchema } from '@/components/Form'
 
 const { t } = useI18n()
 
+const { tableRegister, tableState, tableMethods } = useTable({
+  fetchDataApi: async () => {
+    const { pageSize, currentPage } = tableState
+    const res = await getUserByIdApi({
+      id: unref(currentNodeKey),
+      pageIndex: unref(currentPage),
+      pageSize: unref(pageSize),
+      ...unref(searchParams)
+    })
+    return {
+      list: res.data.list || [],
+      total: res.data.total || 0
+    }
+  }
+})
+const { total, loading, dataList, pageSize, currentPage } = tableState
+const { getList } = tableMethods
+
 const columns: TableColumn[] = [
   {
     field: 'index',
@@ -26,65 +41,141 @@ const columns: TableColumn[] = [
     label: t('userDemo.username')
   },
   {
-    field: 'password',
-    label: t('userDemo.password')
+    field: 'account',
+    label: t('userDemo.account')
   },
   {
     field: 'role',
     label: t('userDemo.role')
   },
   {
-    field: 'remark',
-    label: t('userDemo.remark'),
-    formatter: (row: UserType) => {
-      return h(
-        'span',
-        row.username === 'admin' ? t('userDemo.remarkMessage1') : t('userDemo.remarkMessage2')
-      )
-    }
+    field: 'email',
+    label: t('userDemo.email')
+  },
+  {
+    field: 'createTime',
+    label: t('userDemo.createTime')
   },
   {
     field: 'action',
-    label: t('userDemo.action')
+    label: t('userDemo.action'),
+    slots: {
+      default: (data) => {
+        return (
+          <ElButton type="primary" onClick={() => actionFn(data[0].row)}>
+            {t('tableDemo.action')}
+          </ElButton>
+        )
+      }
+    }
+  }
+]
+
+const searchSchema: FormSchema[] = [
+  {
+    field: 'username',
+    label: t('userDemo.username'),
+    component: 'Input'
+  },
+  {
+    field: 'account',
+    label: t('userDemo.account'),
+    component: 'Input'
   }
 ]
 
-const loading = ref(true)
+const searchParams = ref({})
+const setSearchParams = (params: any) => {
+  currentPage.value = 1
+  searchParams.value = params
+  getList()
+}
 
-let tableDataList = ref<UserType[]>([])
+const actionFn = (data: DepartmentUserItem) => {
+  console.log(data)
+}
 
-const getTableList = async (params?: Params) => {
-  const res = await getUserListApi({
-    params: params || {
-      pageIndex: 1,
-      pageSize: 10
-    }
-  })
-  // .catch(() => {})
-  // .finally(() => {
-  //   loading.value = false
-  // })
-  if (res) {
-    tableDataList.value = res.data.list
-    loading.value = false
-  }
+const treeEl = ref<typeof ElTree>()
+
+const currentNodeKey = ref('')
+const departmentList = ref<DepartmentItem[]>([])
+const fetchDepartment = async () => {
+  const res = await getDepartmentApi()
+  departmentList.value = res.data.list
+  currentNodeKey.value =
+    (res.data.list[0] && res.data.list[0]?.children && res.data.list[0].children[0].id) || ''
+  await nextTick()
+  unref(treeEl)?.setCurrentKey(currentNodeKey.value)
 }
+fetchDepartment()
+
+const currentDepartment = ref('')
+watch(
+  () => currentDepartment.value,
+  (val) => {
+    unref(treeEl)!.filter(val)
+  }
+)
 
-getTableList()
+const currentChange = (data: DepartmentItem) => {
+  if (data.children) return
+  currentNodeKey.value = data.id
+  currentPage.value = 1
+  getList()
+}
 
-const actionFn = (data: TableSlotDefault) => {
-  console.log(data)
+const filterNode = (value: string, data: DepartmentItem) => {
+  if (!value) return true
+  return data.departmentName.includes(value)
 }
 </script>
 
 <template>
-  <ContentWrap :title="t('userDemo.title')" :message="t('userDemo.message')">
-    <Table :columns="columns" :data="tableDataList" :loading="loading" :selection="false">
-      <template #action="data">
-        <ElButton type="primary" @click="actionFn(data as TableSlotDefault)">
-          {{ t('tableDemo.action') }}
-        </ElButton>
-      </template>
-    </Table>
-  </ContentWrap>
+  <div class="flex w-100% h-100%">
+    <ContentWrap class="flex-1">
+      <div class="flex justify-center items-center">
+        <div class="flex-1">{{ t('userDemo.departmentList') }}</div>
+        <ElInput
+          v-model="currentDepartment"
+          class="flex-[2]"
+          :placeholder="t('userDemo.searchDepartment')"
+          clearable
+        />
+      </div>
+      <ElDivider />
+      <ElTree
+        ref="treeEl"
+        :data="departmentList"
+        default-expand-all
+        node-key="id"
+        :current-node-key="currentNodeKey"
+        :props="{
+          label: 'departmentName'
+        }"
+        :filter-node-method="filterNode"
+        @current-change="currentChange"
+      />
+    </ContentWrap>
+    <ContentWrap class="flex-[2] ml-20px">
+      <Search
+        :schema="searchSchema"
+        @reset="setSearchParams"
+        @search="setSearchParams"
+        :search-loading="loading"
+      />
+      <div>
+        <Table
+          v-model:current-page="currentPage"
+          v-model:page-size="pageSize"
+          :columns="columns"
+          :data="dataList"
+          :loading="loading"
+          @register="tableRegister"
+          :pagination="{
+            total
+          }"
+        />
+      </div>
+    </ContentWrap>
+  </div>
 </template>

+ 0 - 62
src/views/Components/Sticky.vue

@@ -1,62 +0,0 @@
-<script setup lang="ts">
-import { ContentWrap } from '@/components/ContentWrap'
-import { useI18n } from '@/hooks/web/useI18n'
-import { Sticky } from '@/components/Sticky'
-import { ElAffix } from 'element-plus'
-
-const { t } = useI18n()
-</script>
-
-<template>
-  <ContentWrap :title="t('stickyDemo.sticky')">
-    <Sticky :offset="90">
-      <div style="padding: 10px; background-color: lightblue"> Sticky 距离顶部90px </div>
-    </Sticky>
-
-    <p style="margin: 80px">Content</p>
-    <p style="margin: 80px">Content</p>
-
-    <el-affix :offset="150">
-      <div style="padding: 10px; background-color: lightblue">Affix 距离顶部150px </div>
-    </el-affix>
-    <p style="margin: 80px">Content</p>
-    <p style="margin: 80px">Content</p>
-    <p style="margin: 80px">Content</p>
-    <p style="margin: 80px">Content</p>
-    <p style="margin: 80px">Content</p>
-    <p style="margin: 80px">Content</p>
-    <p style="margin: 80px">Content</p>
-    <p style="margin: 80px">Content</p>
-    <p style="margin: 80px">Content</p>
-    <p style="margin: 80px">Content</p>
-    <p style="margin: 80px">Content</p>
-    <p style="margin: 80px">Content</p>
-    <p style="margin: 80px">Content</p>
-    <p style="margin: 80px">Content</p>
-    <p style="margin: 80px">Content</p>
-    <p style="margin: 80px">Content</p>
-    <p style="margin: 80px">Content</p>
-    <p style="margin: 80px">Content</p>
-    <p style="margin: 80px">Content</p>
-    <p style="margin: 80px">Content</p>
-    <p style="margin: 80px">Content</p>
-    <p style="margin: 80px">Content</p>
-    <p style="margin: 80px">Content</p>
-    <p style="margin: 80px">Content</p>
-    <p style="margin: 80px">Content</p>
-    <p style="margin: 80px">Content</p>
-
-    <el-affix :offset="150" position="bottom">
-      <div style="padding: 10px; background-color: lightblue">Affix 距离底部150px </div>
-    </el-affix>
-
-    <p style="margin: 80px">Content</p>
-    <p style="margin: 80px">Content</p>
-
-    <Sticky :offset="90" position="bottom">
-      <div style="padding: 10px; background-color: lightblue"> Sticky 距离底部90px </div>
-    </Sticky>
-    <p style="margin: 80px">Content</p>
-    <p style="margin: 80px">Content</p>
-  </ContentWrap>
-</template>