甘特图

This commit is contained in:
KuroSago 2024-10-02 20:09:17 +08:00
parent fb86e64837
commit f843603dc0
13 changed files with 321 additions and 200 deletions

View File

@ -19,7 +19,6 @@
"sdras.vue-vscode-snippets", "sdras.vue-vscode-snippets",
"whtouche.vscode-js-console-utils", "whtouche.vscode-js-console-utils",
"zhuangtongfa.material-theme", "zhuangtongfa.material-theme",
"Vue.vscode-typescript-vue-plugin", // TS使TS*.vue
"esbenp.prettier-vscode", // "esbenp.prettier-vscode", //
"dbaeumer.vscode-eslint" // "dbaeumer.vscode-eslint" //
] ]

View File

@ -7,6 +7,11 @@
"editor.formatOnSave": false, "editor.formatOnSave": false,
"prettier.enable": false, "prettier.enable": false,
"unocss.root": ["./"], "unocss.root": ["./"],
"editor.quickSuggestions": {
"other": true,
"comments": true,
"strings": true
},
"typescript.tsdk": "node_modules/typescript/lib", "typescript.tsdk": "node_modules/typescript/lib",
"vue.server.hybridMode": true, "vue.server.hybridMode": true,
"eslint.validate": ["html", "css", "scss", "json", "jsonc"], "eslint.validate": ["html", "css", "scss", "json", "jsonc"],
@ -17,9 +22,7 @@
"i18n-ally.keystyle": "nested", "i18n-ally.keystyle": "nested",
"i18n-ally.localesPaths": ["src/locales/langs"], "i18n-ally.localesPaths": ["src/locales/langs"],
"editor.fontLigatures": true, "editor.fontLigatures": true,
"editor.quickSuggestions": {
"strings": true
},
"editor.tabSize": 2, "editor.tabSize": 2,
"files.associations": { "files.associations": {
"*.env.*": "dotenv", "*.env.*": "dotenv",

View File

@ -60,55 +60,55 @@
"@dcloudio/uni-quickapp-webview": "3.0.0-alpha-4020720240905001", "@dcloudio/uni-quickapp-webview": "3.0.0-alpha-4020720240905001",
"@multiavatar/multiavatar": "^1.0.7", "@multiavatar/multiavatar": "^1.0.7",
"@ontos/material-design-3-theme-builder": "workspace:*", "@ontos/material-design-3-theme-builder": "workspace:*",
"alova": "^3.0.16", "alova": "^3.0.17",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"normalize-path": "^3.0.0", "normalize-path": "^3.0.0",
"pinia": "^2.2.2", "pinia": "^2.2.3",
"pinia-plugin-persistedstate": "^3.2.1", "pinia-plugin-persistedstate": "^3.2.1",
"unplugin-vue-components": "^0.27.0", "unplugin-vue-components": "^0.27.4",
"vue": "^3.5.10" "vue": "^3.5.10"
}, },
"devDependencies": { "devDependencies": {
"@antfu/eslint-config": "^2.27.3", "@antfu/eslint-config": "^3.7.3",
"@dcloudio/types": "^3.4.12", "@dcloudio/types": "^3.4.12",
"@dcloudio/uni-automator": "3.0.0-alpha-4020720240905001", "@dcloudio/uni-automator": "3.0.0-alpha-4020720240905001",
"@dcloudio/uni-cli-shared": "3.0.0-alpha-4020720240905001", "@dcloudio/uni-cli-shared": "3.0.0-alpha-4020720240905001",
"@dcloudio/uni-stacktracey": "3.0.0-alpha-4020720240905001", "@dcloudio/uni-stacktracey": "3.0.0-alpha-4020720240905001",
"@dcloudio/vite-plugin-uni": "3.0.0-alpha-4020720240905001", "@dcloudio/vite-plugin-uni": "3.0.0-alpha-4020720240905001",
"@egoist/tailwindcss-icons": "^1.8.1", "@egoist/tailwindcss-icons": "^1.8.1",
"@iconify/json": "^2.2.245", "@iconify/json": "^2.2.254",
"@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-node-resolve": "^15.3.0",
"@types/crypto-js": "^4.2.2", "@types/crypto-js": "^4.2.2",
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"@types/node": "^20.16.1", "@types/node": "^22.7.4",
"@types/normalize-path": "^3.0.0", "@types/normalize-path": "^3.0.2",
"@unocss/eslint-config": "^0.62.3", "@unocss/eslint-config": "^0.63.1",
"@unocss/preset-icons": "^0.62.3", "@unocss/preset-icons": "^0.63.1",
"@unocss/preset-uno": "^0.62.3", "@unocss/preset-uno": "^0.63.1",
"@unocss/transformer-directives": "^0.62.3", "@unocss/transformer-directives": "^0.63.1",
"@unocss/vite": "0.62.3", "@unocss/vite": "0.63.1",
"@vitejs/plugin-vue": "^5.1.2", "@vitejs/plugin-vue": "^5.1.4",
"@vue/runtime-core": "^3.4.38", "@vue/runtime-core": "^3.5.10",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"commit-and-tag-version": "^12.4.2", "commit-and-tag-version": "^12.4.4",
"eslint": "^9.10.0", "eslint": "^9.11.1",
"eslint-plugin-format": "^0.1.2", "eslint-plugin-format": "^0.1.2",
"globals": "^15.9.0", "globals": "^15.9.0",
"lint-staged": "^15.2.9", "lint-staged": "^15.2.10",
"picocolors": "^1.0.1", "picocolors": "^1.1.0",
"rollup-plugin-visualizer": "^5.12.0", "rollup-plugin-visualizer": "^5.12.0",
"sass": "^1.79.4", "sass": "^1.79.4",
"simple-git-hooks": "^2.11.1", "simple-git-hooks": "^2.11.1",
"tsx": "^4.19.0", "tsx": "^4.19.1",
"typescript": "^5.5.4", "typescript": "^5.6.2",
"uni-mini-router": "^0.1.6", "uni-mini-router": "^0.1.6",
"uni-read-pages-vite": "^0.0.6", "uni-read-pages-vite": "^0.0.6",
"unocss": "^0.62.3", "unocss": "^0.63.1",
"unocss-preset-weapp": "^0.62.0", "unocss-preset-weapp": "^0.62.2",
"unplugin-auto-import": "^0.18.2", "unplugin-auto-import": "^0.18.3",
"vite": "^5.4.2", "vite": "^5.4.8",
"vite-plugin-restart": "^0.4.1" "vite-plugin-restart": "^0.4.1"
}, },
"simple-git-hooks": { "simple-git-hooks": {

View File

@ -13,5 +13,51 @@ onHide(() => {
</script> </script>
<style lang="scss"> <style lang="scss">
view,
scroll-view,
swiper,
match-media,
movable-area,
movable-view,
cover-view,
cover-image,
icon,
text,
rich-text,
progress,
button,
checkboxe,
ditor,
form,
input,
label,
picker,
picker-view,
radio,
slider,
switch,
textarea,
navigator,
audio,
camera,
image,
video,
live-player,
live-pusher,
map,
canvas,
web-view,
:before,
:after {
box-sizing: border-box;
}
/* 隐藏scroll-view的滚动条 */
::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
}
</style> </style>

View File

@ -1,41 +1,41 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { ThingType } from './type';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import isoWeek from 'dayjs/plugin/isoWeek'; import isoWeek from 'dayjs/plugin/isoWeek';
import weekday from 'dayjs/plugin/weekday'; import weekday from 'dayjs/plugin/weekday';
import { getWeekDates } from './helper'; import { getWeekDates } from './helper';
import type { ThingType } from './type';
const props = withDefaults(defineProps<Props>(), {
groupedEvents: () => new Map(),
timeLineWidth: 80,
});
dayjs.extend(isoWeek); dayjs.extend(isoWeek);
dayjs.extend(weekday); dayjs.extend(weekday);
defineComponent({ name: 'GantDate' }); defineComponent({ name: 'GantDate' });
interface Props { interface Props {
groupedEvents?: Map<number, ThingType[]>; groupedEvents?: Map<number, ThingType[]>
timeLineWidth?: number
} }
const props = withDefaults(defineProps<Props>(), {
groupedEvents: () => new Map()
});
const value = defineModel('value', { const value = defineModel('value', {
type: Number, type: Number,
default: dayjs().valueOf()
default: dayjs().valueOf(),
}); });
/** ** 正确的格式一定为某一天的起始 *** */ /** ** 正确的格式一定为某一天的起始 */
const selectedDate = computed({ const selectedDate = computed({
get: () => dayjs(value.value).startOf('day').valueOf(), get: () => dayjs(value.value).startOf('day').valueOf(),
set: val => { set: (val) => {
value.value = val; value.value = val;
} },
}); });
const weekDates = defineModel('weekDates', { const weekDates = defineModel('weekDates', {
type: Object as () => { date: number; day: string; weekDay: string }[], type: Object as () => { date: number, day: string, weekDay: string }[],
default: () => [] default: () => [],
}); });
// //
@ -44,10 +44,10 @@ function selectDate(date: number) {
} }
// //
function datePickerChange(time: number) { // function datePickerChange(time: number) {
selectDate(time); // selectDate(time);
weekDates.value = getWeekDates(time); // weekDates.value = getWeekDates(time);
} // }
// //
function backToToday() { function backToToday() {
@ -70,18 +70,18 @@ function nextWeek() {
} }
const weekDatesForMarks = computed(() => { const weekDatesForMarks = computed(() => {
return weekDates.value.map(item => { return weekDates.value.map((item) => {
return { return {
...item, ...item,
isMark: props.groupedEvents.get(item.date)?.length isMark: props.groupedEvents.get(item.date)?.length,
}; };
}); });
}); });
</script> </script>
<template> <template>
<div class="gantDateShadow flex flex-col px-5 pb-2 pt-6"> <div class="gantDateShadow flex flex-col pb-2 pt-2">
<div class="relative flex flex-1 flex-row items-center justify-center flex-self-stretch px-7.5"> <div class="relative flex flex-1 flex-row items-center justify-center flex-self-stretch">
<div class="relative flex flex-row items-center gap-2"> <div class="relative flex flex-row items-center gap-2">
<!-- <n-date-picker <!-- <n-date-picker
class="absolute left-0 top-0 z-2 wh-full overflow-hidden opacity-0 !cursor-pointer" class="absolute left-0 top-0 z-2 wh-full overflow-hidden opacity-0 !cursor-pointer"
@ -90,48 +90,54 @@ const weekDatesForMarks = computed(() => {
type="date" type="date"
@update:value="datePickerChange" @update:value="datePickerChange"
/> --> /> -->
<span class=" font-bold"> <span class="font-bold">
{{ dayjs(selectedDate).format('YYYY年M月') }} {{ dayjs(selectedDate).format('YYYY年M月') }}
</span> </span>
<SvgIcon class="rotate-0 font-bold" icon="ic:round-keyboard-arrow-down" /> <SvgIcon class="rotate-0 font-bold" icon="i-ic-round-keyboard-arrow-down" />
</div>
<div class="absolute b h-full flex-col flex items-center justify-center right-0 flex-self-stretch">
<div
class="text-onPrimary text-20 bg-primary py-1 pr-1 pl-2 rounded-l-full flex items-center justify-center line-height-none"
@click="backToToday"
>
回到今日
</div>
</div> </div>
<!-- <n-button
size="small"
text
class="absolute right-0 text-3 !text-subtitle-color"
type="tertiary"
@click="backToToday"
>
回到今日
</n-button> -->
</div> </div>
<div class="relative mt-3 flex flex-row items-center flex-self-stretch px-5.5"> <div
<div class="absolute left-0 h-5 w-5 flex items-center justify-center rounded-full bg-primary" @click="prevWeek"> class="relative mt-8rpx flex flex-row items-center flex-self-stretch"
<SvgIcon class="text-5 text-white font-bold" icon="material-symbols:chevron-left-rounded" /> :style="{
paddingLeft: `${props.timeLineWidth}rpx`,
}"
>
<div
class="absolute h-32 w-32 flex items-center justify-center rounded-full bg-primary left-8rpx" @click="prevWeek"
>
<SvgIcon class="text-28 text-white font-bold" icon="i-material-symbols-chevron-left-rounded" />
</div> </div>
<!-- 周历表 --> <!-- 周历表 -->
<div class="flex flex-1 flex-row items-center justify-between flex-self-stretch px-10"> <div class="flex w-[calc((100%/7.5)*7)] flex-row items-center justify-between flex-self-stretch">
<div <div
v-for="(item, index) in weekDatesForMarks" v-for="(item, index) in weekDatesForMarks"
:key="index" :key="index"
class="flex flex-col cursor-pointer items-center justify-center" class="flex flex-col cursor-pointer flex-1 items-center justify-center"
@click="selectDate(item.date)" @click="selectDate(item.date)"
> >
<span <span
class="text-subtitle-color" class="text-20"
:class="{ :class="{
'text-primary': item.date === selectedDate 'text-primary': item.date === selectedDate,
}" }"
> >
{{ item.weekDay }} {{ item.weekDay }}
</span> </span>
<span <span
class=" font-bold" class="text-20 font-bold"
:class="{ :class="{
'text-primary': item.date === selectedDate 'text-primary': item.date === selectedDate,
}" }"
> >
{{ item.day }} {{ item.day }}
@ -142,13 +148,14 @@ const weekDatesForMarks = computed(() => {
class="mt-0 h-1 w-1 rounded-full" class="mt-0 h-1 w-1 rounded-full"
:class="{ :class="{
'bg-primary': item.isMark, 'bg-primary': item.isMark,
'!bg-[#CCCCCC]': item.isMark && item.date < dayjs().startOf('day').valueOf() '!bg-[#CCCCCC]': item.isMark && item.date < dayjs().startOf('day').valueOf(),
}" }"
/> />
</div> </div>
</div> </div>
<div class="absolute right-0 h-5 w-5 flex items-center justify-center rounded-full bg-primary" @click="nextWeek">
<SvgIcon class="text-5 text-white font-bold" icon="material-symbols:chevron-right-rounded" /> <div class="absolute right-8rpx h-32 w-32 flex items-center justify-center rounded-full bg-primary" @click="nextWeek">
<SvgIcon class="text-5 text-white font-bold" icon="i-material-symbols-chevron-right-rounded" />
</div> </div>
</div> </div>
</div> </div>
@ -156,6 +163,6 @@ const weekDatesForMarks = computed(() => {
<style lang="scss" scoped> <style lang="scss" scoped>
.gantDateShadow { .gantDateShadow {
box-shadow: 0px 8px 20px 10px rgba(0, 0, 0, 0.02); box-shadow: 0rpx 16rpx 40rpx 20rpx rgba(0, 0, 0, 0.02);
} }
</style> </style>

View File

@ -1,42 +1,42 @@
<script lang="ts" setup> <script lang="ts" setup>
import dayjs from 'dayjs';
import type { ThingType } from './type'; import type { ThingType } from './type';
import dayjs from 'dayjs';
defineComponent({ name: 'GantView' });
interface Props {
value?: number; //
yStep?: number;
minuteStep?: number;
weekDates?: { date: number; day: string; weekDay: string }[];
groupedEvents?: Map<number, ThingType[]>;
}
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
yStep: 80, yStep: 80,
minuteStep: 80 / 60, minuteStep: 80 / 60,
value: dayjs().valueOf(), value: dayjs().valueOf(),
weekDates: () => [], weekDates: () => [],
groupedEvents: () => new Map() groupedEvents: () => new Map(),
timeLineWidth: 80,
}); });
const emit = defineEmits<Emits>(); const emit = defineEmits<Emits>();
defineComponent({ name: 'GantView' });
interface Props {
value?: number //
yStep?: number
timeLineWidth?: number
minuteStep?: number
weekDates?: { date: number, day: string, weekDay: string }[]
groupedEvents?: Map<number, ThingType[]>
}
interface Emits { interface Emits {
(e: 'clickThing', value: ThingType): void; (e: 'clickThing', value: ThingType): void
} }
// Y 00:00 - 24:00 , 1H , dayjs 00:00 // Y 00:00 - 24:00 , 1H , dayjs 00:00
const Y_AXIS = Array.from({ length: 24 }, (_, i) => { const Y_AXIS = Array.from({ length: 24 }, (_, i) => {
return { return {
label: dayjs().startOf('day').add(i, 'hour').format('HH:00'), label: dayjs().startOf('day').add(i, 'hour').format('HH'),
top: i * props.yStep top: i * props.yStep,
}; };
}); });
/** ** 正确的格式一定为某一天的起始 *** */ /** ** 正确的格式一定为某一天的起始 */
const selectedDate = computed(() => { const selectedDate = computed(() => {
return dayjs(props.value).startOf('day').valueOf(); return dayjs(props.value).startOf('day').valueOf();
}); });
@ -46,24 +46,18 @@ watch(selectedDate, () => {
}); });
onMounted(() => { onMounted(() => {
nextTick(() => { // nextTick(() => {
scrollToThing(); // scrollToThing();
}); // });
}); });
const scrollbarRef = ref<any | null>(null); function scrollToThing(): number {
function scrollToThing() {
const _thing = props.groupedEvents.get(selectedDate.value); const _thing = props.groupedEvents.get(selectedDate.value);
const currentTime = dayjs().startOf('minute'); const currentTime = dayjs().startOf('minute');
if (!_thing) { if (!_thing) {
scrollbarRef.value?.scrollTo({ return currentTime.diff(currentTime.startOf('day'), 'minute') * props.minuteStep - props.yStep;
top: currentTime.diff(currentTime.startOf('day'), 'minute') * props.minuteStep - props.yStep,
behavior: 'smooth'
});
return;
} }
let closestEvent: ThingType | null = null; let closestEvent: ThingType | null = null;
@ -72,8 +66,8 @@ function scrollToThing() {
for (const event of _thing) { for (const event of _thing) {
const eventTime = dayjs(event.startTime); const eventTime = dayjs(event.startTime);
if ( if (
!closestEvent || !closestEvent
Math.abs(eventTime.diff(currentTime)) < Math.abs(dayjs(closestEvent.startTime).diff(currentTime)) || Math.abs(eventTime.diff(currentTime)) < Math.abs(dayjs(closestEvent.startTime).diff(currentTime))
) { ) {
closestEvent = event; closestEvent = event;
} }
@ -98,33 +92,45 @@ function scrollToThing() {
closestEvent = _thing[0]; closestEvent = _thing[0];
} }
// if (!closestEvent.toTop)
if (closestEvent && closestEvent.toTop) { closestEvent.toTop = 0;
scrollbarRef.value?.scrollTo({ return closestEvent.toTop;
top: closestEvent!.toTop - props.yStep,
behavior: 'smooth'
});
}
} }
const scrollTop = computed(() => {
const top = uni.upx2px(scrollToThing()) - props.yStep / 4;
return top;
});
</script> </script>
<template> <template>
<div class="relative flex-1 border border-red border-solid"> <div class="relative flex-1">
<div ref="scrollbarRef" class="absolute left-0 top-0 w-full h-full"> <scroll-view
:scroll-anchoring="true"
:scroll-with-animation="true"
:scroll-top="scrollTop"
:scroll-y="true"
class="absolute left-0 top-0 w-full h-full "
>
<div class="flex flex-row"> <div class="flex flex-row">
<!-- time line --> <!-- time line -->
<div class="flex flex-col pl-2 pr-1"> <div
:style="{
width: `${props.timeLineWidth}rpx`,
}"
class="flex flex-col"
>
<div <div
v-for="(item, inex) in Y_AXIS" v-for="(item, inex) in Y_AXIS"
:key="inex" :key="inex"
:style="{ :style="{
height: `${props.yStep}rpx` height: `${props.yStep}rpx`,
}" }"
> >
<span <span
class="relative block translate-y-[-50%] text-[#999999FF] line-height-none" class="relative block text-20rpx translate-y-[-50%] text-[#999999FF] line-height-none text-center"
:class="{ :class="{
'translate-y-0': inex === 0 'translate-y-0': inex === 0,
}" }"
> >
{{ item.label }} {{ item.label }}
@ -137,8 +143,10 @@ function scrollToThing() {
<div <div
class="absolute left-0 top-0 z-1 w-full h-full" class="absolute left-0 top-0 z-1 w-full h-full"
:style="{ :style="{
'background-image': `linear-gradient(to right, #f7f7f7 1rpx, transparent 1rpx, transparent calc(100% / 7.5)),linear-gradient(to bottom, #f7f7f7 1rpx, transparent 1rpx, transparent ${props.yStep}rpx)`, 'background-image': `linear-gradient(to right, #f7f7f7 1rpx,
'background-size': `calc(100%/7.5) ${props.yStep}rpx` transparent 1rpx, transparent calc(100% / 7.5)),
linear-gradient(to bottom, #f7f7f7 1rpx, transparent 1rpx, transparent ${props.yStep}rpx)`,
'background-size': `calc(100%/7.5) ${props.yStep}rpx`,
}" }"
/> />
<!-- 事件组 分为 7day --> <!-- 事件组 分为 7day -->
@ -153,24 +161,23 @@ function scrollToThing() {
:key="index" :key="index"
class="relative flex flex-1 transition-background-color will-change-background-color" class="relative flex flex-1 transition-background-color will-change-background-color"
> >
<!-- 'background-color': addColorAlpha(itm.color, 0.06) --> <!-- 'background-color': addColorAlpha(itm.color, 0.06) -->
<div <div
v-for="itm in props.groupedEvents.get(item.date)" v-for="itm in props.groupedEvents.get(item.date)"
:key="itm.id" :key="itm.id"
class="absolute w-full flex-shrink-0 cursor-pointer flex-self-stretch py-1" class="absolute w-full flex-shrink-0 cursor-pointer flex-self-stretch"
:style="{ :style="{
top: `${itm.toTop}rpx`, top: `${itm.toTop}rpx`,
height: `${itm.height}rpx`, height: `${itm.height}rpx`,
background: itm.color, background: itm.color,
borderTop: `2rpx solid ${itm.color}`, borderTop: `0rpx solid ${itm.color}`,
}" }"
@click="emit('clickThing', itm)" @click="emit('clickThing', itm)"
> >
<span <span
class="text-3" class="text-28 !text-white"
:style="{ :style="{
color: itm.color color: itm.color,
}" }"
> >
{{ itm.name }} {{ itm.name }}
@ -180,7 +187,7 @@ function scrollToThing() {
</div> </div>
</div> </div>
</div> </div>
</div> </scroll-view>
</div> </div>
</template> </template>

View File

@ -0,0 +1,74 @@
<!--
## File: /src/components/WeekGantView/demo.vue
Project: uniapp_vue3_vite_ts
Created Date: 2024-10-02 19:57:19
Author: KuroSago
## Last Modified: 2024-10-02 19:57:19
Modified By:
## Copyright (c) 2024 self.
Use To:
-->
<script lang="ts" setup>
defineComponent({ name: 'WeekGantViewDemo' });
const D = dayjs();
const value = ref(D.valueOf());
const things = ref([
{
id: 1,
name: '测试1',
startTime: D.hour(12).minute(0).second(0).millisecond(0).valueOf(),
endTime: D.hour(14).minute(0).second(0).millisecond(0).valueOf(),
dec: '测试1',
color: 'rgba(255, 0, 0, 0.5)',
},
]);
//
function getRandomTimeWithinWeek() {
const now = dayjs();
const startOfWeek = now.startOf('week');
const endOfWeek = now.endOf('week');
const randomTime = startOfWeek.add(Math.random() * (endOfWeek.diff(startOfWeek)), 'millisecond');
return randomTime;
}
function getRandomColor() {
const r = Math.floor(Math.random() * 256);
const g = Math.floor(Math.random() * 256);
const b = Math.floor(Math.random() * 256);
const a = Math.random().toFixed(2); //
return `rgba(${r}, ${g}, ${b}, ${a})`;
}
function addThing() {
const startTime = getRandomTimeWithinWeek();
const endTime = startTime.add(Math.random() * 2, 'hour');
things.value.push({
id: things.value.length + 1,
name: `测试${things.value.length + 1}`,
startTime: startTime.valueOf(),
endTime: endTime.valueOf(),
dec: `测试${things.value.length + 1}`,
color: getRandomColor(),
});
}
</script>
<template>
<button class="" @click="addThing">
addThing
</button>
<div class="h-660rpx flex flex-col mx-4 bg-primary-100 rounded-3 shadow overflow-clip ">
<week-gant-view v-model:value="value" v-model:things="things" />
</div>
</template>

View File

@ -1,25 +1,15 @@
import dayjs from 'dayjs';
import type { ThingType } from './type'; import type { ThingType } from './type';
import isoWeek from 'dayjs/plugin/isoWeek'; import dayjs from 'dayjs';
import weekday from 'dayjs/plugin/weekday'; import 'dayjs/locale/zh-cn';
export function getWeekDates(date: string | number | Date | dayjs.Dayjs | null | undefined) { export function getWeekDates(date: string | number | Date | dayjs.Dayjs | null | undefined) {
dayjs.extend(isoWeek);
dayjs.extend(weekday);
const startOfWeek = dayjs(date).weekday(0); const startOfWeek = dayjs(date).weekday(0);
const _weekDates: { date: number; day: string; weekDay: string }[] = []; const _weekDates: { date: number, day: string, weekDay: string }[] = [];
for (let i = 0; i < 7; i += 1) { for (let i = 0; i < 7; i += 1) {
_weekDates.push({ _weekDates.push({
date: startOfWeek.add(i, 'day').startOf('day').valueOf(), date: startOfWeek.add(i, 'day').startOf('day').valueOf(),
day: startOfWeek.add(i, 'day').format('D'), day: startOfWeek.add(i, 'day').format('D'),
weekDay: startOfWeek.add(i, 'day').format('周dd') weekDay: startOfWeek.add(i, 'day').format('ddd'),
}); });
} }
return _weekDates; return _weekDates;
@ -27,14 +17,14 @@ export function getWeekDates(date: string | number | Date | dayjs.Dayjs | null |
export function groupEventsByDay({ export function groupEventsByDay({
events, events,
minuteStep minuteStep,
}: { }: {
events: ThingType[]; events: ThingType[]
minuteStep: number; minuteStep: number
}): Map<number, ThingType[]> { }): Map<number, ThingType[]> {
const _groupedEvents = new Map(); const _groupedEvents = new Map();
events.forEach(event => { events.forEach((event) => {
const { startTime, endTime } = event; const { startTime, endTime } = event;
const startOfDay = dayjs(startTime).startOf('day').valueOf(); const startOfDay = dayjs(startTime).startOf('day').valueOf();
@ -50,7 +40,7 @@ export function groupEventsByDay({
_groupedEvents.get(startOfDay).push({ _groupedEvents.get(startOfDay).push({
...event, ...event,
toTop: toTop * minuteStep, toTop: toTop * minuteStep,
height: duration * minuteStep height: duration * minuteStep,
}); });
}); });

View File

@ -1,48 +1,49 @@
<script lang="ts" setup> <script lang="ts" setup>
import dayjs from 'dayjs';
import type { ThingType } from './type'; import type { ThingType } from './type';
import { getWeekDates, groupEventsByDay } from './helper'; import dayjs from 'dayjs';
import GantDate from './GantDate.vue'; import GantDate from './GantDate.vue';
import GantView from './GantView.vue'; import GantView from './GantView.vue';
import { getWeekDates, groupEventsByDay } from './helper';
defineComponent({ name: 'WeekGantView' });
const emit = defineEmits<Emits>(); const emit = defineEmits<Emits>();
defineComponent({ name: 'WeekGantView' });
interface Emits { interface Emits {
( (
e: 'update:value', e: 'update:value',
value: number, value: number,
params: { params: {
isSameWeek: boolean; isSameWeek: boolean
} }
): void; ): void
(e: 'clickThing', value: ThingType): void; (e: 'clickThing', value: ThingType): void
} }
// Y px , 1H = 80px // Y px , 1H = 80px
const Y_STEP = 80; const Y_STEP = 80;
// time line
const TIME_LINE_WIDTH = 44;
// //
const MINUTE_STEP = Y_STEP / 60; const MINUTE_STEP = Y_STEP / 60;
const value = defineModel('value', { const value = defineModel('value', {
type: Number, type: Number,
default: dayjs().valueOf() default: dayjs().valueOf(),
}); });
const selectedDate = ref(value.value); const selectedDate = ref(value.value);
const loading = defineModel('loading', { // const loading = defineModel('loading', {
type: Boolean, // type: Boolean,
default: false // default: false,
}); // });
const things = defineModel('things', { const things = defineModel('things', {
type: Object as () => ThingType[], type: Object as () => ThingType[],
default: () => [] default: () => [],
}); });
const weekDates = ref(getWeekDates(selectedDate.value)); const weekDates = ref(getWeekDates(selectedDate.value));
@ -50,8 +51,8 @@ const weekDates = ref(getWeekDates(selectedDate.value));
const groupedEvents = computed(() => const groupedEvents = computed(() =>
groupEventsByDay({ groupEventsByDay({
events: things.value, events: things.value,
minuteStep: MINUTE_STEP minuteStep: MINUTE_STEP,
}) }),
); );
watch(selectedDate, (n_val, o_val) => { watch(selectedDate, (n_val, o_val) => {
@ -63,14 +64,15 @@ watch(selectedDate, (n_val, o_val) => {
<template> <template>
<div :delay="1000" content-class="flex flex-1 flex-col" class="flex flex-1 flex-col"> <div :delay="1000" content-class="flex flex-1 flex-col" class="flex flex-1 flex-col">
<gant-date v-model:value="selectedDate" v-model:weekDates="weekDates" :grouped-events="groupedEvents" /> <GantDate v-model:value="selectedDate" v-model:week-dates="weekDates" :grouped-events="groupedEvents" :time-line-width="TIME_LINE_WIDTH" />
<gant-view <GantView
v-if="things.length" v-if="things.length"
:grouped-events="groupedEvents" :grouped-events="groupedEvents"
:week-dates="weekDates" :week-dates="weekDates"
:y-step="Y_STEP" :y-step="Y_STEP"
:minute-step="MINUTE_STEP" :minute-step="MINUTE_STEP"
:value="selectedDate" :value="selectedDate"
:time-line-width="TIME_LINE_WIDTH"
@click-thing=" @click-thing="
e => { e => {
emit('clickThing', e); emit('clickThing', e);

View File

@ -1,4 +1,6 @@
import { createSSRApp } from 'vue'; import { createSSRApp } from 'vue';
import isoWeek from 'dayjs/plugin/isoWeek';
import weekday from 'dayjs/plugin/weekday';
import App from './App.vue'; import App from './App.vue';
import { setupRouter } from './router'; import { setupRouter } from './router';
import { setupStore } from './stores'; import { setupStore } from './stores';
@ -10,6 +12,10 @@ export function createApp() {
setupStore(app); setupStore(app);
app.mixin(themeMixin); app.mixin(themeMixin);
setupRouter(app); setupRouter(app);
dayjs.extend(isoWeek);
dayjs.extend(weekday);
return { return {
app, app,
}; };

View File

@ -9,15 +9,16 @@
## Last Modified: 2024-06-24 11:30:88 ## Last Modified: 2024-06-24 11:30:88
Modified By: Administrator Modified By: kurosago
## Copyright (c) 2024 self. ## Copyright (c) 2024 self.
Use To: 启动页面 Use To: 启动页面
--> -->
<script lang="ts" setup> <script lang="ts" setup>
import { useUserStore } from '@/stores/modules/user'; import WeekGantViewDemo from '@/components/WeekGantView/demo.vue';
import { useSystemInfoStore } from '@/stores/modules/system'; import { useSystemInfoStore } from '@/stores/modules/system';
import { useUserStore } from '@/stores/modules/user';
const { changeThemeModel } = useSystemInfoStore(); const { changeThemeModel } = useSystemInfoStore();
@ -63,41 +64,25 @@ function toNumerology() {
name: 'Numerology', name: 'Numerology',
}); });
} }
const D = dayjs();
const things = ref([
{
id: 1,
name: '测试1',
startTime: D.startOf('day').valueOf(),
endTime: D.endOf('day').valueOf(),
dec: '测试1',
color: 'rgba(255, 0, 0, 0.5)',
},
]);
const value = ref(D.valueOf());
</script> </script>
<template> <template>
<view class="bg-primary-90 flex-1"> <view class="bg-background flex-1">
<div class="h-600rpx flex flex-col border border-red border-solid" > <div class="flex flex-col gap-4 mt-32rpx">
<week-gant-view v-model:value="value" v-model:things="things"/> <WeekGantViewDemo />
<button @click="changeTheme">
changeTheme
</button>
<button @click="toColorCard">
色卡
</button>
<button @click="toNumerology">
算命
</button>
</div> </div>
<!-- <span @click="changeTheme">
changeTheme
</span> -->
<!-- <button @click="toColorCard">
色卡
</button>
<button @click="toNumerology">
算命
</button> -->
</view> </view>
</template> </template>

View File

@ -10,6 +10,7 @@ declare module 'vue' {
AppProvider: typeof import('./../components/AppProvider/index.vue')['default'] AppProvider: typeof import('./../components/AppProvider/index.vue')['default']
BasicButton: typeof import('./../components/BasicButton/index.vue')['default'] BasicButton: typeof import('./../components/BasicButton/index.vue')['default']
CourseDetailGant: typeof import('./../components/CourseDetailGant/index.vue')['default'] CourseDetailGant: typeof import('./../components/CourseDetailGant/index.vue')['default']
Demo: typeof import('./../components/WeekGantView/demo.vue')['default']
Gant: typeof import('./../components/Gant/index.vue')['default'] Gant: typeof import('./../components/Gant/index.vue')['default']
GantDate: typeof import('./../components/WeekGantView/GantDate.vue')['default'] GantDate: typeof import('./../components/WeekGantView/GantDate.vue')['default']
GantView: typeof import('./../components/WeekGantView/GantView.vue')['default'] GantView: typeof import('./../components/WeekGantView/GantView.vue')['default']

View File

@ -34,7 +34,9 @@ export default defineConfig(async ({ mode }) => {
css: { css: {
preprocessorOptions: { preprocessorOptions: {
scss: { scss: {
additionalData: '@import "./src/uni.scss";', // additionalData: '@import "./src/uni.scss";',
quietDeps: true,
api: 'modern-compiler',
}, },
}, },
@ -44,7 +46,6 @@ export default defineConfig(async ({ mode }) => {
server: { server: {
// host: true, // host: true,
open: true, open: true,
https: true,
port: Number.parseInt(VITE_PORT!, 10), port: Number.parseInt(VITE_PORT!, 10),
proxy: resolveProxy([[VITE_PROXY_PREFIX! as string, VITE_BASE_URL! as string], [VITE_UPLOAD_PROXY_PREFIX! as string, VITE_UPLOAD_URL! as string]]), proxy: resolveProxy([[VITE_PROXY_PREFIX! as string, VITE_BASE_URL! as string], [VITE_UPLOAD_PROXY_PREFIX! as string, VITE_UPLOAD_URL! as string]]),
}, },