19 changed files with 1002 additions and 123 deletions
@ -1,6 +1,5 @@ |
|||||
|
|
||||
@font-face { |
@font-face { |
||||
font-family: ZhanKuKuaiLeTi; |
font-family: ZhanKuKuaiLeTi; |
||||
src: url('@/assets/fonts/ZhanKuKuaiLeTi2016XiuDingBan-1.ttf'); |
src: url('@/assets/fonts/ZhanKuKuaiLeTi2016XiuDingBan-1.ttf'); |
||||
} |
} |
||||
|
|
||||
@ -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