Переглянути джерело

添加个人中心以及日志查询

18834741618 1 рік тому
батько
коміт
63a4c3af0d

+ 5 - 0
.gitignore

@@ -6,6 +6,11 @@ yarn-debug.log*
 yarn-error.log*
 pnpm-debug.log*
 lerna-debug.log*
+yarn.lock
+package-lock.json
+.gitignore
+settings.json
+auto-import.d.ts
 
 node_modules
 .DS_Store

+ 1 - 1
.vscode/settings.json

@@ -14,4 +14,4 @@
   },
   "editor.formatOnSave": true,
   "files.eol": "\n"
-}
+}

+ 1 - 0
package.json

@@ -21,6 +21,7 @@
     "nprogress": "^0.2.0",
     "pinia": "^2.0.29",
     "vue": "^3.2.45",
+    "vue-cropper": "^1.0.5",
     "vue-router": "^4.1.6",
     "vxe-table": "^4.3.9",
     "xe-utils": "^3.5.7"

+ 1 - 0
src/auto-import.d.ts

@@ -115,6 +115,7 @@ declare global {
   const useArrayFilter: typeof import('@vueuse/core')['useArrayFilter']
   const useArrayFind: typeof import('@vueuse/core')['useArrayFind']
   const useArrayFindIndex: typeof import('@vueuse/core')['useArrayFindIndex']
+  const useArrayFindLast: typeof import('@vueuse/core')['useArrayFindLast']
   const useArrayJoin: typeof import('@vueuse/core')['useArrayJoin']
   const useArrayMap: typeof import('@vueuse/core')['useArrayMap']
   const useArrayReduce: typeof import('@vueuse/core')['useArrayReduce']

+ 1 - 0
src/components.d.ts

@@ -9,6 +9,7 @@ declare module '@vue/runtime-core' {
   export interface GlobalComponents {
     AdvancedForm: typeof import('./components/form/AdvancedForm.vue')['default']
     BasicForm: typeof import('./components/form/BasicForm.vue')['default']
+    Cropper: typeof import('./components/avatar/cropper.vue')['default']
     DialogForm: typeof import('./components/form/DialogForm.vue')['default']
     ElDict: typeof import('./components/ElDict.vue')['default']
     ElEditor: typeof import('./components/ElEditor.vue')['default']

+ 6 - 1
src/components/GlobalHeader.vue

@@ -5,6 +5,7 @@ import { useThemeStore } from '@/stores/theme'
 import avatar from '@/assets/images/avatar.png'
 import { ElMessageBox } from 'element-plus'
 import { isAbsolutePath } from '@/utils/utils'
+import router from '@/router'
 
 const themeStore = useThemeStore()
 // 整体风格
@@ -69,6 +70,10 @@ const logout = () => {
   })
 }
 
+const goInfo = () => {
+  router.push({ name: 'userInfo' })
+}
+
 // 主题设置
 const settingVisible = ref(false)
 </script>
@@ -122,7 +127,7 @@ const settingVisible = ref(false)
           </span>
           <template #dropdown>
             <el-dropdown-menu>
-              <el-dropdown-item>
+              <el-dropdown-item @click="goInfo">
                 <el-icon><icon-setting-two></icon-setting-two></el-icon>
                 <div>个人设置</div>
               </el-dropdown-item>

+ 203 - 0
src/components/avatar/cropper.vue

@@ -0,0 +1,203 @@
+<!-- eslint-disable @typescript-eslint/no-non-null-assertion -->
+<script lang="ts" setup>
+import 'vue-cropper/dist/index.css'
+import { VueCropper } from 'vue-cropper'
+import type { UploadFile } from 'element-plus'
+import { ElMessage } from 'element-plus'
+import type { UploadInstance } from 'element-plus'
+
+const uploadRef = ref<UploadInstance>()
+interface Options {
+  img: string | ArrayBuffer | null // 裁剪图片的地址
+  info: true // 裁剪框的大小信息
+  outputSize: number // 裁剪生成图片的质量 [1至0.1]
+  outputType: string // 裁剪生成图片的格式
+  canScale: boolean // 图片是否允许滚轮缩放
+  autoCrop: boolean // 是否默认生成截图框
+  autoCropWidth: number // 默认生成截图框宽度
+  autoCropHeight: number // 默认生成截图框高度
+  fixedBox: boolean // 固定截图框大小 不允许改变
+  fixed: boolean // 是否开启截图框宽高固定比例
+  fixedNumber: Array<number> // 截图框的宽高比例  需要配合centerBox一起使用才能生效
+  full: boolean // 是否输出原图比例的截图
+  canMoveBox: boolean // 截图框能否拖动
+  original: boolean // 上传图片按照原始比例渲染
+  centerBox: boolean // 截图框是否被限制在图片里面
+  infoTrue: boolean // true 为展示真实输出图片宽高 false 展示看到的截图框宽高
+  accept: string // 上传允许的格式
+  mode: string
+}
+// 父组件传参props
+const props = defineProps<{
+  dialogVisible: boolean
+  circleUrl: string
+}>()
+
+const dialogVisible = ref(props.dialogVisible)
+
+const emits = defineEmits(['closeAvatarDialog'])
+// 裁剪组件需要使用到的参数
+const options = reactive<Options>({
+  img: props.circleUrl, // 需要剪裁的图片
+  autoCrop: true, // 是否默认生成截图框
+  autoCropWidth: 200, // 默认生成截图框的宽度
+  autoCropHeight: 200, // 默认生成截图框的长度
+  fixedBox: true, // 是否固定截图框的大小 不允许改变
+  info: true, // 裁剪框的大小信息
+  outputSize: 1, // 裁剪生成图片的质量 [1至0.1]
+  outputType: 'png', // 裁剪生成图片的格式
+  canScale: true, // 图片是否允许滚轮缩放
+  fixed: true, // 是否开启截图框宽高固定比例
+  fixedNumber: [1, 1], // 截图框的宽高比例 需要配合centerBox一起使用才能生效 1比1
+  full: false, // 是否输出原图比例的截图
+  canMoveBox: false, // 截图框能否拖动
+  original: true, // 上传图片按照原始比例渲染
+  centerBox: true, // 截图框是否被限制在图片里面
+  infoTrue: false, // true 为展示真实输出图片宽高 false 展示看到的截图框宽高
+  accept: 'image/jpeg,image/jpg,image/png,image/gif,image/x-icon',
+  mode: 'contain'
+})
+
+const beforeUpload = (file: UploadFile) => {
+  console.log(file)
+  const { size } = file
+  const fileType = file.raw?.type
+  const type = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/x-icon']
+  if (type.indexOf(fileType!) === -1) {
+    ElMessage.warning('仅支持jpeg,jpg,png,gif,icon格式上传')
+    uploadRef.value?.clearFiles()
+  } else if (Number(size) / 1024 / 1024 > 2) {
+    ElMessage.error('图片大小不能超过2MB!')
+    uploadRef.value?.clearFiles()
+  } else {
+    const reader = new FileReader()
+    // 转化为base64
+    reader.readAsDataURL(file.raw!)
+    reader.onload = () => {
+      // 获取到需要剪裁的图片 展示到剪裁框中
+      options.img = reader.result
+    }
+  }
+}
+
+const previews: any = ref({})
+const showBoxStyle: any = ref({})
+
+const cropperRef: any = ref({})
+const previewHandle = (data: any) => {
+  previews.value = data // 预览img图片
+  showBoxStyle.value = {
+    width: data.w + 'px',
+    height: data.h + 'px',
+    overflow: 'hidden',
+    margin: '0',
+    zoom: 200 / data.h
+  }
+}
+
+const goSave = () => {
+  cropperRef.value.getCropData((data: any) => {
+    emits('closeAvatarDialog', data)
+  })
+  cropperRef.value.getCropBlob((data: any) => {
+    // console.log(data)
+  })
+}
+
+const changeScale = (num: number) => {
+  const scale = num || 1
+  cropperRef.value.changeScale(scale)
+}
+const rotateLeft = () => {
+  cropperRef.value.rotateLeft()
+}
+const rotateRight = () => {
+  cropperRef.value.rotateRight()
+}
+</script>
+
+<template>
+  <el-dialog
+    title="上传头像"
+    v-model="dialogVisible"
+    :show-close="false"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    destroy-on-close
+    width="65%"
+  >
+    <div class="croWrap">
+      <div style="width: 600px; height: 400px">
+        <vueCropper
+          ref="cropperRef"
+          :img="options.img"
+          :info="true"
+          :autoCropWidth="options.autoCropWidth"
+          :autoCropHeight="options.autoCropHeight"
+          :info-true="options.infoTrue"
+          :auto-crop="options.autoCrop"
+          :fixed-box="options.fixedBox"
+          :can-move="options.canMoveBox"
+          :can-scale="options.canScale"
+          :fixed-number="options.fixedNumber"
+          :fixed="options.fixed"
+          :full="options.full"
+          :center-box="options.centerBox"
+          :mode="options.mode"
+          @real-time="previewHandle"
+        />
+      </div>
+      <div class="previewBox">
+        <div :style="showBoxStyle">
+          <img :src="previews.url" :style="previews.img" />
+        </div>
+      </div>
+    </div>
+    <div class="btnWrap">
+      <el-upload
+        ref="uploadRef"
+        class="upload-demo"
+        action="#"
+        :auto-upload="false"
+        :on-change="beforeUpload"
+        :show-file-list="false"
+      >
+        <template #trigger>
+          <el-button type="primary">选择图片</el-button>
+        </template>
+      </el-upload>
+      <el-button @click="changeScale(1)">放大</el-button>
+      <el-button @click="changeScale(-1)">缩小</el-button>
+      <el-button @click="rotateLeft">向左</el-button>
+      <el-button @click="rotateRight">向右</el-button>
+    </div>
+    <template #footer>
+      <span class="dialog-footer">
+        <el-button @click="dialogVisible = false">Cancel</el-button>
+        <el-button type="primary" @click="goSave">确定</el-button>
+      </span>
+    </template>
+  </el-dialog>
+</template>
+
+<style lang="scss" scoped>
+.btnWrap {
+  display: flex;
+  align-items: center;
+  margin-top: 12px;
+  .upload-demo {
+    margin-right: 12px;
+  }
+}
+.croWrap {
+  display: flex;
+  justify-content: space-between;
+  .previewBox {
+    width: 200px;
+    height: 200px;
+    border: 1px solid #ccc;
+    overflow: hidden;
+    border-radius: 50%;
+  }
+}
+</style>

+ 28 - 0
src/router/asyncRouter.ts

@@ -210,6 +210,34 @@ const asyncRouter: RouteRecordRaw[] = [
       }
     ]
   },
