feat(Workplace): Add wrokplace demo

feat(Component): Add Highlight component

feat(hooks): Add useTimeAgo hook
This commit is contained in:
kailong321200875 2022-01-23 21:54:27 +08:00
parent 3fc79c0ae7
commit c53fa562e5
13 changed files with 552 additions and 8 deletions

BIN
src/assets/imgs/avatar.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

View File

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

View File

@ -0,0 +1,65 @@
<script lang="tsx">
import { defineComponent, PropType, computed, h, unref } from 'vue'
import { propTypes } from '@/utils/propTypes'
export default defineComponent({
name: 'Highlight',
props: {
tag: propTypes.string.def('span'),
keys: {
type: Array as PropType<string[]>,
default: () => []
},
color: propTypes.string.def('var(--el-color-primary)')
},
emits: ['click'],
setup(props, { emit, slots }) {
const keyNodes = computed(() => {
return props.keys.map((key) => {
return h(
'span',
{
onClick: () => {
emit('click', key)
},
style: {
color: props.color,
cursor: 'pointer'
}
},
key
)
})
})
const parseText = (text: string) => {
props.keys.forEach((key, index) => {
const regexp = new RegExp(key, 'g')
text = text.replace(regexp, `{{${index}}}`)
})
return text.split(/{{|}}/)
}
const renderText = () => {
if (!slots?.default) return null
const node = slots?.default()[0].children
if (!node) {
return slots?.default()[0]
}
const textArray = parseText(node as string)
const regexp = /^[0-9]*$/
const nodes = textArray.map((t) => {
if (regexp.test(t)) {
return unref(keyNodes)[Math.floor(Number(t))] || t
}
return t
})
return h(props.tag, nodes)
}
return () => renderText()
}
})
</script>

View File

