19 changed files with 1002 additions and 123 deletions
@ -0,0 +1,5 @@ |
|||
import { createContext } from "@/hooks/createContext/createContext.uts" |
|||
|
|||
const key = "x-calendar--item" |
|||
const { Provider, Consumer, useContext } = createContext(key) |
|||
export { Provider, Consumer, useContext } |
|||
@ -0,0 +1,103 @@ |
|||
|
|||
export function getWeek(time : Date) { |
|||
let week = time.getDay() // 当前周几 |
|||
// week = (week - 1) < 0 ? 0 : (week - 1) |
|||
if(week == 0) week = 7 // 0 表示周日 |
|||
return week |
|||
} |
|||
|
|||
export function getCurrentMonth(time : Date) : number { |
|||
let month = time.getMonth() + 1 |
|||
return month |
|||
} |
|||
|
|||
export function getLastMonth(curDate : Date) { |
|||
let lastDate = new Date() |
|||
lastDate.setMonth(curDate.getMonth() - 1) |
|||
return lastDate |
|||
} |
|||
|
|||
export function getLastMonthDay(curDate : Date, offset : number) { |
|||
let lastDate = new Date(curDate.getTime()) |
|||
lastDate.setHours(-24 * offset); |
|||
return lastDate |
|||
} |
|||
|
|||
export function getNextMonthDay(curDate : Date, offset : number) { |
|||
let lastDate = new Date(curDate.getTime()) |
|||
lastDate.setHours(24 * offset); |
|||
return lastDate |
|||
} |
|||
|
|||
|
|||
export function format(time : Date) { |
|||
return `${time.getFullYear()}-${time.getMonth() + 1}-${time.getDate()}` |
|||
} |
|||
|
|||
export function formatFull(time : Date) { |
|||
const year = (time.getFullYear()) + "" |
|||
const month = (time.getMonth() + 1) + "" |
|||
const date = (time.getDate()) + "" |
|||
const hour = (time.getHours()) + "" |
|||
const minute = (time.getMinutes()) + "" |
|||
const second = (time.getSeconds()) + "" |
|||
return `${year}-${month.padStart(2, '0')}-${date.padStart(2, '0')} ${hour.padStart(2, '0')}:${minute.padStart(2, '0')}:${second.padStart(2, '0')}` |
|||
} |
|||
|
|||
// 获取当前月份应该有多少天 |
|||
export function getCurMonthDayNum(time : Date) : number { |
|||
let day = 31; |
|||
let month = time.getMonth() + 1 |
|||
let year = time.getFullYear() |
|||
if ([1, 3, 5, 7, 8, 10, 12].indexOf(month) != -1) { |
|||
day = 31; |
|||
} else if ([4, 6, 9, 11].indexOf(month) != -1) { |
|||
day = 30; |
|||
} else if (year % 4 == 0) { |
|||
day = 29 |
|||
} else { |
|||
day = 28 |
|||
} |
|||
return day |
|||
} |
|||
|
|||
export function generateDate<T = Date>(_nowDate : Date | string | number, format : (date : Date) => T = (v : Date) => (v as T)) { |
|||
if (typeof _nowDate === "string" || typeof _nowDate === "number") { |
|||
_nowDate = new Date(_nowDate as string) |
|||
} |
|||
|
|||
const allDate : T[] = [] |
|||
|
|||
let firstDate = new Date(_nowDate.getTime()) |
|||
firstDate.setDate(1) |
|||
const firstWeek = getWeek(firstDate) |
|||
console.log(firstDate.getTime()); |
|||
for (var i = 1; i < firstWeek; i++) { |
|||
const date = getLastMonthDay(firstDate, i) |
|||
allDate.unshift(format(new Date(date.getTime())) as T) |
|||
} |
|||
|
|||
let curDate = new Date(_nowDate.getTime()) |
|||
const allDay = getCurMonthDayNum(curDate) |
|||
for (var i = 1; i <= getCurMonthDayNum(curDate); i++) { |
|||
curDate.setDate(i) |
|||
allDate.push(format(new Date(curDate.getTime())) as T) |
|||
} |
|||
|
|||
let endDate = new Date(_nowDate.getTime()) |
|||
endDate.setDate(allDay) |
|||
const endWeek = 7 - getWeek(endDate) |
|||
for (var i = 1; i <= endWeek; i++) { |
|||
const date = getNextMonthDay(endDate, i) |
|||
allDate.push(format(new Date(date.getTime())) as T) |
|||
} |
|||
// 至少6行 |
|||
if (~~(allDate.length / 7) < 6) { |
|||
for (var i = endWeek + 1; i < endWeek + 1 + 7; i++) { |
|||
const date = getNextMonthDay(endDate, i) |
|||
allDate.push(format(new Date(date.getTime())) as T) |
|||
} |
|||
} |
|||
|
|||
return allDate |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
<template> |
|||
<Context.Consumer> |
|||
<template v-slot="data"> |
|||
{{data.label}} |
|||
</template> |
|||
</Context.Consumer> |
|||
</template> |
|||
|
|||
<script setup lang="uts"> |
|||
import Context from "./context.uts" |
|||
</script> |
|||
|
|||
<style> |
|||
|
|||
</style> |
|||
@ -0,0 +1,63 @@ |
|||
<template> |
|||
<view class="x-calendar"> |
|||
<view class="grid"> |
|||
{{all.length}} |
|||
<view class="grid-item" v-for="(day, index) in all" :key="index"> |
|||
<view class="grid-item-box"> |
|||
<view class="grid-item-content"> |
|||
<Provider contextKey="x-calendar--item" :value="day">1 |
|||
<Consumer contextKey="x-calendar--item"> |
|||
<template v-slot="data"> |
|||
{{data["label"]}} |
|||
</template> |
|||
</Consumer> |
|||
<!-- <slot><text class="grid-item-text">{{ day.label }}</text></slot> --> |
|||
</Provider> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script setup lang="uts"> |
|||
import { format, generateDate } from "./util.uts" |
|||
import { useContext } from "./context.uts" |
|||
import Consumer from "@/hooks/createContext/Consumer.uvue" |
|||
import Provider from "@/hooks/createContext/Provider.uvue" |
|||
|
|||
const props = withDefaults(defineProps<{ |
|||
current ?: string |
|||
}>(), { |
|||
current: format(new Date()) |
|||
}) |
|||
|
|||
interface TItem { label : string; value : Date; } |
|||
|
|||
const currentDate = computed(() => { |
|||
return new Date(props.current) |
|||
}) |
|||
|
|||
const all = computed<any[]>(() => { |
|||
const Fn = (v : Date) : any => ({ |
|||
label: v.getDate() + "", |
|||
value: v |
|||
} as any) |
|||
return generateDate<any>(format(currentDate.value), Fn as (v: Date)=>any) |
|||
}) |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.grid { |
|||
display: flex; |
|||
flex-direction: row; |
|||
flex-wrap: wrap; |
|||
|
|||
.grid-item { |
|||
width: calc(100% / 6); |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,308 @@ |
|||
<template> |
|||
<view class="container"> |
|||
<view class="page-body"> |
|||
<view class='wrapper'> |
|||
<view class='toolbar' @tap="format"> |
|||
<view :class="formats.bold ? 'ql-active' : ''" class="iconfont icon-zitijiacu" data-name="bold"> |
|||
</view> |
|||
<view :class="formats.italic ? 'ql-active' : ''" class="iconfont icon-zitixieti" data-name="italic"> |
|||
</view> |
|||
<view :class="formats.underline ? 'ql-active' : ''" class="iconfont icon-zitixiahuaxian" |
|||
data-name="underline"></view> |
|||
<view :class="formats.strike ? 'ql-active' : ''" class="iconfont icon-zitishanchuxian" |
|||
data-name="strike"> |
|||
</view> |
|||
|
|||
<view :class="formats.align === 'left' ? 'ql-active' : ''" class="iconfont icon-zuoduiqi" |
|||
data-name="align" data-value="left"></view> |
|||
|
|||
<view :class="formats.align === 'center' ? 'ql-active' : ''" class="iconfont icon-juzhongduiqi" |
|||
data-name="align" data-value="center"></view> |
|||
<view :class="formats.align === 'right' ? 'ql-active' : ''" class="iconfont icon-youduiqi" |
|||
data-name="align" data-value="right"></view> |
|||
<view :class="formats.align === 'justify' ? 'ql-active' : ''" class="iconfont icon-zuoyouduiqi" |
|||
data-name="align" data-value="justify"></view> |
|||
|
|||
<view :class="formats.lineHeight ? 'ql-active' : ''" class="iconfont icon-line-height" |
|||
data-name="lineHeight" data-value="2"></view> |
|||
<view :class="formats.letterSpacing ? 'ql-active' : ''" class="iconfont icon-Character-Spacing" |
|||
data-name="letterSpacing" data-value="2em"></view> |
|||
<view :class="formats.marginTop ? 'ql-active' : ''" class="iconfont icon-722bianjiqi_duanqianju" |
|||
data-name="marginTop" data-value="20px"></view> |
|||
<view :class="formats.marginBottom ? 'ql-active' : ''" class="iconfont icon-723bianjiqi_duanhouju" |
|||
data-name="marginBottom" data-value="20px"></view> |
|||
|
|||
<view :class="formats.fontFamily ? 'ql-active' : ''" class="iconfont icon-font" |
|||
data-name="fontFamily" data-value="Pacifico"></view> |
|||
<view :class="formats.fontSize === '24px' ? 'ql-active' : ''" class="iconfont icon-fontsize" |
|||
data-name="fontSize" data-value="24px"></view> |
|||
<view :class="formats.color === '#0000ff' ? 'ql-active' : ''" class="iconfont icon-text_color" |
|||
data-name="color" data-value="#0000ff"></view> |
|||
<view :class="formats.backgroundColor === '#00ff00' ? 'ql-active' : ''" |
|||
class="iconfont icon-fontbgcolor" data-name="backgroundColor" data-value="#00ff00"></view> |
|||
|
|||
<view class="iconfont icon--checklist" data-name="list" data-value="check"></view> |
|||
<view :class="formats.list === 'ordered' ? 'ql-active' : ''" class="iconfont icon-youxupailie" |
|||
data-name="list" data-value="ordered"></view> |
|||
<view :class="formats.list === 'bullet' ? 'ql-active' : ''" class="iconfont icon-wuxupailie" |
|||
data-name="list" data-value="bullet"></view> |
|||
|
|||
|
|||
<view class="iconfont icon-outdent" data-name="indent" data-value="-1"></view> |
|||
<view class="iconfont icon-indent" data-name="indent" data-value="+1"></view> |
|||
|
|||
|
|||
<view :class="formats.header === 1 ? 'ql-active' : ''" class="iconfont icon-format-header-1" |
|||
data-name="header" :data-value="1"></view> |
|||
<view :class="formats.script === 'sub' ? 'ql-active' : ''" class="iconfont icon-zitixiabiao" |
|||
data-name="script" data-value="sub"></view> |
|||
<view :class="formats.script === 'super' ? 'ql-active' : ''" class="iconfont icon-zitishangbiao" |
|||
data-name="script" data-value="super"></view> |
|||
|
|||
<view :class="formats.direction === 'rtl' ? 'ql-active' : ''" class="iconfont icon-direction-rtl" |
|||
data-name="direction" data-value="rtl"></view> |
|||
|
|||
<view class="iconfont icon-date" @tap="insertDate"></view> |
|||
<view class="iconfont icon-fengexian" @tap="insertDivider"></view> |
|||
<view class="iconfont icon-charutupian" @tap="chooseInsertImage"></view> |
|||
<view class="iconfont icon-clearedformat" @tap="removeFormat"></view> |
|||
<view class="iconfont icon-undo" @tap="undo"></view> |
|||
<view class="iconfont icon-redo" @tap="redo"></view> |
|||
<view class="iconfont icon-shanchu" @tap="clearShowModal"></view> |
|||
</view> |
|||
|
|||
<view class="editor-wrapper"> |
|||
<editor id="editor" class="ql-container" placeholder="开始输入..." show-img-size show-img-toolbar |
|||
show-img-resize @statuschange="onStatusChange" :read-only="readOnly" @ready="onEditorReady"> |
|||
</editor> |
|||
</view> |
|||
<view> |
|||
<button @tap="getCon">控制台打印文本内容</button> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
type Context = { |
|||
id ?: string; |
|||
pageId ?: number; |
|||
} |
|||
|
|||
export default { |
|||
data() { |
|||
return { |
|||
readOnly: false, |
|||
formats: {}, |
|||
editorCtx: {} as Context, |
|||
// 自动化测试 |
|||
autoTest: false, |
|||
undoTest: false, |
|||
redoTest: false, |
|||
removeFormatTest: false, |
|||
insertImageTest: false, |
|||
blurTest: false |
|||
} |
|||
}, |
|||
onLoad() { |
|||
uni.loadFontFace({ |
|||
family: 'Pacifico', |
|||
source: 'url("/static/font/Pacifico-Regular.ttf")', |
|||
success() { |
|||
console.log('success load font') |
|||
}, |
|||
fail() { |
|||
console.log('fail load font load') |
|||
} |
|||
}) |
|||
}, |
|||
methods: { |
|||
readOnlyChange() { |
|||
this.readOnly = !this.readOnly |
|||
}, |
|||
onEditorReady() { |
|||
uni.createSelectorQuery().select('#editor').context((res) => { |
|||
this.editorCtx = res.context |
|||
}).exec() |
|||
}, |
|||
// 自动化测试专用 |
|||
setContents(options) { |
|||
this.editorCtx.setContents({ |
|||
delta: { |
|||
ops: options |
|||
}, |
|||
success: (res) => { |
|||
console.log('setContents-success', res) |
|||
}, |
|||
fail: (err) => { |
|||
console.log(err) |
|||
} |
|||
}) |
|||
}, |
|||
blur() { |
|||
this.editorCtx.blur({ |
|||
success: (res) => { |
|||
console.log('编辑器失焦:', res) |
|||
this.blurTest = true |
|||
}, |
|||
fail: (err) => { |
|||
console.log(err) |
|||
} |
|||
}) |
|||
}, |
|||
getCon() { |
|||
this.editorCtx.getContents({ |
|||
success: (res) => { |
|||
console.log('文本详情:', res) |
|||
}, |
|||
fail: (err) => { |
|||
console.log(err) |
|||
} |
|||
}) |
|||
}, |
|||
undo() { |
|||
this.editorCtx.undo({ |
|||
success: (res) => { |
|||
this.undoTest = true |
|||
}, |
|||
fail: (err) => { |
|||
this.undoTest = false |
|||
} |
|||
}) |
|||
}, |
|||
redo() { |
|||
this.editorCtx.redo({ |
|||
success: (res) => { |
|||
this.redoTest = true |
|||
}, |
|||
fail: (err) => { |
|||
this.redoTest = false |
|||
} |
|||
}) |
|||
}, |
|||
format(e) { |
|||
let { name, value } = e.target.dataset |
|||
if (!name) return |
|||
// console.log('format', name, value) |
|||
this.editorCtx.format(name, value) |
|||
}, |
|||
onStatusChange(e) { |
|||
const formats = e.detail |
|||
this.formats = formats |
|||
}, |
|||
insertDivider() { |
|||
this.editorCtx.insertDivider({ |
|||
success: function () { |
|||
console.log('insert divider success') |
|||
} |
|||
}) |
|||
}, |
|||
clear() { |
|||
this.editorCtx.clear({ |
|||
success: function (res) { |
|||
console.log("clear success") |
|||
} |
|||
}) |
|||
}, |
|||
clearShowModal() { |
|||
uni.showModal({ |
|||
title: '清空编辑器', |
|||
content: '确定清空编辑器全部内容?', |
|||
success: res => { |
|||
if (res.confirm) { |
|||
this.clear() |
|||
} |
|||
} |
|||
}) |
|||
}, |
|||
removeFormat() { |
|||
this.editorCtx.removeFormat({ |
|||
success: (res) => { |
|||
console.log('removeFormat-success', res) |
|||
this.removeFormatTest = true |
|||
}, |
|||
fail: (err) => { |
|||
this.removeFormatTest = false |
|||
} |
|||
}) |
|||
}, |
|||
insertDate() { |
|||
const date = new Date() |
|||
const formatDate = `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}` |
|||
this.editorCtx.insertText({ |
|||
text: formatDate |
|||
}) |
|||
}, |
|||
insertImage(image) { |
|||
this.editorCtx.insertImage({ |
|||
src: image, |
|||
alt: '图像', |
|||
success: () => { |
|||
console.log('insert image success') |
|||
this.insertImageTest = true |
|||
} |
|||
}) |
|||
}, |
|||
chooseInsertImage() { |
|||
uni.chooseImage({ |
|||
count: 1, |
|||
success: (res) => { |
|||
this.insertImage(res.tempFilePaths[0]) |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
/* @import "./editor-icon.css"; */ |
|||
|
|||
.page-body { |
|||
height: calc(100vh - var(--window-top) - var(--status-bar-height)); |
|||
} |
|||
|
|||
.wrapper { |
|||
height: 100%; |
|||
} |
|||
|
|||
.editor-wrapper { |
|||
height: calc(100vh - var(--window-top) - var(--status-bar-height) - 140px - 46px); |
|||
background: #fff; |
|||
} |
|||
|
|||
.iconfont { |
|||
display: inline-block; |
|||
width: 30px; |
|||
height: 30px; |
|||
cursor: pointer; |
|||
font-size: 20px; |
|||
margin: 0px 6px; |
|||
align-content: center; |
|||
} |
|||
|
|||
.toolbar { |
|||
box-sizing: border-box; |
|||
border-bottom: 0; |
|||
font-family: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif; |
|||
flex-direction: row; |
|||
flex-wrap: wrap; |
|||
height: 140px; |
|||
padding-left: 10px; |
|||
} |
|||
|
|||
.ql-container { |
|||
box-sizing: border-box; |
|||
padding: 12px 15px; |
|||
width: 100%; |
|||
min-height: 30vh; |
|||
height: 100%; |
|||
font-size: 16px; |
|||
line-height: 1.5; |
|||
} |
|||
|
|||
.ql-active { |
|||
color: #06c; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,30 @@ |
|||
<template> |
|||
<view class="x-mask"> |
|||
|
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name:"x-mask", |
|||
data() { |
|||
return { |
|||
|
|||
}; |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped lang="scss"> |
|||
|
|||
.x-mask { |
|||
position: fixed; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
top: 0; |
|||
background-color: rgba(0, 0, 0, .2); |
|||
z-index: 999; |
|||
} |
|||
|
|||
</style> |
|||
@ -0,0 +1,66 @@ |
|||
<template> |
|||
<view class="x-slide-menu" v-if="modelValue"> |
|||
<x-mask @click="handleHide"></x-mask> |
|||
<div class="x-slide-menu__content"></div> |
|||
</view> |
|||
</template> |
|||
|
|||
<script setup lang="uts"> |
|||
const props = withDefaults(defineProps<{ |
|||
contentWidth ?: string, |
|||
modelValue : boolean |
|||
}>(), { |
|||
contentWidth: "80%" |
|||
|
|||
}) |
|||
|
|||
const emit = defineEmits<{ |
|||
(e: 'open'): void |
|||
(e: 'close'): void |
|||
(e: 'update:modelValue', isShow: boolean): void |
|||
}>() |
|||
|
|||
watch(() => props.modelValue, (v) => { |
|||
if (v) { |
|||
emit("open") |
|||
} else { |
|||
emit("close") |
|||
} |
|||
}) |
|||
|
|||
const state = reactive({}) |
|||
|
|||
const computedContentStyle = computed(() => { |
|||
return { |
|||
width: props.contentWidth |
|||
} |
|||
}) |
|||
|
|||
function handleHide() { |
|||
emit("update:modelValue", false) |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.x-slide-menu { |
|||
position: fixed; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
top: 0; |
|||
width: 100%; |
|||
height: 100%; |
|||
z-index: 100; |
|||
|
|||
&__content { |
|||
position: absolute; |
|||
left: 0; |
|||
top: 0; |
|||
bottom: 0; |
|||
width: 70%; |
|||
height: 100%; |
|||
background-color: white; |
|||
z-index: 1000; |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,26 @@ |
|||
<template> |
|||
<view> |
|||
|
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
props: { |
|||
contextKey: { |
|||
type: String, |
|||
required: true, |
|||
} |
|||
}, |
|||
setup(props) { |
|||
const slots = useSlots() |
|||
const data = inject(props.contextKey) |
|||
const FN : () => VNode = () => renderSlot(slots, 'default', data) |
|||
return FN |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
|
|||
</style> |
|||
@ -0,0 +1,17 @@ |
|||
<template></template> |
|||
|
|||
<script> |
|||
export default { |
|||
props: ["value", "contextKey"], |
|||
setup(props, context) { |
|||
const slots = useSlots() |
|||
provide(props.contextKey, props.value) |
|||
const FN : () => VNode = () => renderSlot(slots, 'default') |
|||
return () : VNode => h(FN) |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
|
|||
</style> |
|||
@ -0,0 +1,21 @@ |
|||
<script> |
|||
export default { |
|||
name: "asd", |
|||
props: { |
|||
value: { |
|||
type: Object, |
|||
required: true, |
|||
}, |
|||
contextKey: { |
|||
type: String, |
|||
required: true, |
|||
} |
|||
}, |
|||
setup(props) { |
|||
const slots = useSlots() |
|||
provide(props.contextKey, props.value) |
|||
const FN : () => VNode = () => renderSlot(slots, 'default') |
|||
return FN |
|||
} |
|||
} |
|||
</script> |
|||
@ -0,0 +1,51 @@ |
|||
import { renderSlot } from "vue" |
|||
|
|||
interface IProps { |
|||
value : object |
|||
} |
|||
|
|||
type ContextValue = { |
|||
Provider : any |
|||
useContext : () => any |
|||
Consumer : any |
|||
} |
|||
|
|||
type TCreateContext = (key : string) => ContextValue |
|||
|
|||
export const createContext : TCreateContext = (key) => { |
|||
const Provider = { |
|||
props: { |
|||
value: { |
|||
type: Object, |
|||
required: true |
|||
} |
|||
}, |
|||
setup(props : IProps) { |
|||
const slots = useSlots() |
|||
provide(key, props.value) |
|||
const FN : () => VNode = () => renderSlot(slots, 'default') |
|||
return () : VNode => h(FN) |
|||
} |
|||
} |
|||
const useContext : () => any = () : any => { |
|||
return inject(key) as any |
|||
} |
|||
const Consumer = { |
|||
setup() { |
|||
const slots = useSlots() |
|||
const data = useContext() |
|||
const FN : () => VNode = () => renderSlot(slots, 'default', data) |
|||
return () : VNode => h(FN, {}) |
|||
} |
|||
} |
|||
|
|||
const FnP = Provider |
|||
const FnC = Consumer |
|||
|
|||
const value: ContextValue = { |
|||
Provider: FnP, |
|||
useContext, |
|||
Consumer: FnC, |
|||
} |
|||
return value |
|||
} |
|||
@ -0,0 +1,47 @@ |
|||
import { renderSlot } from "vue" |
|||
import ProviderComp from "./Provider.uvue" |
|||
import ConsumerComp from "./Consumer.uvue" |
|||
|
|||
interface IProps { |
|||
value : object |
|||
} |
|||
|
|||
type ContextValue = { |
|||
Provider : any |
|||
useContext : () => any |
|||
Consumer : any |
|||
} |
|||
|
|||
type TCreateContext = (key : string) => ContextValue |
|||
|
|||
export const createContext : TCreateContext = (key) => { |
|||
const useContext : () => any = () : any => { |
|||
return inject(key) as any |
|||
} |
|||
// const Consumer = { |
|||
// setup() { |
|||
// const slots = useSlots() |
|||
// const data = useContext() |
|||
// const FN : () => VNode = () => renderSlot(slots, 'default', data) |
|||
// return () : VNode => h(FN, {}) |
|||
// } |
|||
// } |
|||
|
|||
// const FnC = Consumer |
|||
|
|||
const value : ContextValue = { |
|||
Provider: (): VNode=> { |
|||
return h(ProviderComp, { contextKey: key },) |
|||
}, |
|||
useContext, |
|||
Consumer: h(ConsumerComp, { contextKey: key }), |
|||
// Consumer: (): VNode=>{ |
|||
// const slots = useSlots() |
|||
// const data = useContext() |
|||
// // const FN : () => VNode = () => renderSlot(slots, 'default', data) |
|||
// console.log(data); |
|||
// return renderSlot(slots, 'default', data) |
|||
// } |
|||
} |
|||
return value |
|||
} |
|||
@ -0,0 +1,233 @@ |
|||
<template> |
|||
<x-page> |
|||
<x-navbar> |
|||
<template v-slot:left> |
|||
<view @click="isShowMenu = true" class=""> |
|||
菜单 |
|||
</view> |
|||
</template> |
|||
<text class="navbar-text">LuMi</text> |
|||
</x-navbar> |
|||
<view class="tabs"> |
|||
<view class="tab"> |
|||
<text class="tab-text">{{nowYear}}年{{nowMonth}}月</text> |
|||
</view> |
|||
</view> |
|||
<view class="rili"> |
|||
<view class="grid"> |
|||
<view class="grid-item" :style="getGridItemStyle(day)" v-for="day in all" :key="day"> |
|||
<view class="grid-item-box"> |
|||
<view class="grid-item-content"> |
|||
<text class="grid-item-text" :style="getGridItemTextStyle(day)">{{ day.label }}</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<!-- <text class="clear" @click="click2" v-if="recordDate.length">清除所有</text> --> |
|||
<view class="list"> |
|||
<view class="item" v-for="(item, index) in recordDate"> |
|||
<text class="label">{{item.label}}</text> |
|||
<text class="close" @click="delIem(item, index)">x</text> |
|||
</view> |
|||
</view> |
|||
<view class="float-btn" @click="click"> |
|||
<text class="float-btn-text">打卡</text> |
|||
</view> |
|||
<x-calendar> |
|||
<!-- <x-calendar--item aa="213"></x-calendar--item> --> |
|||
</x-calendar> |
|||
<x-slide-menu v-model="isShowMenu"></x-slide-menu> |
|||
</x-page> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { getCurMonthDayNum, getCurrentMonth, formatFull, getLastMonth, getLastMonthDay, format, getWeek, getNextMonthDay, generateDate } from "@/utils/date.uts" |
|||
|
|||
const isShowMenu = ref(false) |
|||
|
|||
type TItem = { label : string; value : Date; } |
|||
|
|||
const nowDate = ref(new Date()) |
|||
const nowYear = computed(() => { |
|||
return nowDate.value.getFullYear() |
|||
}) |
|||
const nowMonth = computed(() => { |
|||
return nowDate.value.getMonth() + 1 |
|||
}) |
|||
const recordDate = ref<TItem[]>([]) |
|||
|
|||
const all = computed(() => { |
|||
return generateDate<TItem>(format(nowDate.value), (v : Date) => ({ |
|||
label: v.getDate() + "", |
|||
value: v |
|||
})) |
|||
}) |
|||
|
|||
const isRecord = (date : Date) => { |
|||
let count = 0 |
|||
for (var i = 0; i < recordDate.value.length; i++) { |
|||
var item = recordDate.value[i]; |
|||
if (format(item.value) == format(date)) { |
|||
count++ |
|||
} |
|||
} |
|||
return count |
|||
} |
|||
|
|||
const isToday = (date : Date) => { |
|||
return format(nowDate.value) == format(date) |
|||
} |
|||
|
|||
const getGridItemStyle = (day : TItem) => { |
|||
const count = isRecord(day.value) |
|||
return { |
|||
backgroundColor: count ? `rgba(0, 0, 0, ${count * 0.2})` : 'rgba(0, 0, 0, 0.05)' |
|||
} |
|||
} |
|||
|
|||
const getGridItemTextStyle = (day : TItem) => { |
|||
const count = isRecord(day.value) |
|||
return { |
|||
color: count ? '#ffffff' : isToday(day.value) ? 'black' : 'rgba(0, 0, 0, .08)' |
|||
} |
|||
} |
|||
|
|||
// const data = uni.getStorageSync("save") |
|||
// if (Array.isArray(data as [])) { |
|||
// let result : TItem[] = [] |
|||
// for (let i = 0; i < data.length; i++) { |
|||
// let v : number = data[i] as number |
|||
// const date = new Date(v) |
|||
// result.push({ |
|||
// label: formatFull(date), |
|||
// value: date |
|||
// }) |
|||
// } |
|||
// recordDate.value = result |
|||
// } else { |
|||
recordDate.value = [] |
|||
// } |
|||
// |
|||
const click2 = () => { |
|||
uni.removeStorageSync("save") |
|||
recordDate.value = [] |
|||
} |
|||
|
|||
const click = () => { |
|||
const date = new Date() |
|||
recordDate.value.unshift({ |
|||
label: formatFull(date), |
|||
value: date |
|||
} as TItem) |
|||
uni.setStorageSync("save", unref(recordDate).map(v => v.value.getTime())) |
|||
} |
|||
|
|||
const delIem = (item : TItem, index : number) => { |
|||
recordDate.value.splice(index, 1) |
|||
uni.setStorageSync("save", unref(recordDate)) |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.navbar-text { |
|||
font-size: 45rpx; |
|||
font-family: ZhanKuKuaiLeTi; |
|||
} |
|||
|
|||
.tabs { |
|||
display: flex; |
|||
flex-direction: row; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 25rpx 0 15rpx; |
|||
margin-left: -20rpx; |
|||
|
|||
.tab { |
|||
margin-left: 20rpx; |
|||
|
|||
.tab-text { |
|||
font-size: 45rpx; |
|||
font-family: ZhanKuKuaiLeTi; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.clear { |
|||
padding: 20rpx 100rpx; |
|||
} |
|||
|
|||
.list { |
|||
.item { |
|||
padding: 20rpx 100rpx; |
|||
flex-direction: row; |
|||
align-items: center; |
|||
|
|||
.label { |
|||
flex: 1; |
|||
width: 0; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.rili { |
|||
margin-top: -30rpx; |
|||
|
|||
.grid { |
|||
flex-direction: row; |
|||
flex-wrap: wrap; |
|||
justify-content: center; |
|||
margin-left: -30rpx; |
|||
margin-top: -30rpx; |
|||
padding: 30rpx 80rpx 0; |
|||
|
|||
.grid-item { |
|||
width: 55rpx; |
|||
margin-left: 30rpx; |
|||
margin-top: 30rpx; |
|||
border-radius: 15rpx; |
|||
background-color: rgba(0, 0, 0, 0.05); |
|||
|
|||
.grid-item-box { |
|||
padding-bottom: 100%; |
|||
position: relative; |
|||
|
|||
.grid-item-content { |
|||
position: absolute; |
|||
left: 0; |
|||
right: 0; |
|||
top: 0; |
|||
bottom: 0; |
|||
align-items: center; |
|||
justify-content: center; |
|||
|
|||
.grid-item-text { |
|||
color: rgba(0, 0, 0, .08); |
|||
font-size: 25rpx; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
.float-btn { |
|||
position: fixed; |
|||
z-index: 99; |
|||
left: 50%; |
|||
transform: translate(-50%); |
|||
bottom: 100rpx; |
|||
width: 100rpx; |
|||
height: 100rpx; |
|||
align-items: center; |
|||
justify-content: center; |
|||
border-radius: 100rpx; |
|||
background-color: white; |
|||
box-shadow: |
|||
0 2px 10px rgba(0, 0, 0, 0.1); |
|||
|
|||
.float-btn-text { |
|||
font-size: 25rpx; |
|||
} |
|||
} |
|||
</style> |
|||
Loading…
Reference in new issue