feat(工具箱): 添加“工作时间可视化”工具

This commit is contained in:
2025-06-16 00:08:31 +08:00
parent bca8602a3a
commit 383280031c
3 changed files with 304 additions and 0 deletions

View File

@@ -312,6 +312,17 @@ export const toolList = [
version: '1',
enabled: true,
},
{
id: 'visualized-working-hours',
component: 'Other/VisualizedWorkingHours/VisualizedWorkingHours',
title: '工作时间可视化',
iconClass: 'mdi mdi-clock-digital',
desc: '用趣味化的方式呈现工作收益与时间进度,让薪资进度和下班期待看得见。',
createdAt: '',
updatedAt: '',
version: '0',
enabled: false,
},
],
},
];

View File

@@ -0,0 +1,243 @@
<template>
<div class="tool-detail-page">
<!-- 配置选项 -->
<n-card size="small" style="--color: #2196F3;">
<template #header>
<div class="card-title">
<span class="card-title__icon mdi mdi-cog-outline"></span>
<span class="card-title__label">配置选项</span>
</div>
</template>
<n-form
class="form-no-feedback config-inputs"
label-align="right"
label-placement="left"
label-width="auto"
>
<n-form-item label="日薪">
<n-input-number
v-model:value="configData.dailyWage.value"
:min="0"
:max="99999999"
:precision="2"
:step="1"
></n-input-number>
</n-form-item>
<n-form-item label="收入币种">
<n-input
v-model:value="configData.currencyOfIncome.value"
placeholder="用于显示,例如:¥"
type="text"
:maxlength="8"
></n-input>
</n-form-item>
<n-form-item label="工作时间">
<n-input-group>
<n-time-picker
v-model:formatted-value="configData.workTimeStart.value"
format="HH:mm"
/>
<n-time-picker
v-model:formatted-value="configData.workTimeStop.value"
format="HH:mm"
/>
</n-input-group>
</n-form-item>
</n-form>
</n-card>
<!-- 实时进度 -->
<n-card size="small" style="--color: #F44336;">
<template #header>
<div class="card-title">
<span class="card-title__icon mdi mdi-chart-line"></span>
<span class="card-title__label">实时进度</span>
</div>
</template>
<div class="progress-item">
<div class="progress-item__row">
<span>本月已赚</span>
<span>1000 </span>
</div>
<div class="progress-item__row">
<n-progress
color="var(--color)"
type="line"
:height="16"
:percentage="35"
:processing="true"
:show-indicator="false"
/>
</div>
<div class="progress-item__row">
<span>35%</span>
<span>剩余 20.5 </span>
</div>
</div>
<div class="progress-item">
<div class="progress-item__row">
<span>今日已赚</span>
<span>100 </span>
</div>
<div class="progress-item__row">
<n-progress
color="var(--color)"
type="line"
:height="16"
:percentage="60"
:processing="true"
:show-indicator="false"
/>
</div>
<div class="progress-item__row">
<span>60%</span>
<span>剩余 5.6 小时</span>
</div>
</div>
</n-card>
<!-- 下班冲刺 -->
<n-card size="small" style="--color: #4CAF50;">
<template #header>
<div class="card-title">
<span class="card-title__icon mdi mdi-clock-outline"></span>
<span class="card-title__label">下班冲刺</span>
</div>
</template>
<div class="time-info">
<div class="time-info__row">
<span>距离下班还剩</span>
</div>
<div class="time-info__row">
<span>05:30:20</span>
</div>
<div class="time-info__row">
<span>漫长的一天才过一半加油吧</span>
</div>
</div>
</n-card>
<!-- 获得成就 -->
<!-- <n-card size="small" style="--color: #FF9800;">
<template #header>
<div class="card-title">
<span class="card-title__icon mdi mdi-trophy-outline"></span>
<span class="card-title__label">获得成就</span>
</div>
</template>
</n-card> -->
</div>
</template>
<script setup>
import {
NButton, NCard, NForm, NFormItem,
NInput, NInputGroup, NInputNumber,
NProgress, NTimePicker,
} from 'naive-ui';
import {
onBeforeMount,
} from 'vue';
import {
configData, initData,
} from './data';
onBeforeMount(() => {
initData();
});
</script>
<style lang="less" scoped>
.card-title > span {
vertical-align: middle;
}
.card-title__icon {
color: var(--color, #252525);
font-size: 1.2em;
}
.card-title__label {
margin-left: 0.5em;
}
.config-inputs {
.n-form-item {
max-width: 480px;
}
:deep(.n-form-item-blank) > * {
width: 100%;
}
:deep(.n-input-group) > * {
flex-grow: 1;
width: 0;
}
}
.progress-item :not(:first-child) {
margin-top: 12px;
}
.progress-item__row {
display: flex;
align-items: center;
justify-content: space-between;
&:not(:first-child) {
margin-top: 4px;
}
&:first-child, &:last-child {
color: #505050;
}
&:first-child span:last-child {
color: var(--color);
font-size: 1.2em;
font-weight: bold;
}
&:last-child {
font-size: 0.8em;
}
}
.time-info {
padding: 16px 0;
background-color: var(--color-action);
}
.time-info__row {
text-align: center;
&:first-child, &:last-child {
color: #505050;
}
&:nth-child(1) {
font-size: 1em;
}
&:nth-child(2) {
font-size: 2em;
}
&:nth-child(3) {
font-size: 0.8em;
}
}
</style>

View File

@@ -0,0 +1,50 @@
import { useLocalStorage } from '@vueuse/core';
import { KEY_PREFIX } from '@/assets/js/local-storage';
/** 模块名称 */
const STORAGE_PREFIX = KEY_PREFIX + 'visualized-working-hours/';
/** 配置选项 */
export const configData = {
/** 收入币种 */
currencyOfIncome: useLocalStorage(STORAGE_PREFIX + 'currencyOfIncome', ''),
/** 日薪 */
dailyWage: useLocalStorage(STORAGE_PREFIX + 'dailyWage', 100),
/** 午休时长 */
lunchBreakDuration: useLocalStorage(STORAGE_PREFIX + 'lunchBreakDuration', 1),
/** 工作开始时间 */
workTimeStart: useLocalStorage(STORAGE_PREFIX + 'workTimeStart', ''),
/** 工作结束时间 */
workTimeStop: useLocalStorage(STORAGE_PREFIX + 'workTimeStop', ''),
};
/** 初始化数据 */
export function initData() {
let {
currencyOfIncome,
workTimeStart,
workTimeStop,
} = configData;
let timeRegExp = new RegExp(/^\d{2}:\d{2}$/);
if (!currencyOfIncome.value) {
currencyOfIncome.value = '¥';
}
if (!workTimeStart.value.match(timeRegExp)) {
workTimeStart.value = '09:00';
}
if (!workTimeStop.value.match(timeRegExp)) {
workTimeStop.value = '18:00';
}
}