@ -38,7 +38,11 @@ const loginOut = () => {
<template>
<ElDropdown :class="prefixCls" trigger="click">
<div class="flex items-center">
<img src="@/assets/imgs/avatar.png" alt="" class="w-[calc(var(--tags-view-height)-10px)]" />
<img
src="@/assets/imgs/avatar.jpg"
alt=""
class="w-[calc(var(--tags-view-height)-10px)] rounded-[50%]"
/>
<span class="<lg:hidden text-14px pl-[5px] text-[var(--top-header-text-color)]">Archer</span>
</div>
<template #dropdown>

View File

@ -0,0 +1,48 @@
import { useTimeAgo as useTimeAgoCore, UseTimeAgoMessages } from '@vueuse/core'
import { computed, unref } from 'vue'
import { useLocaleStoreWithOut } from '@/store/modules/locale'
const TIME_AGO_MESSAGE_MAP: {
'zh-CN': UseTimeAgoMessages
en: UseTimeAgoMessages
} = {
'zh-CN': {
justNow: '刚刚',
past: (n) => (n.match(/\d/) ? `${n}` : n),
future: (n) => (n.match(/\d/) ? `${n}` : n),
month: (n, past) => (n === 1 ? (past ? '上个月' : '下个月') : `${n} 个月`),
year: (n, past) => (n === 1 ? (past ? '去年' : '明年') : `${n}`),
day: (n, past) => (n === 1 ? (past ? '昨天' : '明天') : `${n}`),
week: (n, past) => (n === 1 ? (past ? '上周' : '下周') : `${n}`),
hour: (n) => `${n} 小时`,
minute: (n) => `${n} 分钟`,
second: (n) => `${n}`
},
en: {
justNow: '刚刚',
past: (n) => (n.match(/\d/) ? `${n} ago` : n),
future: (n) => (n.match(/\d/) ? `in ${n}` : n),
month: (n, past) =>
n === 1 ? (past ? 'last month' : 'next month') : `${n} month${n > 1 ? 's' : ''}`,
year: (n, past) =>
n === 1 ? (past ? 'last year' : 'next year') : `${n} year${n > 1 ? 's' : ''}`,
day: (n, past) => (n === 1 ? (past ? 'yesterday' : 'tomorrow') : `${n} day${n > 1 ? 's' : ''}`),
week: (n, past) =>
n === 1 ? (past ? 'last week' : 'next week') : `${n} week${n > 1 ? 's' : ''}`,
hour: (n) => `${n} hour${n > 1 ? 's' : ''}`,
minute: (n) => `${n} minute${n > 1 ? 's' : ''}`,
second: (n) => `${n} second${n > 1 ? 's' : ''}`
}
}
export const useTimeAgo = (time: Date | number | string) => {
const localeStore = useLocaleStoreWithOut()
const currentLocale = computed(() => localeStore.getCurrentLocale)
const timeAgo = useTimeAgoCore(time, {
messages: TIME_AGO_MESSAGE_MAP[unref(currentLocale).lang]
})
return timeAgo
}

View File

@ -71,7 +71,8 @@ export default {
menu12: 'Menu1-2',
menu2: 'Menu2',
dashboard: 'Dashboard',
analysis: 'Analysis'
analysis: 'Analysis',
workplace: 'Workplace'
},
analysis: {
newUser: 'New user',
@ -109,6 +110,28 @@ export default {
saturday: 'Saturday',
sunday: 'Sunday'
},
workplace: {
goodMorning: 'Good morning',
happyDay: 'Wish you happy every day!',
toady: `It's sunny today`,
project: 'Project',
access: 'Project access',
toDo: 'To do',
introduction: 'A serious introduction',
more: 'More',
shortcutOperation: 'Shortcut operation',
operation: 'Operation',
index: 'Index',
personal: 'Personal',
team: 'Team',
quote: 'Quote',
contribution: 'Contribution',
hot: 'Hot',
yield: 'Yield',
dynamic: 'Dynamic',
push: 'push',
pushCode: 'Archer push code to GitHub'
},
formDemo: {
input: 'Input',
inputNumber: 'InputNumber',

View File

@ -71,7 +71,8 @@ export default {
menu12: '菜单1-2',
menu2: '菜单2',
dashboard: '首页',
analysis: '分析页'
analysis: '分析页',
workplace: '工作台'
},
analysis: {
newUser: '新增用户',
@ -109,6 +110,28 @@ export default {
saturday: '周六',
sunday: '周日'
},
workplace: {
goodMorning: '早安',
happyDay: '祝你开心每一天!',
toady: '今日晴',
project: '项目数',
access: '项目访问',
toDo: '待办',
introduction: '一个正经的简介',
more: '更多',
shortcutOperation: '快捷操作',
operation: '操作',
index: '指数',
personal: '个人',
team: '团队',
quote: '引用',
contribution: '贡献',
hot: '热度',
yield: '产量',
dynamic: '动态',
push: '推送',
pushCode: 'Archer 推送 代码到 Github'
},
formDemo: {
input: '输入框',
inputNumber: '数字输入框',

View File

@ -1,6 +1,13 @@
import * as echarts from 'echarts/core'
import { BarChart, LineChart, PieChart, MapChart, PictorialBarChart } from 'echarts/charts'
import {
BarChart,
LineChart,
PieChart,
MapChart,
PictorialBarChart,
RadarChart
} from 'echarts/charts'
import {
TitleComponent,
@ -27,7 +34,8 @@ echarts.use([
PieChart,
MapChart,
CanvasRenderer,
PictorialBarChart
PictorialBarChart,
RadarChart
])
export default echarts

View File

@ -54,8 +54,16 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
name: 'Analysis',
meta: {
title: t('router.analysis'),
noCache: true,
affix: true
noCache: true
}
},
{
path: 'workplace',
component: () => import('@/views/Dashboard/Workplace.vue'),
name: 'Workplace',
meta: {
title: t('router.workplace'),
noCache: true
}
}
]

View File

@ -63,3 +63,35 @@ export const findIndex = <T = Recordable>(ary: Array<T>, fn: Fn): number => {
export const trim = (str: string) => {
return str.replace(/(^\s*)|(\s*$)/g, '')
}
/**
* @param {Date | number | string} time
* @param {String} fmt yyyy-MM-ddyyyy-MM-dd HH:mm:ss
*/
export function formatTime(time: Date | number | string, fmt: string) {
if (!time) return ''
else {
const date = new Date(time)
const o = {
'M+': date.getMonth() + 1,
'd+': date.getDate(),
'H+': date.getHours(),
'm+': date.getMinutes(),
's+': date.getSeconds(),
'q+': Math.floor((date.getMonth() + 3) / 3),
S: date.getMilliseconds()
}
if (/(y+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length))
}
for (const k in o) {
if (new RegExp('(' + k + ')').test(fmt)) {
fmt = fmt.replace(
RegExp.$1,
RegExp.$1.length === 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length)
)
}
}
return fmt
}
}

View File

@ -32,7 +32,7 @@ setTimeout(() => {
<ElCol :span="24">
<ElCard shadow="hover" class="mb-20px">
<ElSkeleton :loading="loading" animated :rows="4">
<Echart :options="lineOptions" :height="400" />
<Echart :options="lineOptions" :height="350" />
</ElSkeleton>
</ElCard>
</ElCol>

View File

@ -0,0 +1,299 @@
<script setup lang="ts">
import { useTimeAgo } from '@/hooks/web/useTimeAgo'
import { ElRow, ElCol, ElSkeleton, ElCard, ElDivider, ElLink } from 'element-plus'
import { useI18n } from '@/hooks/web/useI18n'
import { ref } from 'vue'
import { CountTo } from '@/components/CountTo'
import { formatTime } from '@/utils'
import { Echart } from '@/components/Echart'
import { radarOption } from './echarts-data'
import { Highlight } from '@/components/Highlight'
interface Project {
name: string
icon: string
message: string
personal: string
time: Date | number | string
}
interface Dynamic {
keys: string[]
time: Date | number | string
}
interface Team {
name: string
icon: string
}
const { t } = useI18n()
const projects: Project[] = [
{
name: 'Github',
icon: 'akar-icons:github-fill',
message: t('workplace.introduction'),
personal: 'Archer',
time: new Date()
},
{
name: 'Vue',
icon: 'logos:vue',
message: t('workplace.introduction'),
personal: 'Archer',
time: new Date()
},
{
name: 'Angular',
icon: 'logos:angular-icon',
message: t('workplace.introduction'),
personal: 'Archer',
time: new Date()
},
{
name: 'React',
icon: 'logos:react',
message: t('workplace.introduction'),
personal: 'Archer',
time: new Date()
},
{
name: 'Webpack',
icon: 'logos:webpack',
message: t('workplace.introduction'),
personal: 'Archer',
time: new Date()
},
{
name: 'Vite',
icon: 'vscode-icons:file-type-vite',
message: t('workplace.introduction'),
personal: 'Archer',
time: new Date()
}
]
const dynamics: Dynamic[] = [
{
keys: [t('workplace.push'), 'Github'],
time: new Date()
},
{
keys: [t('workplace.push'), 'Github'],
time: new Date()
},
{
keys: [t('workplace.push'), 'Github'],
time: new Date()
},
{
keys: [t('workplace.push'), 'Github'],
time: new Date()
},
{
keys: [t('workplace.push'), 'Github'],
time: new Date()
},
{
keys: [t('workplace.push'), 'Github'],
time: new Date()
}
]
const team: Team[] = [
{
name: 'Github',
icon: 'akar-icons:github-fill'
},
{
name: 'Vue',
icon: 'logos:vue'
},
{
name: 'Angular',
icon: 'logos:angular-icon'
},
{
name: 'React',
icon: 'logos:react'
},
{
name: 'Webpack',
icon: 'logos:webpack'
},
{
name: 'Vite',
icon: 'vscode-icons:file-type-vite'
}
]
const loading = ref(true)
setTimeout(() => {
loading.value = false
}, 1000)
</script>
<template>
<div>
<ElCard shadow="never">
<ElSkeleton :loading="loading" animated>
<ElRow :gutter="20" justify="space-between">
<ElCol :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
<div class="flex items-center">
<img
src="@/assets/imgs/avatar.jpg"
alt=""
class="w-70px h-70px rounded-[50%] mr-20px"
/>
<div>
<div class="text-20px text-700">
{{ t('workplace.goodMorning') }}Archer{{ t('workplace.happyDay') }}
</div>
<div class="mt-10px text-14px text-gray-500">
{{ t('workplace.toady') }}20 - 32
</div>
</div>
</div>
</ElCol>
<ElCol :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
<div class="flex h-70px items-center justify-end <sm:mt-20px">
<div class="px-8px text-right">
<div class="text-14px text-gray-400 mb-20px">{{ t('workplace.project') }}</div>
<CountTo class="text-20px" :start-val="0" :end-val="40" :duration="2600" />
</div>
<ElDivider direction="vertical" />
<div class="px-8px text-right">
<div class="text-14px text-gray-400 mb-20px">{{ t('workplace.toDo') }}</div>
<CountTo class="text-20px" :start-val="0" :end-val="10" :duration="2600" />
</div>
<ElDivider direction="vertical" border-style="dashed" />
<div class="px-8px text-right">
<div class="text-14px text-gray-400 mb-20px">{{ t('workplace.access') }}</div>
<CountTo class="text-20px" :start-val="0" :end-val="2340" :duration="2600" />
</div>
</div>
</ElCol>
</ElRow>
</ElSkeleton>
</ElCard>
</div>
<ElRow class="mt-20px" :gutter="20" justify="space-between">
<ElCol :xl="16" :lg="16" :md="24" :sm="24" :xs="24" class="mb-20px">
<ElSkeleton :loading="loading" animated>
<ElCard shadow="never">
<template #header>
<div class="flex justify-between">
<span>{{ t('workplace.project') }}</span>
<ElLink type="primary" :underline="false">{{ t('workplace.more') }}</ElLink>
</div>
</template>
<ElRow>
<ElCol
v-for="(item, index) in projects"
:key="`card-${index}`"
:xl="8"
:lg="8"
:md="12"
:sm="24"
:xs="24"
>
<ElCard shadow="hover">
<div class="flex items-center">
<Icon :icon="item.icon" :size="25" class="mr-10px" />
<span class="text-16px">{{ item.name }}</span>
</div>
<div class="mt-15px text-14px text-gray-400">{{ item.message }}</div>
<div class="mt-20px text-12px text-gray-400 flex justify-between">
<span>{{ item.personal }}</span>
<span>{{ formatTime(item.time, 'yyyy-MM-dd') }}</span>
</div>
</ElCard>
</ElCol>
</ElRow>
</ElCard>
</ElSkeleton>
<ElSkeleton :loading="loading" animated>
<ElCard shadow="never" class="mt-20px">
<template #header>
<div class="flex justify-between">
<span>{{ t('workplace.dynamic') }}</span>
<ElLink type="primary" :underline="false">{{ t('workplace.more') }}</ElLink>
</div>
</template>
<div v-for="(item, index) in dynamics" :key="`dynamics-${index}`">
<div class="flex items-center">
<img
src="@/assets/imgs/avatar.jpg"
alt=""
class="w-35px h-35px rounded-[50%] mr-20px"
/>
<div>
<div class="text-14px">
<Highlight :keys="item.keys"> {{ t('workplace.pushCode') }} </Highlight>
</div>
<div class="mt-15px text-12px text-gray-400">
{{ useTimeAgo(item.time) }}
</div>
</div>
</div>
<ElDivider />
</div>
</ElCard>
</ElSkeleton>
</ElCol>
<ElCol :xl="8" :lg="8" :md="24" :sm="24" :xs="24" class="mb-20px">
<ElSkeleton :loading="loading" animated>
<ElCard shadow="never">
<template #header>
<span>{{ t('workplace.shortcutOperation') }}</span>
</template>
<ElCol
v-for="item in 9"
:key="`card-${item}`"
:xl="12"
:lg="12"
:md="12"
:sm="24"
:xs="24"
class="mb-10px"
>
<ElLink type="default" :underline="false">
{{ t('workplace.operation') }}{{ item }}
</ElLink>
</ElCol>
</ElCard>
</ElSkeleton>
<ElSkeleton :loading="loading" animated>
<ElCard shadow="never" class="mt-20px">
<template #header>
<span>xx{{ t('workplace.index') }}</span>
</template>
<Echart :options="radarOption" :height="400" />
</ElCard>
</ElSkeleton>
<ElSkeleton :loading="loading" animated>
<ElCard shadow="never" class="mt-20px">
<template #header>
<span>{{ t('workplace.team') }}</span>
</template>
<ElRow>
<ElCol v-for="item in team" :key="`team-${item.name}`" :span="12" class="mb-20px">
<div class="flex items-center">
<Icon :icon="item.icon" class="mr-10px" />
<ElLink type="default" :underline="false">
{{ item.name }}
</ElLink>
</div>
</ElCol>
</ElRow>
</ElCard>
</ElSkeleton>
</ElCol>
</ElRow>
</template>

View File

@ -151,3 +151,34 @@ export const barOptions: EChartsOption = {
}
]
}
export const radarOption: EChartsOption = {
legend: {
data: [t('workplace.personal'), t('workplace.team')]
},
radar: {
// shape: 'circle',
indicator: [
{ name: t('workplace.quote'), max: 65 },
{ name: t('workplace.contribution'), max: 160 },
{ name: t('workplace.hot'), max: 300 },
{ name: t('workplace.yield'), max: 130 }
]
},
series: [
{
name: `xxx${t('workplace.index')}`,
type: 'radar',
data: [
{
value: [42, 30, 20, 35],
name: t('workplace.personal')
},
{
value: [50, 140, 28, 35],
name: t('workplace.team')
}
]
}
]
}