+  {
+    path: '/monitor',
+    name: 'monitor',
+    meta: {
+      title: '系统监控',
+      icon: 'Notification'
+    },
+    children: [
+      {
+        path: '/monitor/logList',
+        name: 'logList',
+        component: () => import('@/views/monitor/LogList.vue'),
+        meta: {
+          title: '日志查询'
+        }
+      }
+    ]
+  },
+  {
+    path: '/userInfo',
+    name: 'userInfo',
+    component: () => import('@/views/user/UserInfo.vue'),
+    meta: {
+      title: '个人中心',
+      icon: 'House'
+    },
+    hidden: true
+  },
   {
     path: '/:pathMatch(.*)*',
     redirect: '/exception/404',

+ 127 - 0
src/views/monitor/LogList.vue

@@ -0,0 +1,127 @@
+<script setup lang="ts">
+import type { BasicForm, ICRUD } from '@/types/form'
+
+const CRUD: ICRUD = {
+  create(data: any) {
+    console.log(data)
+  },
+  update(data: any) {
+    console.log(data)
+  },
+  getList() {
+    return new Promise(resolve => {
+      resolve({
+        list: [],
+        total: 2
+      })
+    })
+  },
+  delete(data: any) {
+    console.log(data)
+  },
+  deleteBatch(data: any) {
+    console.log(data)
+  }
+}
+
+const logtFormConfig = reactive<BasicForm>({
+  span: 24,
+  formItems: [
+    {
+      label: '日期',
+      value: '',
+      name: 'date',
+      type: 'date-picker',
+      props: {
+        type: 'datetimerange',
+        startPlaceholder: '开始时间',
+        endPlaceholder: '结束时间'
+      },
+      rules: [{ required: true, message: '请选择日期', trigger: 'change' }],
+      search: true
+    },
+    {
+      label: '操作菜单',
+      value: '',
+      name: 'menu',
+      type: 'input',
+      rules: [{ required: true, message: '请输入操作菜单', trigger: 'change' }],
+      search: true
+    },
+    {
+      label: '操作用户',
+      value: '',
+      name: 'type',
+      type: 'input',
+      rules: [{ required: true, message: '请输入操作用户', trigger: 'blur' }],
+      search: true
+    },
+    {
+      label: 'url',
+      value: '',
+      name: 'url',
+      type: 'input',
+      rules: [{ required: true, message: '请输入url', trigger: 'blur' }],
+      search: true
+    }
+  ]
+})
+
+const curRow = ref<any>(null)
+const handleRowClick = ({ row }: { row: any }) => {
+  console.log(row)
+  curRow.value = row
+}
+const rowClassName = ({ row }: { row: any }) => {
+  if (row.id === curRow.value.id) {
+    return 'active'
+  }
+}
+</script>
+
+<template>
+  <el-container>
+    <el-aside width="200px" class="logAside">
+      <el-menu default-active="2" class="el-menu-vertical-demo">
+        <el-menu-item index="2">
+          <el-icon>
+            <InfoFilled color="#409eff" />
+          </el-icon>
+          <span>登陆日志</span>
+        </el-menu-item>
+        <el-menu-item index="3">
+          <el-icon>
+            <QuestionFilled color="#e6a23d" />
+          </el-icon>
+          <span>访问日志</span>
+        </el-menu-item>
+        <el-menu-item index="4">
+          <el-icon>
+            <CircleCloseFilled color="#f56c6d" />
+          </el-icon>
+          <span>异常日志</span>
+        </el-menu-item>
+      </el-menu>
+    </el-aside>
+    <el-main class="logMain">
+      <pro-table :crud="CRUD" :formConfig="logtFormConfig" :row-class-name="rowClassName" @cell-click="handleRowClick">
+        <vxe-column field="description" title="访问功能"></vxe-column>
+        <vxe-column field="type" title=""></vxe-column>
+      </pro-table>
+    </el-main>
+  </el-container>
+</template>
+
+<style lang="scss" scoped>
+.logAside {
+  background-color: #fff;
+
+  .el-menu {
+    border-right: 0;
+  }
+}
+
+.logMain {
+  padding: 0 0 0 var(--main-padding);
+}
+</style>

+ 449 - 0
src/views/user/UserInfo.vue

@@ -0,0 +1,449 @@
+<script setup lang="ts">
+import type { BasicForm, ICRUD } from '@/types/form'
+import AvatarCropper from '@/components/avatar/cropper.vue'
+const activeName = ref('basic')
+const activeName1 = ref()
+const dialogVisible = ref(false)
+
+const formData = reactive<any>({})
+const formDataPass = reactive<any>({})
+const formConfig = reactive<BasicForm>({
+  span: 24,
+  props: {
+    labelPosition: 'right'
+  },
+  formItems: [
+    {
+      label: '姓名',
+      value: '',
+      name: 'name',
+      type: 'input',
+      rules: [{ required: true, message: '请输入用户名', trigger: 'blur' }]
+    },
+    {
+      label: '签名',
+      value: '',
+      name: 'loginName',
+      type: 'input'
+    },
+    {
+      label: '公司',
+      value: '',
+      name: 'company',
+      type: 'input'
+    },
+    {
+      label: '部门',
+      value: '',
+      name: 'part',
+      type: 'input'
+    },
+    {
+      label: '备注',
+      value: '',
+      name: 'remark',
+      type: 'input'
+    }
+  ]
+})
+const telConfig = reactive<BasicForm>({
+  span: 24,
+  props: {
+    labelPosition: 'right'
+  },
+  formItems: [
+    {
+      label: '邮箱',
+      value: '',
+      name: 'emil',
+      type: 'input'
+    },
+    {
+      label: '手机号',
+      value: '',
+      name: 'phone',
+      type: 'input'
+    },
+    {
+      label: '电话',
+      value: '',
+      name: 'tel',
+      type: 'input'
+    }
+  ]
+})
+const passFormRef = ref<any>()
+const validatePass = (rule: any, value: any, callback: any) => {
+  if (value === '') {
+    callback(new Error('请输入密码'))
+  } else {
+    callback()
+  }
+}
+const validatePass2 = (rule: any, value: any, callback: any) => {
+  if (value === '') {
+    callback(new Error('请再次输入密码'))
+  } else if (value !== formDataPass.pass) {
+    callback(new Error('俩次输入不相同'))
+  } else {
+    callback()
+  }
+}
+const passWordConfig = reactive<BasicForm>({
+  span: 24,
+  props: {
+    labelPosition: 'right'
+  },
+  formItems: [
+    {
+      label: '旧密码',
+      value: '',
+      name: 'old',
+      type: 'input',
+      props: {
+        type: 'password'
+      },
+      rules: [{ validator: validatePass, trigger: 'blur' }]
+    },
+    {
+      label: '新密码',
+      value: '',
+      name: 'pass',
+      type: 'input',
+      props: {
+        type: 'password'
+      },
+      rules: [{ validator: validatePass, trigger: 'blur' }]
+    },
+    {
+      label: '确认新密码',
+      value: '',
+      name: 'checkPass',
+      type: 'input',
+      props: {
+        type: 'password'
+      },
+      rules: [{ validator: validatePass2, trigger: 'blur' }]
+    }
+  ]
+})
+const savePass = () => {
+  console.log(passFormRef.value)
+  passFormRef.value.submit()
+}
+const create = (data: any) => {
+  console.log(data)
+}
+const update = (data: any) => {
+  console.log(data)
+}
+
+const handleSave = () => {
+  console.log(formConfig)
+}
+
+const circleUrl = ref('https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png')
+const closeAvatarDialog = (val: string) => {
+  circleUrl.value = val
+  dialogVisible.value = false
+}
+
+// 图片裁剪
+const goCropper = () => {
+  dialogVisible.value = true
+}
+const CRUD: ICRUD = {
+  create(data: any) {
+    console.log(data)
+  },
+  update(data: any) {
+    console.log(data)
+  },
+  getList() {
+    return new Promise(resolve => {
+      resolve({
+        list: [],
+        total: 2
+      })
+    })
+  },
+  delete(data: any) {
+    console.log(data)
+  },
+  deleteBatch(data: any) {
+    console.log(data)
+  }
+}
+
+const logtFormConfig = reactive<BasicForm>({
+  span: 24,
+  formItems: [
+    {
+      label: '操作菜单',
+      value: '',
+      name: '',
+      type: 'input',
+      rules: [{ required: true, message: '请选输入操作菜单', trigger: 'change' }]
+    },
+    {
+      label: '操作用户',
+      value: '',
+      name: 'type',
+      type: 'input',
+      rules: [{ required: true, message: '请输入操作用户', trigger: 'blur' }]
+    }
+  ]
+})
+
+const curRow = ref<any>(null)
+const handleRowClick = ({ row }: { row: any }) => {
+  console.log(row)
+  curRow.value = row
+}
+const rowClassName = ({ row }: { row: any }) => {
+  if (row.id === curRow.value.id) {
+    return 'active'
+  }
+}
+</script>
+
+<template>
+  <el-container class="wrap">
+    <el-aside width="500px">
+      <el-card class="box-card">
+        <template #header>
+          <div class="card-header">
+            <span>
+              <div class="personBg">
+                <el-icon :size="14">
+                  <UserFilled />
+                </el-icon>
+              </div>
+              基本信息
+            </span>
+          </div>
+        </template>
+        <div class="infoWrap">
+          <div class="basicInfo">
+            <el-avatar :size="80" />
+            <div class="info">
+              <h2>管理员</h2>
+              <p>个人签名</p>
+              <el-button type="primary" icon="CollectionTag" round>admin</el-button>
+            </div>
+          </div>
+          <div class="infoList">
+            <div class="infoItem">
+              <el-icon :size="20">
+                <Phone />
+              </el-icon>
+              <span>18951655371</span>
+            </div>
+            <div class="infoItem">
+              <el-icon :size="20">
+                <Iphone />
+              </el-icon>
+              <span>025-83672322</span>
+            </div>
+            <div class="infoItem">
+              <el-icon :size="20">
+                <Message />
+              </el-icon>
+              <span>117575171@qq.com</span>
+            </div>
+            <div class="infoItem">
+              <el-icon :size="20">
+                <Location />
+              </el-icon>
+              <span>方是科技</span>
+            </div>
+            <div class="infoItem">
+              <el-icon :size="20">
+                <OfficeBuilding />
+              </el-icon>
+              <span>行政部</span>
+            </div>
+            <div class="infoItem">
+              <el-icon :size="20">
+                <Coin />
+              </el-icon>
+              <span>租户管理员</span>
+            </div>
+          </div>
+        </div>
+      </el-card>
+    </el-aside>
+    <el-main class="logMain">
+      <el-card class="box-card">
+        <template #header>
+          <div class="card-header">
+            <span>
+              <div class="personBg">
+                <el-icon :size="14">
+                  <EditPen />
+                </el-icon>
+              </div>
+              资料修改
+            </span>
+          </div>
+        </template>
+        <div>
+          <el-tabs v-model="activeName" class="demo-tabs">
+            <el-tab-pane label="基本信息" name="basic">
+              <pro-form
+                :formConfig="formConfig"
+                :formData="formData"
+                ref="proFormRef"
+                :update="update"
+                :create="create"
+              >
+              </pro-form>
+              <div class="btnLabel">
+                <el-button type="primary" @click="handleSave">保存</el-button>
+              </div>
+            </el-tab-pane>
+            <el-tab-pane label="上传头像" name="avator">
+              <span class="avatar">
+                <el-avatar :size="150" :src="circleUrl" @click="goCropper" />
+                <div>
+                  <el-button type="primary" @click="handleSave">保存</el-button>
+                </div>
+              </span>
+            </el-tab-pane>
+            <el-tab-pane label="联系方式" name="phone">
+              <pro-form :formConfig="telConfig" :formData="formData" ref="telFormRef" :update="update" :create="create">
+              </pro-form>
+              <div class="btnLabel">
+                <el-button type="primary" @click="handleSave">保存</el-button>
+              </div>
+            </el-tab-pane>
+            <el-tab-pane label="修改密码" name="password">
+              <pro-form
+                :formConfig="passWordConfig"
+                :formData="formDataPass"
+                ref="passFormRef"
+                :update="update"
+                :create="create"
+              >
+              </pro-form>
+              <div class="btnLabel">
+                <el-button type="primary" @click="savePass">保存</el-button>
+              </div>
+            </el-tab-pane>
+            <el-tab-pane label="我的日志" name="log" class="resetTable">
+              <el-tabs v-model="activeName1" type="card" class="demo-tabs">
+                <el-tab-pane label="登录日志" name="login"></el-tab-pane>
+                <el-tab-pane label="访问日志" name="fw"></el-tab-pane>
+              </el-tabs>
+              <pro-table
+                :crud="CRUD"
+                :formConfig="logtFormConfig"
+                :row-class-name="rowClassName"
+                :showToolbar="false"
+                :height="'480px'"
+                @cell-click="handleRowClick"
+              >
+                <vxe-column field="description" title="耗时"></vxe-column>
+                <vxe-column field="type" title="请求参数"></vxe-column>
+                <vxe-column field="type" title="返回结果"></vxe-column>
+                <vxe-column field="type" title="公司"></vxe-column>
+                <vxe-column field="type" title="部门"></vxe-column>
+              </pro-table>
+            </el-tab-pane>
+          </el-tabs>
+        </div>
+      </el-card>
+    </el-main>
+  </el-container>
+  <AvatarCropper
+    v-if="dialogVisible"
+    :dialogVisible="dialogVisible"
+    :circleUrl="circleUrl"
+    @closeAvatarDialog="closeAvatarDialog"
+  ></AvatarCropper>
+</template>
+
+<style lang="scss" scoped>
+.wrap {
+  height: 100%;
+  .logMain {
+    padding: 0 0 0 var(--main-padding);
+  }
+  .resetTable {
+    :deep(.el-card__body) {
+      padding: 0 !important;
+    }
+    :deep(.el-tabs--card > .el-tabs__header) {
+      margin: 0;
+      border-bottom: 0;
+    }
+  }
+  .box-card {
+    height: 100%;
+    .avatar {
+      display: flex;
+      flex-direction: column;
+      margin-left: 80px;
+      align-items: center;
+      float: left;
+      .el-button {
+        margin-top: 20px;
+      }
+    }
+  }
+  .personBg {
+    color: #1890ff;
+    background-color: #e6f7ff;
+    width: 24px;
+    height: 24px;
+    border-radius: 50%;
+    text-align: center;
+    vertical-align: middle;
+    display: inline-block;
+  }
+
+  .infoWrap {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+  }
+
+  .basicInfo {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+
+    .info {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      margin-top: 10px;
+
+      p {
+        margin: 10px 0;
+      }
+    }
+  }
+
+  .infoList {
+    width: 380px;
+    margin-top: 10px;
+
+    .infoItem {
+      display: flex;
+      flex-direction: row;
+      align-items: center;
+      margin-top: 20px;
+
+      span {
+        display: inline-block;
+        margin-left: 20px;
+      }
+    }
+  }
+
+  .btnLabel {
+    margin-left: 100px;
+  }
+}
+</style>