Merge pull request #174 from LOG1997/release

Release
This commit is contained in:
LOG1997
2026-01-04 15:54:20 +08:00
committed by GitHub
113 changed files with 2150 additions and 2153 deletions

View File

@@ -1,7 +1,7 @@
{ {
"name": "log-lottery", "name": "log-lottery",
"private": true, "private": true,
"version": "0.5.2", "version": "0.5.3",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {

2
src-tauri/Cargo.lock generated
View File

@@ -77,7 +77,7 @@ checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
[[package]] [[package]]
name = "app" name = "app"
version = "0.5.1" version = "0.5.2"
dependencies = [ dependencies = [
"log", "log",
"serde", "serde",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "app" name = "app"
version = "0.5.2" version = "0.5.3"
description = "A Tauri App" description = "A Tauri App"
authors = ["you"] authors = ["you"]
license = "" license = ""
@@ -21,5 +21,5 @@ tauri-build = { version = "2.5.3", features = [] }
serde_json = "1.0" serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
log = "0.4" log = "0.4"
tauri = { version = "2.9.4", features = [] } tauri = { version = "2.9.4", features = ["devtools"] }
tauri-plugin-log = "2" tauri-plugin-log = "2"

View File

@@ -1,7 +1,7 @@
{ {
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"productName": "log-lottery", "productName": "log-lottery",
"version": "0.5.2", "version": "0.5.3",
"identifier": "to2026.xyz", "identifier": "to2026.xyz",
"build": { "build": {
"frontendDist": "../dist", "frontendDist": "../dist",

View File

@@ -2,61 +2,61 @@ import type { AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosReq
import axios from 'axios' import axios from 'axios'
class Request { class Request {
private instance: AxiosInstance private instance: AxiosInstance
constructor(config: AxiosRequestConfig) { constructor(config: AxiosRequestConfig) {
this.instance = axios.create({ this.instance = axios.create({
baseURL: '/api', baseURL: '/api',
timeout: 10000, timeout: 10000,
...config, ...config,
}) })
// 添加请求拦截器 // 添加请求拦截器
this.instance.interceptors.request.use( this.instance.interceptors.request.use(
(config: InternalAxiosRequestConfig) => { (config: InternalAxiosRequestConfig) => {
// 在发送请求之前做些什么 // 在发送请求之前做些什么
console.log('请求拦截器被触发') console.log('请求拦截器被触发')
return config return config
}, },
(error: any) => { (error: any) => {
// 对请求错误做些什么 // 对请求错误做些什么
console.error('请求拦截器发生错误:', error) console.error('请求拦截器发生错误:', error)
return Promise.reject(error) return Promise.reject(error)
}, },
) )
// 添加响应拦截器 // 添加响应拦截器
this.instance.interceptors.response.use( this.instance.interceptors.response.use(
(response: AxiosResponse) => { (response: AxiosResponse) => {
// 对响应数据做些什么 // 对响应数据做些什么
console.log('响应拦截器被触发') console.log('响应拦截器被触发')
const responseData = response.data const responseData = response.data
return responseData return responseData
}, },
(error: any) => { (error: any) => {
// 对响应错误做些什么 // 对响应错误做些什么
console.error('响应拦截器发生错误:', error) console.error('响应拦截器发生错误:', error)
return Promise.reject(error) return Promise.reject(error)
}, },
) )
} }
public async request<T>(config: AxiosRequestConfig): Promise<T> { public async request<T>(config: AxiosRequestConfig): Promise<T> {
const response: AxiosResponse<T> = await this.instance.request(config) const response: AxiosResponse<T> = await this.instance.request(config)
return response.data return response.data
} }
} }
// 函数 // 函数
function request<T>(config: AxiosRequestConfig): Promise<T> { function request<T>(config: AxiosRequestConfig): Promise<T> {
const instance = new Request(config) const instance = new Request(config)
return instance.request(config) return instance.request(config)
} }
export default request export default request

View File

@@ -3,28 +3,28 @@ import { computed } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
const props = defineProps({ const props = defineProps({
data: { data: {
type: Array as any, type: Array as any,
default: [] as any[], default: [] as any[],
}, },
tableColumns: { tableColumns: {
type: Array, type: Array,
default: [] as any[], default: [] as any[],
}, },
}) })
const { t } = useI18n() const { t } = useI18n()
const dataColumns = computed<any[]>(() => { const dataColumns = computed<any[]>(() => {
// 不带有actions的列 // 不带有actions的列
const columns = props.tableColumns.filter((item: any) => !item.actions) const columns = props.tableColumns.filter((item: any) => !item.actions)
return columns return columns
}) })
const actionsColumns = computed<any[]>(() => { const actionsColumns = computed<any[]>(() => {
// 带有actions的列 // 带有actions的列
const columns = props.tableColumns.filter((item: any) => item.actions) const columns = props.tableColumns.filter((item: any) => item.actions)
return columns return columns
}) })
</script> </script>

View File

@@ -3,40 +3,40 @@ import { onMounted, ref, toRefs } from 'vue'
import i18n from '@/locales/i18n' import i18n from '@/locales/i18n'
interface Props { interface Props {
title: string title: string
desc?: string desc?: string
cancelText?: string cancelText?: string
submitText?: string submitText?: string
submitFunc?: () => void submitFunc?: () => void
cancelFunc?: () => void cancelFunc?: () => void
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
cancelText: i18n.global.t('button.cancel'), cancelText: i18n.global.t('button.cancel'),
submitText: i18n.global.t('button.confirm'), submitText: i18n.global.t('button.confirm'),
cancelFunc: () => {}, cancelFunc: () => {},
}) })
const visible = defineModel('visible', { const visible = defineModel('visible', {
type: Boolean, type: Boolean,
default: false, default: false,
}) })
const dialogRef = ref <HTMLDialogElement | null> (null) const dialogRef = ref <HTMLDialogElement | null> (null)
function defaultCancelFunc() { function defaultCancelFunc() {
dialogRef.value?.close() dialogRef.value?.close()
} }
function showDialog() { function showDialog() {
dialogRef.value?.showModal() dialogRef.value?.showModal()
} }
defineExpose({ defineExpose({
showDialog, showDialog,
closed, closed,
}) })
onMounted(() => { onMounted(() => {
dialogRef.value?.addEventListener('close', () => { dialogRef.value?.addEventListener('close', () => {
visible.value = false visible.value = false
}) })
}) })
const { title, desc, cancelText, submitText, submitFunc, cancelFunc = defaultCancelFunc } = toRefs(props) const { title, desc, cancelText, submitText, submitFunc, cancelFunc = defaultCancelFunc } = toRefs(props)
</script> </script>

View File

@@ -4,16 +4,16 @@ import { ref } from 'vue'
const drawerTriggerRef = ref <HTMLDialogElement | null> (null) const drawerTriggerRef = ref <HTMLDialogElement | null> (null)
const visible = ref(false) const visible = ref(false)
function showDrawer() { function showDrawer() {
drawerTriggerRef.value?.click() drawerTriggerRef.value?.click()
visible.value = true visible.value = true
} }
function closeDrawer() { function closeDrawer() {
drawerTriggerRef.value?.click() drawerTriggerRef.value?.click()
visible.value = false visible.value = false
} }
defineExpose({ defineExpose({
showDrawer, showDrawer,
closeDrawer, closeDrawer,
}) })
</script> </script>

View File

@@ -2,38 +2,38 @@
import type { IFileData } from './type' import type { IFileData } from './type'
import { ListMusic, Upload, X } from 'lucide-vue-next' import { ListMusic, Upload, X } from 'lucide-vue-next'
import { ref } from 'vue' import { ref } from 'vue'
import { getBlobObjectUrl, readFileAsJsonData, readFileData, readFileDataAsBlob } from '@/utils/file' import { getBlobObjectUrl, readFileAsJsonData, readFileDataAsBlob } from '@/utils/file'
const props = defineProps<{ const props = defineProps<{
limitType?: string limitType?: string
mode?: 'file' | 'json' mode?: 'file' | 'json'
}>() }>()
const emits = defineEmits<{ const emits = defineEmits<{
uploadFile: [fileData: IFileData | null] uploadFile: [fileData: IFileData | null]
}>() }>()
const originFileName = ref<string | null>(null) const originFileName = ref<string | null>(null)
const fileData = ref<IFileData | null>(null) const fileData = ref<IFileData | null>(null)
async function handleFileChange(e: Event) { async function handleFileChange(e: Event) {
const file = ((e.target as HTMLInputElement).files as FileList)[0] const file = ((e.target as HTMLInputElement).files as FileList)[0]
const type = file.type const type = file.type
if (props.mode === 'json') { if (props.mode === 'json') {
const fileRes = await readFileAsJsonData(file) const fileRes = await readFileAsJsonData(file)
const jsonData = JSON.parse(fileRes) const jsonData = JSON.parse(fileRes)
fileData.value = { data: jsonData, fileName: file.name, type } fileData.value = { data: jsonData, fileName: file.name, type }
originFileName.value = file.name originFileName.value = file.name
emits('uploadFile', fileData.value)
return
}
const { data: blobData, fileName } = await readFileDataAsBlob(file)
fileData.value = { data: blobData, fileName, type }
originFileName.value = fileName
emits('uploadFile', fileData.value) emits('uploadFile', fileData.value)
return
}
const { data: blobData, fileName } = await readFileDataAsBlob(file)
fileData.value = { data: blobData, fileName, type }
originFileName.value = fileName
emits('uploadFile', fileData.value)
} }
function removeFile() { function removeFile() {
fileData.value = null fileData.value = null
emits('uploadFile', null) emits('uploadFile', null)
} }
</script> </script>

View File

@@ -5,7 +5,7 @@ defineProps<{ msg: string }>()
const count = ref(0) const count = ref(0)
function addCount() { function addCount() {
count.value++ count.value++
} }
</script> </script>

View File

@@ -5,31 +5,31 @@ import localforage from 'localforage'
import { onMounted, ref } from 'vue' import { onMounted, ref } from 'vue'
interface IProps { interface IProps {
imgItem: IImage imgItem: IImage
} }
const props = defineProps<IProps>() const props = defineProps<IProps>()
const imageDbStore = localforage.createInstance({ const imageDbStore = localforage.createInstance({
name: 'imgStore', name: 'imgStore',
}) })
const imgUrl = ref('') const imgUrl = ref('')
async function getImageStoreItem(item: IImage): Promise<string> { async function getImageStoreItem(item: IImage): Promise<string> {
let image = '' let image = ''
if (item.url === 'Storage') { if (item.url === 'Storage') {
const key = item.id const key = item.id
const imageData = await imageDbStore.getItem<IFileData>(key) const imageData = await imageDbStore.getItem<IFileData>(key)
image = URL.createObjectURL(imageData?.data as Blob) image = URL.createObjectURL(imageData?.data as Blob)
} }
else { else {
image = item.url as string image = item.url as string
} }
return image return image
} }
onMounted(async () => { onMounted(async () => {
imgUrl.value = await getImageStoreItem(props.imgItem) imgUrl.value = await getImageStoreItem(props.imgItem)
}) })
</script> </script>

View File

@@ -4,14 +4,14 @@ import { onMounted, ref, toRefs, watch } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
const props = defineProps({ const props = defineProps({
totalNumber: { totalNumber: {
type: Number, type: Number,
default: 0, default: 0,
}, },
separatedNumber: { separatedNumber: {
type: Array<Separate>, type: Array<Separate>,
default: [], default: [],
}, },
}) })
const emits = defineEmits(['submitData']) const emits = defineEmits(['submitData'])
const { t } = useI18n() const { t } = useI18n()
@@ -19,55 +19,55 @@ const separatedNumberRef = ref()
const { separatedNumber, totalNumber } = toRefs(props) const { separatedNumber, totalNumber } = toRefs(props)
const scaleList = ref<number[]>([]) const scaleList = ref<number[]>([])
function editScale(item: number) { function editScale(item: number) {
if (item === totalNumber.value) { if (item === totalNumber.value) {
return return
} }
if (scaleList.value.includes(item)) { if (scaleList.value.includes(item)) {
const index = scaleList.value.indexOf(item) const index = scaleList.value.indexOf(item)
scaleList.value.splice(index, 1) scaleList.value.splice(index, 1)
separatedNumber.value.splice(index, 1) separatedNumber.value.splice(index, 1)
} }
else { else {
scaleList.value.push(item) scaleList.value.push(item)
scaleList.value.sort((a, b) => a - b) scaleList.value.sort((a, b) => a - b)
} }
} }
function clearData() { function clearData() {
emits('submitData', separatedNumber.value) emits('submitData', separatedNumber.value)
separatedNumberRef.value.close() separatedNumberRef.value.close()
} }
watch(scaleList, (val: number[]) => { watch(scaleList, (val: number[]) => {
separatedNumber.value.length = 0 separatedNumber.value.length = 0
for (let i = 1; i < scaleList.value.length; i++) { for (let i = 1; i < scaleList.value.length; i++) {
separatedNumber.value[i - 1] = { separatedNumber.value[i - 1] = {
id: i.toString(), id: i.toString(),
count: val[i] - val[i - 1], count: val[i] - val[i - 1],
isUsedCount: 0, isUsedCount: 0,
}
} }
}
}, { deep: true }) }, { deep: true })
watch(totalNumber, (val) => { watch(totalNumber, (val) => {
if (val <= 0) { if (val <= 0) {
return return
} }
separatedNumberRef.value.showModal() separatedNumberRef.value.showModal()
// scaleList.value = [0, val] // scaleList.value = [0, val]
scaleList.value = Array.from({ length: separatedNumber.value.length + 1 }).fill(totalNumber.value) as number[] scaleList.value = Array.from({ length: separatedNumber.value.length + 1 }).fill(totalNumber.value) as number[]
for (let i = separatedNumber.value.length - 1; i >= 0; i--) { for (let i = separatedNumber.value.length - 1; i >= 0; i--) {
scaleList.value[i] = scaleList.value[i + 1] - separatedNumber.value[i].count scaleList.value[i] = scaleList.value[i + 1] - separatedNumber.value[i].count
} }
if (scaleList.value[0] !== 0) { if (scaleList.value[0] !== 0) {
scaleList.value.unshift(0) scaleList.value.unshift(0)
} }
}) })
onMounted(() => { onMounted(() => {
// 阻止esc事件 // 阻止esc事件
document.addEventListener('keydown', (e) => { document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') { if (e.key === 'Escape') {
e.preventDefault() e.preventDefault()
} }
}) })
}) })
</script> </script>

View File

@@ -1,6 +1,6 @@
<script setup lang='ts'> <script setup lang='ts'>
const props = defineProps<{ const props = defineProps<{
title: string title: string
}>() }>()
</script> </script>

View File

@@ -2,22 +2,22 @@
import { computed } from 'vue' import { computed } from 'vue'
const props = defineProps({ const props = defineProps({
prefix: { prefix: {
type: String, type: String,
default: 'icon', default: 'icon',
}, },
name: { name: {
type: String, type: String,
required: true, required: true,
}, },
color: { color: {
type: String, type: String,
default: '#242424', default: '#242424',
}, },
size: { size: {
type: String, type: String,
default: '24px', default: '24px',
}, },
}) })
const symbolId = computed(() => `#${props.prefix}-${props.name}`) const symbolId = computed(() => `#${props.prefix}-${props.name}`)

View File

@@ -6,16 +6,16 @@ import { nextTick, onMounted, onUnmounted, ref } from 'vue'
// 布局参数 Props // 布局参数 Props
interface MasonryWaterfallProps { interface MasonryWaterfallProps {
columnWidth?: number | string // 列宽px/选择器) columnWidth?: number | string // 列宽px/选择器)
gutter?: number // 列/行间距 gutter?: number // 列/行间距
fitWidth?: boolean // 容器宽度自适应居中 fitWidth?: boolean // 容器宽度自适应居中
} }
// 默认参数 // 默认参数
const props = withDefaults(defineProps<MasonryWaterfallProps>(), { const props = withDefaults(defineProps<MasonryWaterfallProps>(), {
columnWidth: 120, columnWidth: 120,
gutter: 16, gutter: 16,
fitWidth: true, fitWidth: true,
}) })
// Vue Ref 管理 DOM 容器和 masonry 实例 // Vue Ref 管理 DOM 容器和 masonry 实例
@@ -24,52 +24,52 @@ const masonryInstance: Ref<Masonry | null> = ref(null)
// 初始化 masonry仅执行一次因卡片固定 // 初始化 masonry仅执行一次因卡片固定
async function initMasonry() { async function initMasonry() {
if (!masonryContainer.value) if (!masonryContainer.value)
return return
// 等待插槽内容(固定卡片)完全渲染 // 等待插槽内容(固定卡片)完全渲染
await nextTick() await nextTick()
// 初始化 masonry 实例(固定卡片无需销毁旧实例) // 初始化 masonry 实例(固定卡片无需销毁旧实例)
masonryInstance.value = new Masonry(masonryContainer.value, { masonryInstance.value = new Masonry(masonryContainer.value, {
itemSelector: '.masonry-container > *', // 匹配所有固定子项 itemSelector: '.masonry-container > *', // 匹配所有固定子项
columnWidth: props.columnWidth, columnWidth: props.columnWidth,
gutter: props.gutter, gutter: props.gutter,
fitWidth: props.fitWidth, fitWidth: props.fitWidth,
initLayout: true, // 固定卡片直接初始化布局 initLayout: true, // 固定卡片直接初始化布局
}) })
} }
// 刷新布局(仅用于卡片内部内容高度变化) // 刷新布局(仅用于卡片内部内容高度变化)
async function refreshLayout() { async function refreshLayout() {
await nextTick() await nextTick()
if (masonryInstance.value) { if (masonryInstance.value) {
masonryInstance.value.layout?.() masonryInstance.value.layout?.()
} }
} }
// 窗口缩放节流重排(优化性能) // 窗口缩放节流重排(优化性能)
const handleResize = throttle(() => { const handleResize = throttle(() => {
if (masonryInstance.value) { if (masonryInstance.value) {
masonryInstance.value.layout?.() masonryInstance.value.layout?.()
} }
}, 300) }, 300)
// 生命周期:挂载时初始化,卸载时清理 // 生命周期:挂载时初始化,卸载时清理
onMounted(async () => { onMounted(async () => {
await initMasonry() await initMasonry()
window.addEventListener('resize', handleResize) window.addEventListener('resize', handleResize)
}) })
onUnmounted(() => { onUnmounted(() => {
// 销毁实例 + 释放内存 // 销毁实例 + 释放内存
if (masonryInstance.value) { if (masonryInstance.value) {
masonryInstance.value.destroy?.() masonryInstance.value.destroy?.()
masonryInstance.value = null masonryInstance.value = null
} }
// 移除监听 + 取消节流任务 // 移除监听 + 取消节流任务
window.removeEventListener('resize', handleResize) window.removeEventListener('resize', handleResize)
handleResize.cancel() handleResize.cancel()
}) })
// 仅暴露刷新方法(适配卡片内部内容变化) // 仅暴露刷新方法(适配卡片内部内容变化)

View File

@@ -1,19 +1,19 @@
<script setup lang="ts"> <script setup lang="ts">
import type { PrimitiveProps } from "reka-ui" import type { PrimitiveProps } from 'reka-ui'
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from 'vue'
import type { ButtonVariants } from "." import type { ButtonVariants } from '.'
import { Primitive } from "reka-ui" import { Primitive } from 'reka-ui'
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils'
import { buttonVariants } from "." import { buttonVariants } from '.'
interface Props extends PrimitiveProps { interface Props extends PrimitiveProps {
variant?: ButtonVariants["variant"] variant?: ButtonVariants['variant']
size?: ButtonVariants["size"] size?: ButtonVariants['size']
class?: HTMLAttributes["class"] class?: HTMLAttributes['class']
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
as: "button", as: 'button',
}) })
</script> </script>

View File

@@ -1,38 +1,38 @@
import type { VariantProps } from "class-variance-authority" import type { VariantProps } from 'class-variance-authority'
import { cva } from "class-variance-authority" import { cva } from 'class-variance-authority'
export { default as Button } from "./Button.vue" export { default as Button } from './Button.vue'
export const buttonVariants = cva( export const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*=\'size-\'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
{ {
variants: { variants: {
variant: { variant: {
default: default:
"bg-primary text-primary-foreground hover:bg-primary/90", 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive: destructive:
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", 'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
outline: outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", 'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
secondary: secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80", 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost: ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
link: "text-primary underline-offset-4 hover:underline", link: 'text-primary underline-offset-4 hover:underline',
}, },
size: { size: {
"default": "h-9 px-4 py-2 has-[>svg]:px-3", 'default': 'h-9 px-4 py-2 has-[>svg]:px-3',
"sm": "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", 'sm': 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',
"lg": "h-10 rounded-md px-6 has-[>svg]:px-4", 'lg': 'h-10 rounded-md px-6 has-[>svg]:px-4',
"icon": "size-9", 'icon': 'size-9',
"icon-sm": "size-8", 'icon-sm': 'size-8',
"icon-lg": "size-10", 'icon-lg': 'size-10',
}, },
},
defaultVariants: {
variant: 'default',
size: 'default',
},
}, },
defaultVariants: {
variant: "default",
size: "default",
},
},
) )
export type ButtonVariants = VariantProps<typeof buttonVariants> export type ButtonVariants = VariantProps<typeof buttonVariants>

View File

@@ -1,78 +1,78 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ListboxRootEmits, ListboxRootProps } from "reka-ui" import type { ListboxRootEmits, ListboxRootProps } from 'reka-ui'
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from "@vueuse/core" import { reactiveOmit } from '@vueuse/core'
import { ListboxRoot, useFilter, useForwardPropsEmits } from "reka-ui" import { ListboxRoot, useFilter, useForwardPropsEmits } from 'reka-ui'
import { reactive, ref, watch } from "vue" import { reactive, ref, watch } from 'vue'
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils'
import { provideCommandContext } from "." import { provideCommandContext } from '.'
const props = withDefaults(defineProps<ListboxRootProps & { class?: HTMLAttributes["class"] }>(), { const props = withDefaults(defineProps<ListboxRootProps & { class?: HTMLAttributes['class'] }>(), {
modelValue: "", modelValue: '',
}) })
const emits = defineEmits<ListboxRootEmits>() const emits = defineEmits<ListboxRootEmits>()
const delegatedProps = reactiveOmit(props, "class") const delegatedProps = reactiveOmit(props, 'class')
const forwarded = useForwardPropsEmits(delegatedProps, emits) const forwarded = useForwardPropsEmits(delegatedProps, emits)
const allItems = ref<Map<string, string>>(new Map()) const allItems = ref<Map<string, string>>(new Map())
const allGroups = ref<Map<string, Set<string>>>(new Map()) const allGroups = ref<Map<string, Set<string>>>(new Map())
const { contains } = useFilter({ sensitivity: "base" }) const { contains } = useFilter({ sensitivity: 'base' })
const filterState = reactive({ const filterState = reactive({
search: "", search: '',
filtered: { filtered: {
/** The count of all visible items. */ /** The count of all visible items. */
count: 0, count: 0,
/** Map from visible item id to its search score. */ /** Map from visible item id to its search score. */
items: new Map() as Map<string, number>, items: new Map() as Map<string, number>,
/** Set of groups with at least one visible item. */ /** Set of groups with at least one visible item. */
groups: new Set() as Set<string>, groups: new Set() as Set<string>,
}, },
}) })
function filterItems() { function filterItems() {
if (!filterState.search) { if (!filterState.search) {
filterState.filtered.count = allItems.value.size filterState.filtered.count = allItems.value.size
// Do nothing, each item will know to show itself because search is empty // Do nothing, each item will know to show itself because search is empty
return return
}
// Reset the groups
filterState.filtered.groups = new Set()
let itemCount = 0
// Check which items should be included
for (const [id, value] of allItems.value) {
const score = contains(value, filterState.search)
filterState.filtered.items.set(id, score ? 1 : 0)
if (score)
itemCount++
}
// Check which groups have at least 1 item shown
for (const [groupId, group] of allGroups.value) {
for (const itemId of group) {
if (filterState.filtered.items.get(itemId)! > 0) {
filterState.filtered.groups.add(groupId)
break
}
} }
}
filterState.filtered.count = itemCount // Reset the groups
filterState.filtered.groups = new Set()
let itemCount = 0
// Check which items should be included
for (const [id, value] of allItems.value) {
const score = contains(value, filterState.search)
filterState.filtered.items.set(id, score ? 1 : 0)
if (score)
itemCount++
}
// Check which groups have at least 1 item shown
for (const [groupId, group] of allGroups.value) {
for (const itemId of group) {
if (filterState.filtered.items.get(itemId)! > 0) {
filterState.filtered.groups.add(groupId)
break
}
}
}
filterState.filtered.count = itemCount
} }
watch(() => filterState.search, () => { watch(() => filterState.search, () => {
filterItems() filterItems()
}) })
provideCommandContext({ provideCommandContext({
allItems, allItems,
allGroups, allGroups,
filterState, filterState,
}) })
</script> </script>

View File

@@ -1,15 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import type { DialogRootEmits, DialogRootProps } from "reka-ui" import type { DialogRootEmits, DialogRootProps } from 'reka-ui'
import { useForwardPropsEmits } from "reka-ui" import { useForwardPropsEmits } from 'reka-ui'
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog' import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog'
import Command from "./Command.vue" import Command from './Command.vue'
const props = withDefaults(defineProps<DialogRootProps & { const props = withDefaults(defineProps<DialogRootProps & {
title?: string title?: string
description?: string description?: string
}>(), { }>(), {
title: "Command Palette", title: 'Command Palette',
description: "Search for a command to run...", description: 'Search for a command to run...',
}) })
const emits = defineEmits<DialogRootEmits>() const emits = defineEmits<DialogRootEmits>()

View File

@@ -1,15 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import type { PrimitiveProps } from "reka-ui" import type { PrimitiveProps } from 'reka-ui'
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from "@vueuse/core" import { reactiveOmit } from '@vueuse/core'
import { Primitive } from "reka-ui" import { Primitive } from 'reka-ui'
import { computed } from "vue" import { computed } from 'vue'
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils'
import { useCommand } from "." import { useCommand } from '.'
const props = defineProps<PrimitiveProps & { class?: HTMLAttributes["class"] }>() const props = defineProps<PrimitiveProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = reactiveOmit(props, "class") const delegatedProps = reactiveOmit(props, 'class')
const { filterState } = useCommand() const { filterState } = useCommand()
const isRender = computed(() => !!filterState.search && filterState.filtered.count === 0, const isRender = computed(() => !!filterState.search && filterState.filtered.count === 0,

View File

@@ -1,18 +1,18 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ListboxGroupProps } from "reka-ui" import type { ListboxGroupProps } from 'reka-ui'
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from "@vueuse/core" import { reactiveOmit } from '@vueuse/core'
import { ListboxGroup, ListboxGroupLabel, useId } from "reka-ui" import { ListboxGroup, ListboxGroupLabel, useId } from 'reka-ui'
import { computed, onMounted, onUnmounted } from "vue" import { computed, onMounted, onUnmounted } from 'vue'
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils'
import { provideCommandGroupContext, useCommand } from "." import { provideCommandGroupContext, useCommand } from '.'
const props = defineProps<ListboxGroupProps & { const props = defineProps<ListboxGroupProps & {
class?: HTMLAttributes["class"] class?: HTMLAttributes['class']
heading?: string heading?: string
}>() }>()
const delegatedProps = reactiveOmit(props, "class") const delegatedProps = reactiveOmit(props, 'class')
const { allGroups, filterState } = useCommand() const { allGroups, filterState } = useCommand()
const id = useId() const id = useId()
@@ -21,11 +21,11 @@ const isRender = computed(() => !filterState.search ? true : filterState.filtere
provideCommandGroupContext({ id }) provideCommandGroupContext({ id })
onMounted(() => { onMounted(() => {
if (!allGroups.value.has(id)) if (!allGroups.value.has(id))
allGroups.value.set(id, new Set()) allGroups.value.set(id, new Set())
}) })
onUnmounted(() => { onUnmounted(() => {
allGroups.value.delete(id) allGroups.value.delete(id)
}) })
</script> </script>

View File

@@ -1,21 +1,21 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ListboxFilterProps } from "reka-ui" import type { ListboxFilterProps } from 'reka-ui'
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from "@vueuse/core" import { reactiveOmit } from '@vueuse/core'
import { Search } from "lucide-vue-next" import { Search } from 'lucide-vue-next'
import { ListboxFilter, useForwardProps } from "reka-ui" import { ListboxFilter, useForwardProps } from 'reka-ui'
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils'
import { useCommand } from "." import { useCommand } from '.'
defineOptions({ defineOptions({
inheritAttrs: false, inheritAttrs: false,
}) })
const props = defineProps<ListboxFilterProps & { const props = defineProps<ListboxFilterProps & {
class?: HTMLAttributes["class"] class?: HTMLAttributes['class']
}>() }>()
const delegatedProps = reactiveOmit(props, "class") const delegatedProps = reactiveOmit(props, 'class')
const forwardedProps = useForwardProps(delegatedProps) const forwardedProps = useForwardProps(delegatedProps)

View File

@@ -1,16 +1,16 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ListboxItemEmits, ListboxItemProps } from "reka-ui" import type { ListboxItemEmits, ListboxItemProps } from 'reka-ui'
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from 'vue'
import { reactiveOmit, useCurrentElement } from "@vueuse/core" import { reactiveOmit, useCurrentElement } from '@vueuse/core'
import { ListboxItem, useForwardPropsEmits, useId } from "reka-ui" import { ListboxItem, useForwardPropsEmits, useId } from 'reka-ui'
import { computed, onMounted, onUnmounted, ref } from "vue" import { computed, onMounted, onUnmounted, ref } from 'vue'
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils'
import { useCommand, useCommandGroup } from "." import { useCommand, useCommandGroup } from '.'
const props = defineProps<ListboxItemProps & { class?: HTMLAttributes["class"] }>() const props = defineProps<ListboxItemProps & { class?: HTMLAttributes['class'] }>()
const emits = defineEmits<ListboxItemEmits>() const emits = defineEmits<ListboxItemEmits>()
const delegatedProps = reactiveOmit(props, "class") const delegatedProps = reactiveOmit(props, 'class')
const forwarded = useForwardPropsEmits(delegatedProps, emits) const forwarded = useForwardPropsEmits(delegatedProps, emits)
@@ -19,43 +19,43 @@ const { filterState, allItems, allGroups } = useCommand()
const groupContext = useCommandGroup() const groupContext = useCommandGroup()
const isRender = computed(() => { const isRender = computed(() => {
if (!filterState.search) { if (!filterState.search) {
return true return true
}
else {
const filteredCurrentItem = filterState.filtered.items.get(id)
// If the filtered items is undefined means not in the all times map yet
// Do the first render to add into the map
if (filteredCurrentItem === undefined) {
return true
} }
else {
const filteredCurrentItem = filterState.filtered.items.get(id)
// If the filtered items is undefined means not in the all times map yet
// Do the first render to add into the map
if (filteredCurrentItem === undefined) {
return true
}
// Check with filter // Check with filter
return filteredCurrentItem > 0 return filteredCurrentItem > 0
} }
}) })
const itemRef = ref() const itemRef = ref()
const currentElement = useCurrentElement(itemRef) const currentElement = useCurrentElement(itemRef)
onMounted(() => { onMounted(() => {
if (!(currentElement.value instanceof HTMLElement)) if (!(currentElement.value instanceof HTMLElement))
return return
// textValue to perform filter // textValue to perform filter
allItems.value.set(id, currentElement.value.textContent ?? (props.value?.toString() ?? "")) allItems.value.set(id, currentElement.value.textContent ?? (props.value?.toString() ?? ''))
const groupId = groupContext?.id const groupId = groupContext?.id
if (groupId) { if (groupId) {
if (!allGroups.value.has(groupId)) { if (!allGroups.value.has(groupId)) {
allGroups.value.set(groupId, new Set([id])) allGroups.value.set(groupId, new Set([id]))
}
else {
allGroups.value.get(groupId)?.add(id)
}
} }
else {
allGroups.value.get(groupId)?.add(id)
}
}
}) })
onUnmounted(() => { onUnmounted(() => {
allItems.value.delete(id) allItems.value.delete(id)
}) })
</script> </script>

View File

@@ -1,13 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ListboxContentProps } from "reka-ui" import type { ListboxContentProps } from 'reka-ui'
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from "@vueuse/core" import { reactiveOmit } from '@vueuse/core'
import { ListboxContent, useForwardProps } from "reka-ui" import { ListboxContent, useForwardProps } from 'reka-ui'
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils'
const props = defineProps<ListboxContentProps & { class?: HTMLAttributes["class"] }>() const props = defineProps<ListboxContentProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = reactiveOmit(props, "class") const delegatedProps = reactiveOmit(props, 'class')
const forwarded = useForwardProps(delegatedProps) const forwarded = useForwardProps(delegatedProps)
</script> </script>

View File

@@ -1,13 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import type { SeparatorProps } from "reka-ui" import type { SeparatorProps } from 'reka-ui'
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from "@vueuse/core" import { reactiveOmit } from '@vueuse/core'
import { Separator } from "reka-ui" import { Separator } from 'reka-ui'
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils'
const props = defineProps<SeparatorProps & { class?: HTMLAttributes["class"] }>() const props = defineProps<SeparatorProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = reactiveOmit(props, "class") const delegatedProps = reactiveOmit(props, 'class')
</script> </script>
<template> <template>

View File

@@ -1,9 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from 'vue'
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils'
const props = defineProps<{ const props = defineProps<{
class?: HTMLAttributes["class"] class?: HTMLAttributes['class']
}>() }>()
</script> </script>

View File

@@ -1,25 +1,25 @@
import type { Ref } from "vue" import type { Ref } from 'vue'
import { createContext } from "reka-ui" import { createContext } from 'reka-ui'
export { default as Command } from "./Command.vue" export { default as Command } from './Command.vue'
export { default as CommandDialog } from "./CommandDialog.vue" export { default as CommandDialog } from './CommandDialog.vue'
export { default as CommandEmpty } from "./CommandEmpty.vue" export { default as CommandEmpty } from './CommandEmpty.vue'
export { default as CommandGroup } from "./CommandGroup.vue" export { default as CommandGroup } from './CommandGroup.vue'
export { default as CommandInput } from "./CommandInput.vue" export { default as CommandInput } from './CommandInput.vue'
export { default as CommandItem } from "./CommandItem.vue" export { default as CommandItem } from './CommandItem.vue'
export { default as CommandList } from "./CommandList.vue" export { default as CommandList } from './CommandList.vue'
export { default as CommandSeparator } from "./CommandSeparator.vue" export { default as CommandSeparator } from './CommandSeparator.vue'
export { default as CommandShortcut } from "./CommandShortcut.vue" export { default as CommandShortcut } from './CommandShortcut.vue'
export const [useCommand, provideCommandContext] = createContext<{ export const [useCommand, provideCommandContext] = createContext<{
allItems: Ref<Map<string, string>> allItems: Ref<Map<string, string>>
allGroups: Ref<Map<string, Set<string>>> allGroups: Ref<Map<string, Set<string>>>
filterState: { filterState: {
search: string search: string
filtered: { count: number, items: Map<string, number>, groups: Set<string> } filtered: { count: number, items: Map<string, number>, groups: Set<string> }
} }
}>("Command") }>('Command')
export const [useCommandGroup, provideCommandGroupContext] = createContext<{ export const [useCommandGroup, provideCommandGroupContext] = createContext<{
id?: string id?: string
}>("CommandGroup") }>('CommandGroup')

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { DialogRootEmits, DialogRootProps } from "reka-ui" import type { DialogRootEmits, DialogRootProps } from 'reka-ui'
import { DialogRoot, useForwardPropsEmits } from "reka-ui" import { DialogRoot, useForwardPropsEmits } from 'reka-ui'
const props = defineProps<DialogRootProps>() const props = defineProps<DialogRootProps>()
const emits = defineEmits<DialogRootEmits>() const emits = defineEmits<DialogRootEmits>()

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { DialogCloseProps } from "reka-ui" import type { DialogCloseProps } from 'reka-ui'
import { DialogClose } from "reka-ui" import { DialogClose } from 'reka-ui'
const props = defineProps<DialogCloseProps>() const props = defineProps<DialogCloseProps>()
</script> </script>

View File

@@ -1,27 +1,27 @@
<script setup lang="ts"> <script setup lang="ts">
import type { DialogContentEmits, DialogContentProps } from "reka-ui" import type { DialogContentEmits, DialogContentProps } from 'reka-ui'
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from "@vueuse/core" import { reactiveOmit } from '@vueuse/core'
import { X } from "lucide-vue-next" import { X } from 'lucide-vue-next'
import { import {
DialogClose, DialogClose,
DialogContent, DialogContent,
DialogPortal, DialogPortal,
useForwardPropsEmits, useForwardPropsEmits,
} from "reka-ui" } from 'reka-ui'
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils'
import DialogOverlay from "./DialogOverlay.vue" import DialogOverlay from './DialogOverlay.vue'
defineOptions({ defineOptions({
inheritAttrs: false, inheritAttrs: false,
}) })
const props = withDefaults(defineProps<DialogContentProps & { class?: HTMLAttributes["class"], showCloseButton?: boolean }>(), { const props = withDefaults(defineProps<DialogContentProps & { class?: HTMLAttributes['class'], showCloseButton?: boolean }>(), {
showCloseButton: true, showCloseButton: true,
}) })
const emits = defineEmits<DialogContentEmits>() const emits = defineEmits<DialogContentEmits>()
const delegatedProps = reactiveOmit(props, "class") const delegatedProps = reactiveOmit(props, 'class')
const forwarded = useForwardPropsEmits(delegatedProps, emits) const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script> </script>

View File

@@ -1,13 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import type { DialogDescriptionProps } from "reka-ui" import type { DialogDescriptionProps } from 'reka-ui'
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from "@vueuse/core" import { reactiveOmit } from '@vueuse/core'
import { DialogDescription, useForwardProps } from "reka-ui" import { DialogDescription, useForwardProps } from 'reka-ui'
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils'
const props = defineProps<DialogDescriptionProps & { class?: HTMLAttributes["class"] }>() const props = defineProps<DialogDescriptionProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = reactiveOmit(props, "class") const delegatedProps = reactiveOmit(props, 'class')
const forwardedProps = useForwardProps(delegatedProps) const forwardedProps = useForwardProps(delegatedProps)
</script> </script>

View File

@@ -1,8 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from 'vue'
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils'
const props = defineProps<{ class?: HTMLAttributes["class"] }>() const props = defineProps<{ class?: HTMLAttributes['class'] }>()
</script> </script>
<template> <template>

View File

@@ -1,9 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from 'vue'
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils'
const props = defineProps<{ const props = defineProps<{
class?: HTMLAttributes["class"] class?: HTMLAttributes['class']
}>() }>()
</script> </script>

View File

@@ -1,13 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import type { DialogOverlayProps } from "reka-ui" import type { DialogOverlayProps } from 'reka-ui'
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from "@vueuse/core" import { reactiveOmit } from '@vueuse/core'
import { DialogOverlay } from "reka-ui" import { DialogOverlay } from 'reka-ui'
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils'
const props = defineProps<DialogOverlayProps & { class?: HTMLAttributes["class"] }>() const props = defineProps<DialogOverlayProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = reactiveOmit(props, "class") const delegatedProps = reactiveOmit(props, 'class')
</script> </script>
<template> <template>

View File

@@ -1,25 +1,25 @@
<script setup lang="ts"> <script setup lang="ts">
import type { DialogContentEmits, DialogContentProps } from "reka-ui" import type { DialogContentEmits, DialogContentProps } from 'reka-ui'
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from "@vueuse/core" import { reactiveOmit } from '@vueuse/core'
import { X } from "lucide-vue-next" import { X } from 'lucide-vue-next'
import { import {
DialogClose, DialogClose,
DialogContent, DialogContent,
DialogOverlay, DialogOverlay,
DialogPortal, DialogPortal,
useForwardPropsEmits, useForwardPropsEmits,
} from "reka-ui" } from 'reka-ui'
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils'
defineOptions({ defineOptions({
inheritAttrs: false, inheritAttrs: false,
}) })
const props = defineProps<DialogContentProps & { class?: HTMLAttributes["class"] }>() const props = defineProps<DialogContentProps & { class?: HTMLAttributes['class'] }>()
const emits = defineEmits<DialogContentEmits>() const emits = defineEmits<DialogContentEmits>()
const delegatedProps = reactiveOmit(props, "class") const delegatedProps = reactiveOmit(props, 'class')
const forwarded = useForwardPropsEmits(delegatedProps, emits) const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script> </script>

View File

@@ -1,13 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import type { DialogTitleProps } from "reka-ui" import type { DialogTitleProps } from 'reka-ui'
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from "@vueuse/core" import { reactiveOmit } from '@vueuse/core'
import { DialogTitle, useForwardProps } from "reka-ui" import { DialogTitle, useForwardProps } from 'reka-ui'
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils'
const props = defineProps<DialogTitleProps & { class?: HTMLAttributes["class"] }>() const props = defineProps<DialogTitleProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = reactiveOmit(props, "class") const delegatedProps = reactiveOmit(props, 'class')
const forwardedProps = useForwardProps(delegatedProps) const forwardedProps = useForwardProps(delegatedProps)
</script> </script>

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { DialogTriggerProps } from "reka-ui" import type { DialogTriggerProps } from 'reka-ui'
import { DialogTrigger } from "reka-ui" import { DialogTrigger } from 'reka-ui'
const props = defineProps<DialogTriggerProps>() const props = defineProps<DialogTriggerProps>()
</script> </script>

View File

@@ -1,10 +1,10 @@
export { default as Dialog } from "./Dialog.vue" export { default as Dialog } from './Dialog.vue'
export { default as DialogClose } from "./DialogClose.vue" export { default as DialogClose } from './DialogClose.vue'
export { default as DialogContent } from "./DialogContent.vue" export { default as DialogContent } from './DialogContent.vue'
export { default as DialogDescription } from "./DialogDescription.vue" export { default as DialogDescription } from './DialogDescription.vue'
export { default as DialogFooter } from "./DialogFooter.vue" export { default as DialogFooter } from './DialogFooter.vue'
export { default as DialogHeader } from "./DialogHeader.vue" export { default as DialogHeader } from './DialogHeader.vue'
export { default as DialogOverlay } from "./DialogOverlay.vue" export { default as DialogOverlay } from './DialogOverlay.vue'
export { default as DialogScrollContent } from "./DialogScrollContent.vue" export { default as DialogScrollContent } from './DialogScrollContent.vue'
export { default as DialogTitle } from "./DialogTitle.vue" export { default as DialogTitle } from './DialogTitle.vue'
export { default as DialogTrigger } from "./DialogTrigger.vue" export { default as DialogTrigger } from './DialogTrigger.vue'

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { DropdownMenuRootEmits, DropdownMenuRootProps } from "reka-ui" import type { DropdownMenuRootEmits, DropdownMenuRootProps } from 'reka-ui'
import { DropdownMenuRoot, useForwardPropsEmits } from "reka-ui" import { DropdownMenuRoot, useForwardPropsEmits } from 'reka-ui'
const props = defineProps<DropdownMenuRootProps>() const props = defineProps<DropdownMenuRootProps>()
const emits = defineEmits<DropdownMenuRootEmits>() const emits = defineEmits<DropdownMenuRootEmits>()

View File

@@ -1,19 +1,19 @@
<script setup lang="ts"> <script setup lang="ts">
import type { DropdownMenuCheckboxItemEmits, DropdownMenuCheckboxItemProps } from "reka-ui" import type { DropdownMenuCheckboxItemEmits, DropdownMenuCheckboxItemProps } from 'reka-ui'
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from "@vueuse/core" import { reactiveOmit } from '@vueuse/core'
import { Check } from "lucide-vue-next" import { Check } from 'lucide-vue-next'
import { import {
DropdownMenuCheckboxItem, DropdownMenuCheckboxItem,
DropdownMenuItemIndicator, DropdownMenuItemIndicator,
useForwardPropsEmits, useForwardPropsEmits,
} from "reka-ui" } from 'reka-ui'
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils'
const props = defineProps<DropdownMenuCheckboxItemProps & { class?: HTMLAttributes["class"] }>() const props = defineProps<DropdownMenuCheckboxItemProps & { class?: HTMLAttributes['class'] }>()
const emits = defineEmits<DropdownMenuCheckboxItemEmits>() const emits = defineEmits<DropdownMenuCheckboxItemEmits>()
const delegatedProps = reactiveOmit(props, "class") const delegatedProps = reactiveOmit(props, 'class')
const forwarded = useForwardPropsEmits(delegatedProps, emits) const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script> </script>

View File

@@ -1,27 +1,27 @@
<script setup lang="ts"> <script setup lang="ts">
import type { DropdownMenuContentEmits, DropdownMenuContentProps } from "reka-ui" import type { DropdownMenuContentEmits, DropdownMenuContentProps } from 'reka-ui'
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from "@vueuse/core" import { reactiveOmit } from '@vueuse/core'
import { import {
DropdownMenuContent, DropdownMenuContent,
DropdownMenuPortal, DropdownMenuPortal,
useForwardPropsEmits, useForwardPropsEmits,
} from "reka-ui" } from 'reka-ui'
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils'
defineOptions({ defineOptions({
inheritAttrs: false, inheritAttrs: false,
}) })
const props = withDefaults( const props = withDefaults(
defineProps<DropdownMenuContentProps & { class?: HTMLAttributes["class"] }>(), defineProps<DropdownMenuContentProps & { class?: HTMLAttributes['class'] }>(),
{ {
sideOffset: 4, sideOffset: 4,
}, },
) )
const emits = defineEmits<DropdownMenuContentEmits>() const emits = defineEmits<DropdownMenuContentEmits>()
const delegatedProps = reactiveOmit(props, "class") const delegatedProps = reactiveOmit(props, 'class')
const forwarded = useForwardPropsEmits(delegatedProps, emits) const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script> </script>

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { DropdownMenuGroupProps } from "reka-ui" import type { DropdownMenuGroupProps } from 'reka-ui'
import { DropdownMenuGroup } from "reka-ui" import { DropdownMenuGroup } from 'reka-ui'
const props = defineProps<DropdownMenuGroupProps>() const props = defineProps<DropdownMenuGroupProps>()
</script> </script>

View File

@@ -1,19 +1,19 @@
<script setup lang="ts"> <script setup lang="ts">
import type { DropdownMenuItemProps } from "reka-ui" import type { DropdownMenuItemProps } from 'reka-ui'
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from "@vueuse/core" import { reactiveOmit } from '@vueuse/core'
import { DropdownMenuItem, useForwardProps } from "reka-ui" import { DropdownMenuItem, useForwardProps } from 'reka-ui'
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils'
const props = withDefaults(defineProps<DropdownMenuItemProps & { const props = withDefaults(defineProps<DropdownMenuItemProps & {
class?: HTMLAttributes["class"] class?: HTMLAttributes['class']
inset?: boolean inset?: boolean
variant?: "default" | "destructive" variant?: 'default' | 'destructive'
}>(), { }>(), {
variant: "default", variant: 'default',
}) })
const delegatedProps = reactiveOmit(props, "inset", "variant", "class") const delegatedProps = reactiveOmit(props, 'inset', 'variant', 'class')
const forwardedProps = useForwardProps(delegatedProps) const forwardedProps = useForwardProps(delegatedProps)
</script> </script>

View File

@@ -1,13 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import type { DropdownMenuLabelProps } from "reka-ui" import type { DropdownMenuLabelProps } from 'reka-ui'
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from "@vueuse/core" import { reactiveOmit } from '@vueuse/core'
import { DropdownMenuLabel, useForwardProps } from "reka-ui" import { DropdownMenuLabel, useForwardProps } from 'reka-ui'
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils'
const props = defineProps<DropdownMenuLabelProps & { class?: HTMLAttributes["class"], inset?: boolean }>() const props = defineProps<DropdownMenuLabelProps & { class?: HTMLAttributes['class'], inset?: boolean }>()
const delegatedProps = reactiveOmit(props, "class", "inset") const delegatedProps = reactiveOmit(props, 'class', 'inset')
const forwardedProps = useForwardProps(delegatedProps) const forwardedProps = useForwardProps(delegatedProps)
</script> </script>

View File

@@ -1,9 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import type { DropdownMenuRadioGroupEmits, DropdownMenuRadioGroupProps } from "reka-ui" import type { DropdownMenuRadioGroupEmits, DropdownMenuRadioGroupProps } from 'reka-ui'
import { import {
DropdownMenuRadioGroup, DropdownMenuRadioGroup,
useForwardPropsEmits, useForwardPropsEmits,
} from "reka-ui" } from 'reka-ui'
const props = defineProps<DropdownMenuRadioGroupProps>() const props = defineProps<DropdownMenuRadioGroupProps>()
const emits = defineEmits<DropdownMenuRadioGroupEmits>() const emits = defineEmits<DropdownMenuRadioGroupEmits>()

View File

@@ -1,20 +1,20 @@
<script setup lang="ts"> <script setup lang="ts">
import type { DropdownMenuRadioItemEmits, DropdownMenuRadioItemProps } from "reka-ui" import type { DropdownMenuRadioItemEmits, DropdownMenuRadioItemProps } from 'reka-ui'
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from "@vueuse/core" import { reactiveOmit } from '@vueuse/core'
import { Circle } from "lucide-vue-next" import { Circle } from 'lucide-vue-next'
import { import {
DropdownMenuItemIndicator, DropdownMenuItemIndicator,
DropdownMenuRadioItem, DropdownMenuRadioItem,
useForwardPropsEmits, useForwardPropsEmits,
} from "reka-ui" } from 'reka-ui'
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils'
const props = defineProps<DropdownMenuRadioItemProps & { class?: HTMLAttributes["class"] }>() const props = defineProps<DropdownMenuRadioItemProps & { class?: HTMLAttributes['class'] }>()
const emits = defineEmits<DropdownMenuRadioItemEmits>() const emits = defineEmits<DropdownMenuRadioItemEmits>()
const delegatedProps = reactiveOmit(props, "class") const delegatedProps = reactiveOmit(props, 'class')
const forwarded = useForwardPropsEmits(delegatedProps, emits) const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script> </script>

View File

@@ -1,17 +1,17 @@
<script setup lang="ts"> <script setup lang="ts">
import type { DropdownMenuSeparatorProps } from "reka-ui" import type { DropdownMenuSeparatorProps } from 'reka-ui'
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from "@vueuse/core" import { reactiveOmit } from '@vueuse/core'
import { import {
DropdownMenuSeparator, DropdownMenuSeparator,
} from "reka-ui" } from 'reka-ui'
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils'
const props = defineProps<DropdownMenuSeparatorProps & { const props = defineProps<DropdownMenuSeparatorProps & {
class?: HTMLAttributes["class"] class?: HTMLAttributes['class']
}>() }>()
const delegatedProps = reactiveOmit(props, "class") const delegatedProps = reactiveOmit(props, 'class')
</script> </script>
<template> <template>

View File

@@ -1,9 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from 'vue'
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils'
const props = defineProps<{ const props = defineProps<{
class?: HTMLAttributes["class"] class?: HTMLAttributes['class']
}>() }>()
</script> </script>

View File

@@ -1,9 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import type { DropdownMenuSubEmits, DropdownMenuSubProps } from "reka-ui" import type { DropdownMenuSubEmits, DropdownMenuSubProps } from 'reka-ui'
import { import {
DropdownMenuSub, DropdownMenuSub,
useForwardPropsEmits, useForwardPropsEmits,
} from "reka-ui" } from 'reka-ui'
const props = defineProps<DropdownMenuSubProps>() const props = defineProps<DropdownMenuSubProps>()
const emits = defineEmits<DropdownMenuSubEmits>() const emits = defineEmits<DropdownMenuSubEmits>()

View File

@@ -1,17 +1,17 @@
<script setup lang="ts"> <script setup lang="ts">
import type { DropdownMenuSubContentEmits, DropdownMenuSubContentProps } from "reka-ui" import type { DropdownMenuSubContentEmits, DropdownMenuSubContentProps } from 'reka-ui'
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from "@vueuse/core" import { reactiveOmit } from '@vueuse/core'
import { import {
DropdownMenuSubContent, DropdownMenuSubContent,
useForwardPropsEmits, useForwardPropsEmits,
} from "reka-ui" } from 'reka-ui'
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils'
const props = defineProps<DropdownMenuSubContentProps & { class?: HTMLAttributes["class"] }>() const props = defineProps<DropdownMenuSubContentProps & { class?: HTMLAttributes['class'] }>()
const emits = defineEmits<DropdownMenuSubContentEmits>() const emits = defineEmits<DropdownMenuSubContentEmits>()
const delegatedProps = reactiveOmit(props, "class") const delegatedProps = reactiveOmit(props, 'class')
const forwarded = useForwardPropsEmits(delegatedProps, emits) const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script> </script>

View File

@@ -1,17 +1,17 @@
<script setup lang="ts"> <script setup lang="ts">
import type { DropdownMenuSubTriggerProps } from "reka-ui" import type { DropdownMenuSubTriggerProps } from 'reka-ui'
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from "@vueuse/core" import { reactiveOmit } from '@vueuse/core'
import { ChevronRight } from "lucide-vue-next" import { ChevronRight } from 'lucide-vue-next'
import { import {
DropdownMenuSubTrigger, DropdownMenuSubTrigger,
useForwardProps, useForwardProps,
} from "reka-ui" } from 'reka-ui'
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils'
const props = defineProps<DropdownMenuSubTriggerProps & { class?: HTMLAttributes["class"], inset?: boolean }>() const props = defineProps<DropdownMenuSubTriggerProps & { class?: HTMLAttributes['class'], inset?: boolean }>()
const delegatedProps = reactiveOmit(props, "class", "inset") const delegatedProps = reactiveOmit(props, 'class', 'inset')
const forwardedProps = useForwardProps(delegatedProps) const forwardedProps = useForwardProps(delegatedProps)
</script> </script>

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { DropdownMenuTriggerProps } from "reka-ui" import type { DropdownMenuTriggerProps } from 'reka-ui'
import { DropdownMenuTrigger, useForwardProps } from "reka-ui" import { DropdownMenuTrigger, useForwardProps } from 'reka-ui'
const props = defineProps<DropdownMenuTriggerProps>() const props = defineProps<DropdownMenuTriggerProps>()

View File

@@ -1,16 +1,16 @@
export { default as DropdownMenu } from "./DropdownMenu.vue" export { default as DropdownMenu } from './DropdownMenu.vue'
export { default as DropdownMenuCheckboxItem } from "./DropdownMenuCheckboxItem.vue" export { default as DropdownMenuCheckboxItem } from './DropdownMenuCheckboxItem.vue'
export { default as DropdownMenuContent } from "./DropdownMenuContent.vue" export { default as DropdownMenuContent } from './DropdownMenuContent.vue'
export { default as DropdownMenuGroup } from "./DropdownMenuGroup.vue" export { default as DropdownMenuGroup } from './DropdownMenuGroup.vue'
export { default as DropdownMenuItem } from "./DropdownMenuItem.vue" export { default as DropdownMenuItem } from './DropdownMenuItem.vue'
export { default as DropdownMenuLabel } from "./DropdownMenuLabel.vue" export { default as DropdownMenuLabel } from './DropdownMenuLabel.vue'
export { default as DropdownMenuRadioGroup } from "./DropdownMenuRadioGroup.vue" export { default as DropdownMenuRadioGroup } from './DropdownMenuRadioGroup.vue'
export { default as DropdownMenuRadioItem } from "./DropdownMenuRadioItem.vue" export { default as DropdownMenuRadioItem } from './DropdownMenuRadioItem.vue'
export { default as DropdownMenuSeparator } from "./DropdownMenuSeparator.vue" export { default as DropdownMenuSeparator } from './DropdownMenuSeparator.vue'
export { default as DropdownMenuShortcut } from "./DropdownMenuShortcut.vue" export { default as DropdownMenuShortcut } from './DropdownMenuShortcut.vue'
export { default as DropdownMenuSub } from "./DropdownMenuSub.vue" export { default as DropdownMenuSub } from './DropdownMenuSub.vue'
export { default as DropdownMenuSubContent } from "./DropdownMenuSubContent.vue" export { default as DropdownMenuSubContent } from './DropdownMenuSubContent.vue'
export { default as DropdownMenuSubTrigger } from "./DropdownMenuSubTrigger.vue" export { default as DropdownMenuSubTrigger } from './DropdownMenuSubTrigger.vue'
export { default as DropdownMenuTrigger } from "./DropdownMenuTrigger.vue" export { default as DropdownMenuTrigger } from './DropdownMenuTrigger.vue'
export { DropdownMenuPortal } from "reka-ui" export { DropdownMenuPortal } from 'reka-ui'

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { PopoverRootEmits, PopoverRootProps } from "reka-ui" import type { PopoverRootEmits, PopoverRootProps } from 'reka-ui'
import { PopoverRoot, useForwardPropsEmits } from "reka-ui" import { PopoverRoot, useForwardPropsEmits } from 'reka-ui'
const props = defineProps<PopoverRootProps>() const props = defineProps<PopoverRootProps>()
const emits = defineEmits<PopoverRootEmits>() const emits = defineEmits<PopoverRootEmits>()

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { PopoverAnchorProps } from "reka-ui" import type { PopoverAnchorProps } from 'reka-ui'
import { PopoverAnchor } from "reka-ui" import { PopoverAnchor } from 'reka-ui'
const props = defineProps<PopoverAnchorProps>() const props = defineProps<PopoverAnchorProps>()
</script> </script>

View File

@@ -1,28 +1,28 @@
<script setup lang="ts"> <script setup lang="ts">
import type { PopoverContentEmits, PopoverContentProps } from "reka-ui" import type { PopoverContentEmits, PopoverContentProps } from 'reka-ui'
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from "@vueuse/core" import { reactiveOmit } from '@vueuse/core'
import { import {
PopoverContent, PopoverContent,
PopoverPortal, PopoverPortal,
useForwardPropsEmits, useForwardPropsEmits,
} from "reka-ui" } from 'reka-ui'
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils'
defineOptions({ defineOptions({
inheritAttrs: false, inheritAttrs: false,
}) })
const props = withDefaults( const props = withDefaults(
defineProps<PopoverContentProps & { class?: HTMLAttributes["class"] }>(), defineProps<PopoverContentProps & { class?: HTMLAttributes['class'] }>(),
{ {
align: "center", align: 'center',
sideOffset: 4, sideOffset: 4,
}, },
) )
const emits = defineEmits<PopoverContentEmits>() const emits = defineEmits<PopoverContentEmits>()
const delegatedProps = reactiveOmit(props, "class") const delegatedProps = reactiveOmit(props, 'class')
const forwarded = useForwardPropsEmits(delegatedProps, emits) const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script> </script>

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { PopoverTriggerProps } from "reka-ui" import type { PopoverTriggerProps } from 'reka-ui'
import { PopoverTrigger } from "reka-ui" import { PopoverTrigger } from 'reka-ui'
const props = defineProps<PopoverTriggerProps>() const props = defineProps<PopoverTriggerProps>()
</script> </script>

View File

@@ -1,4 +1,4 @@
export { default as Popover } from "./Popover.vue" export { default as Popover } from './Popover.vue'
export { default as PopoverAnchor } from "./PopoverAnchor.vue" export { default as PopoverAnchor } from './PopoverAnchor.vue'
export { default as PopoverContent } from "./PopoverContent.vue" export { default as PopoverContent } from './PopoverContent.vue'
export { default as PopoverTrigger } from "./PopoverTrigger.vue" export { default as PopoverTrigger } from './PopoverTrigger.vue'

View File

@@ -1,8 +1,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { ToasterProps } from "vue-sonner" import type { ToasterProps } from 'vue-sonner'
import { CircleCheckIcon, InfoIcon, Loader2Icon, OctagonXIcon, TriangleAlertIcon, XIcon } from "lucide-vue-next" import { CircleCheckIcon, InfoIcon, Loader2Icon, OctagonXIcon, TriangleAlertIcon, XIcon } from 'lucide-vue-next'
import { Toaster as Sonner } from "vue-sonner" import { Toaster as Sonner } from 'vue-sonner'
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils'
const props = defineProps<ToasterProps>() const props = defineProps<ToasterProps>()
</script> </script>

View File

@@ -1 +1 @@
export { default as Toaster } from "./Sonner.vue" export { default as Toaster } from './Sonner.vue'

View File

@@ -1,19 +1,19 @@
<script setup lang="ts"> <script setup lang="ts">
import type { SwitchRootEmits, SwitchRootProps } from "reka-ui" import type { SwitchRootEmits, SwitchRootProps } from 'reka-ui'
import type { HTMLAttributes } from "vue" import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from "@vueuse/core" import { reactiveOmit } from '@vueuse/core'
import { import {
SwitchRoot, SwitchRoot,
SwitchThumb, SwitchThumb,
useForwardPropsEmits, useForwardPropsEmits,
} from "reka-ui" } from 'reka-ui'
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils'
const props = defineProps<SwitchRootProps & { class?: HTMLAttributes["class"] }>() const props = defineProps<SwitchRootProps & { class?: HTMLAttributes['class'] }>()
const emits = defineEmits<SwitchRootEmits>() const emits = defineEmits<SwitchRootEmits>()
const delegatedProps = reactiveOmit(props, "class") const delegatedProps = reactiveOmit(props, 'class')
const forwarded = useForwardPropsEmits(delegatedProps, emits) const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script> </script>

View File

@@ -1 +1 @@
export { default as Switch } from "./Switch.vue" export { default as Switch } from './Switch.vue'

View File

@@ -1,10 +1,10 @@
export const footerList = { export const footerList = {
data: [ data: [
{ {
id: 0, id: 0,
name: 'Github', name: 'Github',
url: 'https://github.com/LOG1997', url: 'https://github.com/LOG1997',
icon: 'github', icon: 'github',
}, },
], ],
} }

View File

@@ -2,7 +2,7 @@
import { footerList } from './config' import { footerList } from './config'
function skip(url: string) { function skip(url: string) {
window.open(url) window.open(url)
} }
</script> </script>

View File

@@ -1,17 +1,17 @@
export const navList = [ export const navList = [
{ {
id: 0, id: 0,
name: '首页', name: '首页',
url: 'home', url: 'home',
}, },
{ {
id: 1, id: 1,
name: '项目', name: '项目',
url: 'project', url: 'project',
}, },
{ {
id: 2, id: 2,
name: '关于', name: '关于',
url: 'about', url: 'about',
}, },
] ]

View File

@@ -2,7 +2,7 @@
import { navList } from './config' import { navList } from './config'
function skip(url: string) { function skip(url: string) {
window.open(url, '_self') window.open(url, '_self')
} }
</script> </script>

View File

@@ -16,18 +16,18 @@ const settingRef = ref()
const fullScreenRef = ref() const fullScreenRef = ref()
function enterConfig() { function enterConfig() {
router.push('/log-lottery/config') router.push('/log-lottery/config')
} }
function enterHome() { function enterHome() {
router.push('/log-lottery') router.push('/log-lottery')
} }
onMounted(() => { onMounted(() => {
settingRef.value.addEventListener('mouseenter', () => { settingRef.value.addEventListener('mouseenter', () => {
fullScreenRef.value.style.display = 'block' fullScreenRef.value.style.display = 'block'
}) })
settingRef.value.addEventListener('mouseleave', () => { settingRef.value.addEventListener('mouseleave', () => {
fullScreenRef.value.style.display = 'none' fullScreenRef.value.style.display = 'none'
}) })
}) })
</script> </script>

View File

@@ -15,10 +15,10 @@ const mainContainer = ref<HTMLElement | null>(null)
const { y } = useScroll(mainContainer) const { y } = useScroll(mainContainer)
function scrollToTop() { function scrollToTop() {
mainContainer.value?.scrollTo({ mainContainer.value?.scrollTo({
top: 0, top: 0,
behavior: 'smooth', behavior: 'smooth',
}) })
} }
</script> </script>

View File

@@ -1,7 +1,7 @@
import type { ClassValue } from "clsx" import type { ClassValue } from 'clsx'
import { clsx } from "clsx" import { clsx } from 'clsx'
import { twMerge } from "tailwind-merge" import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) { export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)) return twMerge(clsx(inputs))
} }

View File

@@ -6,27 +6,27 @@ import zhCn from './zhCn'
export type Language = 'en' | 'zhCn' export type Language = 'en' | 'zhCn'
export const languageList = [ export const languageList = [
{ {
key: 'zhCn', key: 'zhCn',
name: '中文', name: '中文',
flag: 'zh-cn', flag: 'zh-cn',
}, },
{ {
key: 'en', key: 'en',
name: 'English', name: 'English',
flag: 'en-us', flag: 'en-us',
}, },
] ]
export const browserLanguage = navigator.language.toLowerCase().includes('zh') ? 'zhCn' : 'en' export const browserLanguage = navigator.language.toLowerCase().includes('zh') ? 'zhCn' : 'en'
const globalConfig = JSON.parse(localStorage.getItem('globalConfig') || '{}').globalConfig || {} const globalConfig = JSON.parse(localStorage.getItem('globalConfig') || '{}').globalConfig || {}
// 创建i18n // 创建i18n
const i18n = createI18n({ const i18n = createI18n({
locale: globalConfig.language || browserLanguage, locale: globalConfig.language || browserLanguage,
legacy: false, legacy: false,
messages: { messages: {
zhCn, zhCn,
en, en,
}, },
}) })
export default i18n export default i18n

View File

@@ -1,63 +1,63 @@
export const buttonEn = { export const buttonEn = {
enterLottery: 'Enter Lottery', enterLottery: 'Enter Lottery',
start: 'Start', start: 'Start',
selectLucky: 'Draw the Lucky', selectLucky: 'Draw the Lucky',
continue: 'Continue', continue: 'Continue',
confirm: 'Confirm', confirm: 'Confirm',
cancel: 'Cancel', cancel: 'Cancel',
setting: 'Setting', setting: 'Setting',
delete: 'Delete', delete: 'Delete',
allDelete: 'Delete All', allDelete: 'Delete All',
downloadTemplate: 'Download Template', downloadTemplate: 'Download Template',
importData: 'Import Data', importData: 'Import Data',
resetData: 'Reset Data', resetData: 'Reset Data',
exportResult: 'Export Result', exportResult: 'Export Result',
add: 'Add', add: 'Add',
resetDefault: 'Reset Default', resetDefault: 'Reset Default',
resetAllData: 'Reset All Data', resetAllData: 'Reset All Data',
clearPattern: 'Clear Pattern', clearPattern: 'Clear Pattern',
DefaultPattern: 'Default Pattern', DefaultPattern: 'Default Pattern',
upload: 'Upload', upload: 'Upload',
reset: 'Reset', reset: 'Reset',
play: 'Play', play: 'Play',
setLayout: 'Set Layout', setLayout: 'Set Layout',
close: 'Close', close: 'Close',
noInfoAndImport: 'No Info and import it', noInfoAndImport: 'No Info and import it',
useDefault: 'Use Default Data', useDefault: 'Use Default Data',
loading: 'Loading...', loading: 'Loading...',
} }
export const buttonZhCn = { export const buttonZhCn = {
enterLottery: '进入抽奖', enterLottery: '进入抽奖',
start: '开始', start: '开始',
selectLucky: '抽取幸运儿', selectLucky: '抽取幸运儿',
continue: '继续', continue: '继续',
confirm: '确认', confirm: '确认',
cancel: '取消', cancel: '取消',
setting: '设置', setting: '设置',
delete: '删除', delete: '删除',
allDelete: '删除全部', allDelete: '删除全部',
downloadTemplate: '下载模板', downloadTemplate: '下载模板',
importData: '导入数据', importData: '导入数据',
resetData: '重置数据', resetData: '重置数据',
exportResult: '导出结果', exportResult: '导出结果',
add: '添加', add: '添加',
resetDefault: '重置为默认', resetDefault: '重置为默认',
resetAllData: '重置所有数据', resetAllData: '重置所有数据',
clearPattern: '清除图案', clearPattern: '清除图案',
DefaultPattern: '默认图案', DefaultPattern: '默认图案',
upload: '上传', upload: '上传',
reset: '重置', reset: '重置',
play: '播放', play: '播放',
setLayout: '重设布局', setLayout: '重设布局',
close: '关闭', close: '关闭',
noInfoAndImport: '暂无人员信息,前往导入', noInfoAndImport: '暂无人员信息,前往导入',
useDefault: '使用默认数据', useDefault: '使用默认数据',
loading: '加载中...', loading: '加载中...',
} }
// 导出一个值 // 导出一个值
export const button = { export const button = {
en: buttonEn, en: buttonEn,
zhCn: buttonZhCn, zhCn: buttonZhCn,
} }

View File

@@ -1,34 +1,34 @@
export const dialogEn = { export const dialogEn = {
titleTip: 'Tip!', titleTip: 'Tip!',
titleTemporary: 'Add Temporary Activity', titleTemporary: 'Add Temporary Activity',
dialogPCWeb: 'Please use a PC browser to access for optimal display performance', dialogPCWeb: 'Please use a PC browser to access for optimal display performance',
dialogDelAllPerson: 'This operation will delete all personnel list data. Do you want to continue?', dialogDelAllPerson: 'This operation will delete all personnel list data. Do you want to continue?',
dialogResetWinner: 'This operation will clear the winning information of personnel. Do you want to continue?', dialogResetWinner: 'This operation will clear the winning information of personnel. Do you want to continue?',
dialogResetAllData: 'This operation will reset all data. Do you want to continue?', dialogResetAllData: 'This operation will reset all data. Do you want to continue?',
dialogSingleDrawLimit: 'Only 10 characters can be extracted in a single draw', dialogSingleDrawLimit: 'Only 10 characters can be extracted in a single draw',
dialogLatestBrowser: 'Please use the latest version of Chrome or Edge browser', dialogLatestBrowser: 'Please use the latest version of Chrome or Edge browser',
tipResetPrize: 'Performing operations may reset data and cant recover, please proceed with caution', tipResetPrize: 'Performing operations may reset data and cant recover, please proceed with caution',
uploadFileTitle: 'Upload File', uploadFileTitle: 'Upload File',
uploadImageTitle: 'Upload Image', uploadImageTitle: 'Upload Image',
uploadAudioTitle: 'Upload Audio', uploadAudioTitle: 'Upload Audio',
} }
export const dialogZhCn = { export const dialogZhCn = {
titleTip: '提示!', titleTip: '提示!',
titleTemporary: '增加临时抽奖', titleTemporary: '增加临时抽奖',
dialogPCWeb: '请使用PC进行访问以获得最佳显示效果', dialogPCWeb: '请使用PC进行访问以获得最佳显示效果',
dialogDelAllPerson: '该操作会删除所有人员数据,是否继续?', dialogDelAllPerson: '该操作会删除所有人员数据,是否继续?',
dialogResetWinner: '该操作会清空人员中奖信息,是否继续?', dialogResetWinner: '该操作会清空人员中奖信息,是否继续?',
dialogResetAllData: '该操作会重置所有数据,是否继续?', dialogResetAllData: '该操作会重置所有数据,是否继续?',
dialogSingleDrawLimit: '单次抽取只能抽取10位', dialogSingleDrawLimit: '单次抽取只能抽取10位',
dialogLatestBrowser: '请使用最新版Chrome或者Edge浏览器', dialogLatestBrowser: '请使用最新版Chrome或者Edge浏览器',
tipResetPrize: '进行操作可能会重置数据并不可恢复,请谨慎操作', tipResetPrize: '进行操作可能会重置数据并不可恢复,请谨慎操作',
uploadFileTitle: '上传文件', uploadFileTitle: '上传文件',
uploadImageTitle: '上传图片', uploadImageTitle: '上传图片',
uploadAudioTitle: '上传音频', uploadAudioTitle: '上传音频',
} }
export const dialog = { export const dialog = {
en: dialogEn, en: dialogEn,
zhCn: dialogZhCn, zhCn: dialogZhCn,
} }

View File

@@ -1,60 +1,58 @@
import { success } from 'zod'
export const errorEn = { export const errorEn = {
require: 'required field', require: 'required field',
requireNumber: 'please enter a number', requireNumber: 'please enter a number',
minNumber1: 'the minimum is 1', minNumber1: 'the minimum is 1',
maxNumber100: 'the maximum is 100', maxNumber100: 'the maximum is 100',
uploadSuccess: 'Upload Success', uploadSuccess: 'Upload Success',
uploadFail: 'Upload Failed', uploadFail: 'Upload Failed',
notImage: 'Not Image', notImage: 'Not Image',
personIsAllDone: 'All Person Is Done', personIsAllDone: 'All Person Is Done',
personNotEnough: 'Person Is Not Enough', personNotEnough: 'Person Is Not Enough',
startDraw: 'Now Draw {count} {leftover} people', startDraw: 'Now Draw {count} {leftover} people',
completeInformation: 'Please provide complete information', completeInformation: 'Please provide complete information',
notJsonFile: 'it isn\'t a JSON file', notJsonFile: 'it isn\'t a JSON file',
notAudioFile: 'it isn\'t an audio file', notAudioFile: 'it isn\'t an audio file',
personNameEmpty: 'Please enter name', personNameEmpty: 'Please enter name',
excelFileError: 'The header is inconsistent, please download the template, modify it, and then upload it', excelFileError: 'The header is inconsistent, please download the template, modify it, and then upload it',
exportSuccess: 'Export Success', exportSuccess: 'Export Success',
exportFail: 'Export Failed', exportFail: 'Export Failed',
importSuccess: 'Import Success', importSuccess: 'Import Success',
importFail: 'Import Failed', importFail: 'Import Failed',
downloadSuccess: '下载成功', downloadSuccess: '下载成功',
deleteSuccess: '删除成功', deleteSuccess: '删除成功',
deleteFail: '删除失败', deleteFail: '删除失败',
success: 'Success', success: 'Success',
fail: 'Failed', fail: 'Failed',
} }
export const errorZhCn = { export const errorZhCn = {
require: '必填项', require: '必填项',
requireNumber: '请输入数字', requireNumber: '请输入数字',
minNumber1: '最小为1', minNumber1: '最小为1',
maxNumber100: '最大为100', maxNumber100: '最大为100',
uploadSuccess: '上传成功', uploadSuccess: '上传成功',
uploadFail: '上传失败', uploadFail: '上传失败',
notImage: '不是图片', notImage: '不是图片',
personIsAllDone: '抽奖抽完了', personIsAllDone: '抽奖抽完了',
personNotEnough: '抽奖人数不足', personNotEnough: '抽奖人数不足',
startDraw: '现在抽取{count}{leftover}人', startDraw: '现在抽取{count}{leftover}人',
completeInformation: '请填写完整信息', completeInformation: '请填写完整信息',
notJsonFile: '这不是一个JSON文件', notJsonFile: '这不是一个JSON文件',
notAudioFile: '这不是一个音频文件', notAudioFile: '这不是一个音频文件',
personNameEmpty: '请填写姓名', personNameEmpty: '请填写姓名',
excelFileError: '表头不一致,请先下载模板然后修改后再上传', excelFileError: '表头不一致,请先下载模板然后修改后再上传',
exportSuccess: '导出成功', exportSuccess: '导出成功',
exportFail: '导出失败', exportFail: '导出失败',
importSuccess: '导入成功', importSuccess: '导入成功',
importFail: '导入失败', importFail: '导入失败',
downloadSuccess: '下载成功', downloadSuccess: '下载成功',
deleteSuccess: '删除成功', deleteSuccess: '删除成功',
deleteFail: '删除失败', deleteFail: '删除失败',
success: '成功', success: '成功',
fail: '失败', fail: '失败',
} }
export const error = { export const error = {
en: errorEn, en: errorEn,
zhCn: errorZhCn, zhCn: errorZhCn,
} }

View File

@@ -1,24 +1,24 @@
export const placeHolderEn = { export const placeHolderEn = {
enterTitle: 'Enter Title', enterTitle: 'Enter Title',
name: 'Name', name: 'Name',
winnerCount: 'Lucky Person Count', winnerCount: 'Lucky Person Count',
selectFont: 'Select Font', selectFont: 'Select Font',
timedStop: 'will stop at a scheduled time after starting', timedStop: 'will stop at a scheduled time after starting',
imageName: 'Image Name', imageName: 'Image Name',
personName: 'Please enter name', personName: 'Please enter name',
} }
export const placeHolderZhCn = { export const placeHolderZhCn = {
enterTitle: '输入标题', enterTitle: '输入标题',
name: '名称', name: '名称',
winnerCount: '中奖人数', winnerCount: '中奖人数',
selectFont: '选择字体', selectFont: '选择字体',
timedStop: '开始后定时抽取', timedStop: '开始后定时抽取',
imageName: '图片名称', imageName: '图片名称',
personName: '请填写姓名', personName: '请填写姓名',
} }
export const placeHolder = { export const placeHolder = {
en: placeHolderEn, en: placeHolderEn,
zhCn: placeHolderZhCn, zhCn: placeHolderZhCn,
} }

View File

@@ -1,44 +1,44 @@
export const tooltipEn = { export const tooltipEn = {
settingConfiguration: 'Setting/Configuration', settingConfiguration: 'Setting/Configuration',
nextSong: 'Right Click to Next Song', nextSong: 'Right Click to Next Song',
noSongPlay: 'No Song to Play', noSongPlay: 'No Song to Play',
prizeList: 'Prize List', prizeList: 'Prize List',
addActivity: 'Add Activity', addActivity: 'Add Activity',
downloadTemplateTip: 'After downloading the file, please fill in the data in Excel and save it in xlsx format', downloadTemplateTip: 'After downloading the file, please fill in the data in Excel and save it in xlsx format',
uploadExcelTip: 'Upload the modified Excel file', uploadExcelTip: 'Upload the modified Excel file',
leftClick: 'Left Click to Slice', leftClick: 'Left Click to Slice',
toHome: 'to Home', toHome: 'to Home',
resetLayout: 'This item is time-consuming and performance intensive', resetLayout: 'This item is time-consuming and performance intensive',
defaultLayout: 'The default pattern setting is valid for 17 columns, please set the number of other columns yourself', defaultLayout: 'The default pattern setting is valid for 17 columns, please set the number of other columns yourself',
doneCount: 'Number of winners', doneCount: 'Number of winners',
edit: 'Edit', edit: 'Edit',
delete: 'Delete', delete: 'Delete',
timedStop: 'After the lottery begins, it will stop at a scheduled time by default, set to 0, with the unit in seconds. A value of 0 disables the scheduled stopping function', timedStop: 'After the lottery begins, it will stop at a scheduled time by default, set to 0, with the unit in seconds. A value of 0 disables the scheduled stopping function',
uploadImage: 'Upload Image', uploadImage: 'Upload Image',
pleaseGoto: 'Please go to', pleaseGoto: 'Please go to',
} }
export const tooltipZhCn = { export const tooltipZhCn = {
settingConfiguration: '设置/配置', settingConfiguration: '设置/配置',
nextSong: '右键点击下一首', nextSong: '右键点击下一首',
noSongPlay: '没有音乐可以播放', noSongPlay: '没有音乐可以播放',
prizeList: '奖项列表', prizeList: '奖项列表',
addActivity: '添加抽奖', addActivity: '添加抽奖',
downloadTemplateTip: '下载文件后请在excel中填写数据并保存为xlsx格式', downloadTemplateTip: '下载文件后请在excel中填写数据并保存为xlsx格式',
uploadExcelTip: '上传修改好的excel文件', uploadExcelTip: '上传修改好的excel文件',
leftClick: '左键切割', leftClick: '左键切割',
toHome: '主页', toHome: '主页',
resetLayout: '该项比较耗费时间和性能', resetLayout: '该项比较耗费时间和性能',
defaultLayout: '默认图案设置针对17列时有效其他列数请自行设置', defaultLayout: '默认图案设置针对17列时有效其他列数请自行设置',
doneCount: '已抽取', doneCount: '已抽取',
edit: '编辑', edit: '编辑',
delete: '删除', delete: '删除',
timedStop: '开始抽奖过后定时停止默认为0单位为秒0为关闭定时停止功能', timedStop: '开始抽奖过后定时停止默认为0单位为秒0为关闭定时停止功能',
uploadImage: '上传图片', uploadImage: '上传图片',
pleaseGoto: '请先前往', pleaseGoto: '请先前往',
} }
export const tooltip = { export const tooltip = {
en: tooltipEn, en: tooltipEn,
zhCn: tooltipZhCn, zhCn: tooltipZhCn,
} }

View File

@@ -4,10 +4,10 @@ import { usePrizeConfig } from './prizeConfig'
import { useSystem } from './system' import { useSystem } from './system'
export default function useStore() { export default function useStore() {
return { return {
personConfig: usePersonConfig(), personConfig: usePersonConfig(),
prizeConfig: usePrizeConfig(), prizeConfig: usePrizeConfig(),
globalConfig: useGlobalConfig(), globalConfig: useGlobalConfig(),
system: useSystem(), system: useSystem(),
} }
} }

View File

@@ -3,175 +3,175 @@ import { defineStore } from 'pinia'
import { defaultCurrentPrize, defaultPrizeList } from './data' import { defaultCurrentPrize, defaultPrizeList } from './data'
export const usePrizeConfig = defineStore('prize', { export const usePrizeConfig = defineStore('prize', {
state() { state() {
return { return {
prizeConfig: { prizeConfig: {
prizeList: defaultPrizeList, prizeList: defaultPrizeList,
currentPrize: defaultCurrentPrize, currentPrize: defaultCurrentPrize,
temporaryPrize: { temporaryPrize: {
id: '', id: '',
name: '', name: '',
sort: 0, sort: 0,
isAll: false, isAll: false,
count: 1, count: 1,
isUsedCount: 0, isUsedCount: 0,
picture: { picture: {
id: '-1', id: '-1',
name: '', name: '',
url: '', url: '',
}, },
separateCount: { separateCount: {
enable: true, enable: true,
countList: [], countList: [],
}, },
desc: '', desc: '',
isShow: false, isShow: false,
isUsed: false, isUsed: false,
frequency: 1, frequency: 1,
} as IPrizeConfig, } as IPrizeConfig,
}, },
} }
}, },
getters: { getters: {
// 获取全部配置 // 获取全部配置
getPrizeConfigAll(state) { getPrizeConfigAll(state) {
return state.prizeConfig return state.prizeConfig
}, },
// 获取奖品列表 // 获取奖品列表
getPrizeConfig(state) { getPrizeConfig(state) {
return state.prizeConfig.prizeList return state.prizeConfig.prizeList
}, },
// 根据id获取配置 // 根据id获取配置
getPrizeConfigById(state) { getPrizeConfigById(state) {
return (id: number | string) => { return (id: number | string) => {
return state.prizeConfig.prizeList.find(item => item.id === id) return state.prizeConfig.prizeList.find(item => item.id === id)
} }
}, },
// 获取当前奖项 // 获取当前奖项
getCurrentPrize(state) { getCurrentPrize(state) {
return state.prizeConfig.currentPrize return state.prizeConfig.currentPrize
}, },
// 获取临时的奖项 // 获取临时的奖项
getTemporaryPrize(state) { getTemporaryPrize(state) {
return state.prizeConfig.temporaryPrize return state.prizeConfig.temporaryPrize
}, },
}, },
actions: { actions: {
// 设置奖项 // 设置奖项
setPrizeConfig(prizeList: IPrizeConfig[]) { setPrizeConfig(prizeList: IPrizeConfig[]) {
this.prizeConfig.prizeList = prizeList this.prizeConfig.prizeList = prizeList
},
// 添加奖项
addPrizeConfig(prizeConfigItem: IPrizeConfig) {
this.prizeConfig.prizeList.push(prizeConfigItem)
},
// 删除奖项
deletePrizeConfig(prizeConfigItemId: number | string) {
this.prizeConfig.prizeList = this.prizeConfig.prizeList.filter(item => item.id !== prizeConfigItemId)
},
// 更新奖项数据
updatePrizeConfig(prizeConfigItem: IPrizeConfig) {
const prizeListLength = this.prizeConfig.prizeList.length
if (prizeConfigItem.isUsed && prizeListLength) {
for (let i = 0; i < prizeListLength; i++) {
if (!this.prizeConfig.prizeList[i].isUsed) {
this.setCurrentPrize(this.prizeConfig.prizeList[i])
break
}
}
}
else {
return
}
this.resetTemporaryPrize()
},
// 删除全部奖项
deleteAllPrizeConfig() {
this.prizeConfig.prizeList = [] as IPrizeConfig[]
},
// 设置当前奖项
setCurrentPrize(prizeConfigItem: IPrizeConfig) {
this.prizeConfig.currentPrize = prizeConfigItem
},
// 设置临时奖项
setTemporaryPrize(prizeItem: IPrizeConfig) {
if (prizeItem.isShow === false) {
for (let i = 0; i < this.prizeConfig.prizeList.length; i++) {
if (this.prizeConfig.prizeList[i].isUsed === false) {
this.setCurrentPrize(this.prizeConfig.prizeList[i])
break
}
}
this.resetTemporaryPrize()
return
}
this.prizeConfig.temporaryPrize = prizeItem
},
// 重置临时奖项
resetTemporaryPrize() {
this.prizeConfig.temporaryPrize = {
id: '',
name: '',
sort: 0,
isAll: false,
count: 1,
isUsedCount: 0,
picture: {
id: '-1',
name: '',
url: '',
}, },
separateCount: { // 添加奖项
enable: true, addPrizeConfig(prizeConfigItem: IPrizeConfig) {
countList: [], this.prizeConfig.prizeList.push(prizeConfigItem)
},
// 删除奖项
deletePrizeConfig(prizeConfigItemId: number | string) {
this.prizeConfig.prizeList = this.prizeConfig.prizeList.filter(item => item.id !== prizeConfigItemId)
},
// 更新奖项数据
updatePrizeConfig(prizeConfigItem: IPrizeConfig) {
const prizeListLength = this.prizeConfig.prizeList.length
if (prizeConfigItem.isUsed && prizeListLength) {
for (let i = 0; i < prizeListLength; i++) {
if (!this.prizeConfig.prizeList[i].isUsed) {
this.setCurrentPrize(this.prizeConfig.prizeList[i])
break
}
}
}
else {
return
}
this.resetTemporaryPrize()
},
// 删除全部奖项
deleteAllPrizeConfig() {
this.prizeConfig.prizeList = [] as IPrizeConfig[]
},
// 设置当前奖项
setCurrentPrize(prizeConfigItem: IPrizeConfig) {
this.prizeConfig.currentPrize = prizeConfigItem
},
// 设置临时奖项
setTemporaryPrize(prizeItem: IPrizeConfig) {
if (prizeItem.isShow === false) {
for (let i = 0; i < this.prizeConfig.prizeList.length; i++) {
if (this.prizeConfig.prizeList[i].isUsed === false) {
this.setCurrentPrize(this.prizeConfig.prizeList[i])
break
}
}
this.resetTemporaryPrize()
return
}
this.prizeConfig.temporaryPrize = prizeItem
},
// 重置临时奖项
resetTemporaryPrize() {
this.prizeConfig.temporaryPrize = {
id: '',
name: '',
sort: 0,
isAll: false,
count: 1,
isUsedCount: 0,
picture: {
id: '-1',
name: '',
url: '',
},
separateCount: {
enable: true,
countList: [],
},
desc: '',
isShow: false,
isUsed: false,
frequency: 1,
} as IPrizeConfig
},
// 重置所有配置
resetDefault() {
this.prizeConfig = {
prizeList: defaultPrizeList,
currentPrize: defaultCurrentPrize,
temporaryPrize: {
id: '',
name: '',
sort: 0,
isAll: false,
count: 1,
isUsedCount: 0,
picture: {
id: '-1',
name: '',
url: '',
},
separateCount: {
enable: true,
countList: [],
},
desc: '',
isShow: false,
isUsed: false,
frequency: 1,
} as IPrizeConfig,
}
}, },
desc: '',
isShow: false,
isUsed: false,
frequency: 1,
} as IPrizeConfig
}, },
// 重置所有配置 persist: {
resetDefault() { enabled: true,
this.prizeConfig = { strategies: [
prizeList: defaultPrizeList, {
currentPrize: defaultCurrentPrize, // 如果要存储在localStorage中
temporaryPrize: { storage: localStorage,
id: '', key: 'prizeConfig',
name: '', },
sort: 0, ],
isAll: false,
count: 1,
isUsedCount: 0,
picture: {
id: '-1',
name: '',
url: '',
},
separateCount: {
enable: true,
countList: [],
},
desc: '',
isShow: false,
isUsed: false,
frequency: 1,
} as IPrizeConfig,
}
}, },
},
persist: {
enabled: true,
strategies: [
{
// 如果要存储在localStorage中
storage: localStorage,
key: 'prizeConfig',
},
],
},
}) })

View File

@@ -1,37 +1,37 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
// import { IPrizeConfig } from '@/types/storeType'; // import { IPrizeConfig } from '@/types/storeType';
export const useSystem = defineStore('system', { export const useSystem = defineStore('system', {
state() { state() {
return { return {
isMobile: false, isMobile: false,
isChrome: true, isChrome: true,
} }
},
getters: {
getIsMobile(state) {
return state.isMobile
}, },
getIsChrome(state) { getters: {
return state.isChrome getIsMobile(state) {
return state.isMobile
},
getIsChrome(state) {
return state.isChrome
},
}, },
}, actions: {
actions: { setIsMobile(isMobile: boolean) {
setIsMobile(isMobile: boolean) { this.isMobile = isMobile
this.isMobile = isMobile },
setIsChrome(isChrome: boolean) {
this.isChrome = isChrome
},
}, },
setIsChrome(isChrome: boolean) { persist: {
this.isChrome = isChrome enabled: true,
strategies: [
{
// 如果要存储在localStorage中
// storage: localStorage,
// key: 'globalConfig',
// paths: ['globalConfig'],
},
],
}, },
},
persist: {
enabled: true,
strategies: [
{
// 如果要存储在localStorage中
// storage: localStorage,
// key: 'globalConfig',
// paths: ['globalConfig'],
},
],
},
}) })

View File

@@ -1,3 +1,3 @@
export function getToken() { export function getToken() {
return window.localStorage.getItem('userToken') return window.localStorage.getItem('userToken')
} }

View File

@@ -42,32 +42,32 @@ export function rgba(color: string, opacity: number) {
export function rgbToHex(color: string) { export function rgbToHex(color: string) {
// 去掉字符串中的空格 // 去掉字符串中的空格
color = color.replace(/\s+/g, ''); color = color.replace(/\s+/g, '')
if (isHex(color)) { if (isHex(color)) {
return color return color
} }
// 匹配rgba或rgb格式的字符串 // 匹配rgba或rgb格式的字符串
const rgbaMatch = color.match(/^rgba?\((\d+),(\d+),(\d+),?(\d*\.?\d+)?\)$/i); const rgbaMatch = color.match(/^rgba?\((\d+),(\d+),(\d+),?(\d+(?:\.\d+)?|\.\d+)?\)$/i)
if (!rgbaMatch) { if (!rgbaMatch) {
throw new Error('Invalid color format'); throw new Error('Invalid color format')
} }
const r = parseInt(rgbaMatch[1], 10); const r = Number.parseInt(rgbaMatch[1], 10)
const g = parseInt(rgbaMatch[2], 10); const g = Number.parseInt(rgbaMatch[2], 10)
const b = parseInt(rgbaMatch[3], 10); const b = Number.parseInt(rgbaMatch[3], 10)
const a = rgbaMatch[4] !== undefined ? parseFloat(rgbaMatch[4]) : undefined; const a = rgbaMatch[4] !== undefined ? Number.parseFloat(rgbaMatch[4]) : undefined
// 将RGB值转换为十六进制 // 将RGB值转换为十六进制
let hex = "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase(); let hex = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase()}`
// 如果提供了alpha值则将其转换为十六进制并附加到结果中 // 如果提供了alpha值则将其转换为十六进制并附加到结果中
if (a !== undefined) { if (a !== undefined) {
let alphaHex = Math.round(a * 255).toString(16).toUpperCase(); let alphaHex = Math.round(a * 255).toString(16).toUpperCase()
if (alphaHex.length === 1) { if (alphaHex.length === 1) {
alphaHex = "0" + alphaHex; // 确保alpha值是两位数 alphaHex = `0${alphaHex}` // 确保alpha值是两位数
} }
hex += alphaHex; hex += alphaHex
} }
return hex; return hex
} }

View File

@@ -20,19 +20,19 @@ export function readFileData(file: File): Promise<{ data: string, fileName: stri
export function readFileDataAsBlob(file: File): Promise<{ data: Blob, fileName: string }> { export function readFileDataAsBlob(file: File): Promise<{ data: Blob, fileName: string }> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const reader = new FileReader(); const reader = new FileReader()
reader.onload = () => { reader.onload = () => {
// 直接使用原始文件作为 Blob // 直接使用原始文件作为 Blob
resolve({ data: file, fileName: file.name }); resolve({ data: file, fileName: file.name })
}; }
reader.onerror = () => { reader.onerror = () => {
reject(new Error('文件读取失败')); reject(new Error('文件读取失败'))
}; }
reader.readAsArrayBuffer(file); reader.readAsArrayBuffer(file)
}); })
} }
export async function readLocalFileAsArraybuffer(path: string): Promise<ArrayBuffer> { export async function readLocalFileAsArraybuffer(path: string): Promise<ArrayBuffer> {

View File

@@ -1,10 +1,10 @@
// 提取有哪些字段 // 提取有哪些字段
export function extractFields(data: any) { export function extractFields(data: any) {
const item = data[0] const item = data[0]
// 排除id x y其他都加入数组 // 排除id x y其他都加入数组
const keys = Object.keys(item).filter(key => key !== 'id' && key !== 'x' && key !== 'y') const keys = Object.keys(item).filter(key => key !== 'id' && key !== 'x' && key !== 'y')
if (keys.length > 0) { if (keys.length > 0) {
// 返回数组key value // 返回数组key value
return keys.map(key => ({ label: key, value: true })) return keys.map(key => ({ label: key, value: true }))
} }
} }

View File

@@ -2,36 +2,36 @@
import { computed } from 'vue' import { computed } from 'vue'
const props = defineProps({ const props = defineProps({
rowCount: { rowCount: {
type: Number, type: Number,
default: 17, default: 17,
}, },
cardColor: { cardColor: {
type: String, type: String,
default: '#fff', default: '#fff',
}, },
patternColor: { patternColor: {
type: String, type: String,
default: '#000', default: '#000',
}, },
patternList: { patternList: {
type: Array, type: Array,
default: () => [], default: () => [],
}, },
}) })
const data = computed(() => { const data = computed(() => {
return props return props
}) })
function updatePatternList(event: Event, item: number) { function updatePatternList(event: Event, item: number) {
if (data.value.patternList.includes(item)) { if (data.value.patternList.includes(item)) {
const index = data.value.patternList.indexOf(item) const index = data.value.patternList.indexOf(item)
data.value.patternList.splice(index, 1) data.value.patternList.splice(index, 1)
} }
else { else {
data.value.patternList.push(item) data.value.patternList.push(item)
} }
// emits // emits
} }
</script> </script>

View File

@@ -6,27 +6,27 @@ import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { import {
Command, Command,
CommandEmpty, CommandEmpty,
CommandGroup, CommandGroup,
CommandInput, CommandInput,
CommandItem, CommandItem,
CommandList, CommandList,
} from '@/components/ui/command' } from '@/components/ui/command'
import { import {
Popover, Popover,
PopoverContent, PopoverContent,
PopoverTrigger, PopoverTrigger,
} from '@/components/ui/popover' } from '@/components/ui/popover'
import { useLocalFonts } from '@/hooks/useLocalFonts' import { useLocalFonts } from '@/hooks/useLocalFonts'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
const props = defineProps<{ const props = defineProps<{
disabled?: boolean disabled?: boolean
}>() }>()
const selectedFont = defineModel('selectedFont', { const selectedFont = defineModel('selectedFont', {
type: String, type: String,
required: true, required: true,
}) })
const { getFonts, disabled: browserDisabled, fonts } = useLocalFonts() const { getFonts, disabled: browserDisabled, fonts } = useLocalFonts()
const open = ref(false) const open = ref(false)
@@ -34,25 +34,25 @@ const activeKey = ref('')
const debouncedActiveKey = refDebounced(activeKey, 20) const debouncedActiveKey = refDebounced(activeKey, 20)
const { t } = useI18n() const { t } = useI18n()
function selectFont(selectedValue: any) { function selectFont(selectedValue: any) {
open.value = false open.value = false
activeKey.value = '' activeKey.value = ''
selectedFont.value = selectedValue selectedFont.value = selectedValue
} }
function handelActiveKey(val: string) { function handelActiveKey(val: string) {
activeKey.value = val activeKey.value = val
} }
function handleScroll() { function handleScroll() {
activeKey.value = '' activeKey.value = ''
} }
const disabledStyle = computed(() => { const disabledStyle = computed(() => {
if (props.disabled || browserDisabled) { if (props.disabled || browserDisabled) {
return { return {
cursor: 'not-allowed', cursor: 'not-allowed',
}
} }
} return {}
return {}
}) })
</script> </script>

View File

@@ -7,48 +7,48 @@ import CustomDialog from '@/components/Dialog/index.vue'
import FileUpload from '@/components/FileUpload/index.vue' import FileUpload from '@/components/FileUpload/index.vue'
interface Props { interface Props {
importAllConfigData: (data: any) => void importAllConfigData: (data: any) => void
} }
const props = defineProps<Props>() const props = defineProps<Props>()
const toast = useToast() const toast = useToast()
const limitType = ref('application/json') const limitType = ref('application/json')
const visible = defineModel('visible', { const visible = defineModel('visible', {
type: Boolean, type: Boolean,
required: true, required: true,
}) })
const jsonFileData = ref<IFileData | null>(null) const jsonFileData = ref<IFileData | null>(null)
const { t } = useI18n() const { t } = useI18n()
const uploadDialogRef = ref() const uploadDialogRef = ref()
async function uploadFile(fileData: IFileData | null) { async function uploadFile(fileData: IFileData | null) {
if (!fileData) { if (!fileData) {
jsonFileData.value = null jsonFileData.value = null
return return
} }
const isJson = /application\/json/.test(fileData?.type || '') const isJson = /application\/json/.test(fileData?.type || '')
if (!isJson) { if (!isJson) {
toast.open({ toast.open({
message: t('error.notJsonFile'), message: t('error.notJsonFile'),
type: 'error', type: 'error',
position: 'top-right', position: 'top-right',
}) })
return return
} }
jsonFileData.value = fileData jsonFileData.value = fileData
} }
function submitUpload() { function submitUpload() {
if (jsonFileData.value) { if (jsonFileData.value) {
// 把文件转化为json数据 // 把文件转化为json数据
const jsonData = jsonFileData.value.data const jsonData = jsonFileData.value.data
props.importAllConfigData(jsonData) props.importAllConfigData(jsonData)
} }
} }
watch(visible, (newVal) => { watch(visible, (newVal) => {
if (newVal) { if (newVal) {
uploadDialogRef.value.showDialog() uploadDialogRef.value.showDialog()
} }
}) })
</script> </script>

View File

@@ -6,38 +6,38 @@ import { useViewModel } from './useViewModel'
const { t } = useI18n() const { t } = useI18n()
const { const {
resetData, resetData,
topTitleValue, topTitleValue,
languageValue, languageValue,
textSizeValue, textSizeValue,
currentFontValue, currentFontValue,
currentTitleFontValue, currentTitleFontValue,
titleFontSyncGlobalValue, titleFontSyncGlobalValue,
languageList, languageList,
formErr, formErr,
formData, formData,
cardSizeValue, cardSizeValue,
isShowPrizeListValue, isShowPrizeListValue,
isShowAvatarValue, isShowAvatarValue,
resetPersonLayout, resetPersonLayout,
isRowCountChange, isRowCountChange,
themeValue, themeValue,
backgroundImageValue, backgroundImageValue,
cardColorValue, cardColorValue,
luckyCardColorValue, luckyCardColorValue,
textColorValue, textColorValue,
patternColorValue, patternColorValue,
imageList, imageList,
rowCount, rowCount,
cardColor, cardColor,
patternColor, patternColor,
patternList, patternList,
clearPattern, clearPattern,
resetPattern, resetPattern,
exportAllConfigData, exportAllConfigData,
importAllConfigData, importAllConfigData,
definiteTimeValue, definiteTimeValue,
isWinMusicValue, isWinMusicValue,
} = useViewModel() } = useViewModel()
</script> </script>

View File

@@ -4,9 +4,9 @@ import { useI18n } from 'vue-i18n'
import UploadJsonModal from '../components/UploadDialog.vue' import UploadJsonModal from '../components/UploadDialog.vue'
interface Props { interface Props {
resetData: () => void resetData: () => void
exportAllConfigData: () => void exportAllConfigData: () => void
importAllConfigData: (data: any) => void importAllConfigData: (data: any) => void
} }
defineProps<Props>() defineProps<Props>()

View File

@@ -2,8 +2,8 @@
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
interface Props { interface Props {
resetPersonLayout: () => void resetPersonLayout: () => void
isRowCountChange: number isRowCountChange: number
} }
defineProps<Props>() defineProps<Props>()

View File

@@ -3,12 +3,12 @@ import { useI18n } from 'vue-i18n'
import PatternEdit from '../components/PatternEdit.vue' import PatternEdit from '../components/PatternEdit.vue'
interface Props { interface Props {
rowCount: number rowCount: number
cardColor: string cardColor: string
patternColor: string patternColor: string
patternList: number[] patternList: number[]
clearPattern: () => void clearPattern: () => void
resetPattern: () => void resetPattern: () => void
} }
defineProps<Props>() defineProps<Props>()
const { t } = useI18n() const { t } = useI18n()

View File

@@ -8,7 +8,7 @@ import { daisyuiThemes } from '@/constant/theme'
import 'vue3-colorpicker/style.css' import 'vue3-colorpicker/style.css'
interface Props { interface Props {
imageList: Array<IImage> imageList: Array<IImage>
} }
defineProps<Props>() defineProps<Props>()
const themeList = reactive(daisyuiThemes) const themeList = reactive(daisyuiThemes)

View File

@@ -7,235 +7,235 @@ import { themeChange } from '@/utils'
import { clearAllDbStore } from '@/utils/localforage' import { clearAllDbStore } from '@/utils/localforage'
export function useViewModel() { export function useViewModel() {
type ValidatePayload = zod.infer<typeof schema> type ValidatePayload = zod.infer<typeof schema>
const globalConfig = useStore().globalConfig const globalConfig = useStore().globalConfig
const personConfig = useStore().personConfig const personConfig = useStore().personConfig
const prizeConfig = useStore().prizeConfig const prizeConfig = useStore().prizeConfig
const { const {
getGlobalConfig: globalConfigData, getGlobalConfig: globalConfigData,
getTopTitle: topTitle, getTopTitle: topTitle,
getTheme: localTheme, getTheme: localTheme,
getPatterColor: patternColor, getPatterColor: patternColor,
getPatternList: patternList, getPatternList: patternList,
getCardColor: cardColor, getCardColor: cardColor,
getLuckyColor: luckyCardColor, getLuckyColor: luckyCardColor,
getTextColor: textColor, getTextColor: textColor,
getCardSize: cardSize, getCardSize: cardSize,
getTextSize: textSize, getTextSize: textSize,
getRowCount: rowCount, getRowCount: rowCount,
getIsShowPrizeList: isShowPrizeList, getIsShowPrizeList: isShowPrizeList,
getLanguage: userLanguage, getLanguage: userLanguage,
getBackground: backgroundImage, getBackground: backgroundImage,
getFont: currentFont, getFont: currentFont,
getTitleFont: currentTitleFont, getTitleFont: currentTitleFont,
getTitleFontSyncGlobal: titleFontSyncGlobal, getTitleFontSyncGlobal: titleFontSyncGlobal,
getImageList: imageList, getImageList: imageList,
getIsShowAvatar: isShowAvatar, getIsShowAvatar: isShowAvatar,
getDefiniteTime: definiteTime, getDefiniteTime: definiteTime,
getWinMusic: isWinMusic, getWinMusic: isWinMusic,
} = storeToRefs(globalConfig) } = storeToRefs(globalConfig)
const { getAlreadyPersonList: alreadyPersonList, getNotPersonList: notPersonList } = storeToRefs(personConfig) const { getAlreadyPersonList: alreadyPersonList, getNotPersonList: notPersonList } = storeToRefs(personConfig)
const isRowCountChange = ref(0) // 0未改变1改变,2加载中 const isRowCountChange = ref(0) // 0未改变1改变,2加载中
const themeValue = ref(localTheme.value.name) const themeValue = ref(localTheme.value.name)
const topTitleValue = ref(structuredClone(topTitle.value)) const topTitleValue = ref(structuredClone(topTitle.value))
const cardColorValue = ref(structuredClone(cardColor.value)) const cardColorValue = ref(structuredClone(cardColor.value))
const luckyCardColorValue = ref(structuredClone(luckyCardColor.value)) const luckyCardColorValue = ref(structuredClone(luckyCardColor.value))
const textColorValue = ref(structuredClone(textColor.value)) const textColorValue = ref(structuredClone(textColor.value))
const cardSizeValue = ref(structuredClone(cardSize.value)) const cardSizeValue = ref(structuredClone(cardSize.value))
const textSizeValue = ref(structuredClone(textSize.value)) const textSizeValue = ref(structuredClone(textSize.value))
const rowCountValue = ref(structuredClone(rowCount.value)) const rowCountValue = ref(structuredClone(rowCount.value))
const languageValue = ref(structuredClone(userLanguage.value)) const languageValue = ref(structuredClone(userLanguage.value))
const isShowPrizeListValue = ref(structuredClone(isShowPrizeList.value)) const isShowPrizeListValue = ref(structuredClone(isShowPrizeList.value))
const isShowAvatarValue = ref(structuredClone(isShowAvatar.value)) const isShowAvatarValue = ref(structuredClone(isShowAvatar.value))
const patternColorValue = ref(structuredClone(patternColor.value)) const patternColorValue = ref(structuredClone(patternColor.value))
const backgroundImageValue = ref(backgroundImage.value) const backgroundImageValue = ref(backgroundImage.value)
const currentFontValue = ref(structuredClone(currentFont.value)) const currentFontValue = ref(structuredClone(currentFont.value))
const currentTitleFontValue = ref(structuredClone(currentTitleFont.value)) const currentTitleFontValue = ref(structuredClone(currentTitleFont.value))
const titleFontSyncGlobalValue = ref(structuredClone(titleFontSyncGlobal.value)) const titleFontSyncGlobalValue = ref(structuredClone(titleFontSyncGlobal.value))
const definiteTimeValue = ref(structuredClone(definiteTime.value)) const definiteTimeValue = ref(structuredClone(definiteTime.value))
const isWinMusicValue = ref(structuredClone(isWinMusic.value)) const isWinMusicValue = ref(structuredClone(isWinMusic.value))
const formData = ref({ const formData = ref({
rowCount: rowCountValue, rowCount: rowCountValue,
})
const formErr = ref({
rowCount: '',
})
const schema = zod.object({
rowCount: zod.number({
error: i18n.global.t('error.require'),
// required_error: i18n.global.t('error.require'),
// invalid_type_error: i18n.global.t('error.requireNumber'),
}) })
.min(1, i18n.global.t('error.minNumber1')) const formErr = ref({
.max(100, i18n.global.t('error.maxNumber100')), rowCount: '',
// 格式化
})
const payload: ValidatePayload = {
rowCount: formData.value.rowCount,
}
function parseSchema(props: ValidatePayload) {
return schema.parseAsync(props)
}
function resetPersonLayout() {
isRowCountChange.value = 2
setTimeout(() => {
const alreadyLen = alreadyPersonList.value.length
const notLen = notPersonList.value.length
if (alreadyLen <= 0 && notLen <= 0) {
return
}
const allPersonList = alreadyPersonList.value.concat(notPersonList.value)
const newAlreadyPersonList = allPersonList.slice(0, alreadyLen)
const newNotPersonList = allPersonList.slice(alreadyLen, notLen + alreadyLen)
personConfig.deleteAllPerson()
personConfig.addNotPersonList(newNotPersonList)
personConfig.addAlreadyPersonList(newAlreadyPersonList, null)
isRowCountChange.value = 0
}, 1000)
}
function clearPattern() {
globalConfig.setPatternList([] as number[])
}
function resetPattern() {
globalConfig.resetPatternList()
}
function resetData() {
globalConfig.reset()
personConfig.reset()
prizeConfig.resetDefault()
// 删除所有indexDb
clearAllDbStore()
// 刷新页面
window.location.reload()
}
function exportAllConfigData() {
// const globalConfigData = globalConfig.getGlobalConfig()
// console.log(globalConfigData.value)
// const globalConfigData = globalConfig.getGlobalConfig()
const dataStr = JSON.stringify(globalConfigData.value, null, 2)
const dataUri = `data:application/json;charset=utf-8,${encodeURIComponent(dataStr)}`
const exportFileDefaultName = 'global-config.json'
const linkElement = document.createElement('a')
linkElement.setAttribute('href', dataUri)
linkElement.setAttribute('download', exportFileDefaultName)
linkElement.click()
}
function importAllConfigData(data: any) {
globalConfig.setGlobalConfig(data)
window.location.reload()
}
watch(() => formData.value.rowCount, () => {
payload.rowCount = formData.value.rowCount
parseSchema(payload).then((res) => {
if (res.rowCount) {
isRowCountChange.value = 1
globalConfig.setRowCount(res.rowCount)
}
}).catch((err) => {
formErr.value.rowCount = err.issues[0].message
}) })
}) const schema = zod.object({
rowCount: zod.number({
error: i18n.global.t('error.require'),
// required_error: i18n.global.t('error.require'),
// invalid_type_error: i18n.global.t('error.requireNumber'),
})
.min(1, i18n.global.t('error.minNumber1'))
.max(100, i18n.global.t('error.maxNumber100')),
// 格式化
watch(topTitleValue, (val) => { })
globalConfig.setTopTitle(val) const payload: ValidatePayload = {
}) rowCount: formData.value.rowCount,
watch(themeValue, (val: any) => { }
globalConfig.setTheme({ name: val }) function parseSchema(props: ValidatePayload) {
themeChange(val) return schema.parseAsync(props)
}, { deep: true }) }
watch(cardColorValue, (val: string) => { function resetPersonLayout() {
globalConfig.setCardColor(val) isRowCountChange.value = 2
}, { deep: true }) setTimeout(() => {
watch(luckyCardColorValue, (val: string) => { const alreadyLen = alreadyPersonList.value.length
globalConfig.setLuckyCardColor(val) const notLen = notPersonList.value.length
}, { deep: true }) if (alreadyLen <= 0 && notLen <= 0) {
watch(patternColorValue, (val: string) => { return
globalConfig.setPatterColor(val) }
}) const allPersonList = alreadyPersonList.value.concat(notPersonList.value)
watch(textColorValue, (val: string) => { const newAlreadyPersonList = allPersonList.slice(0, alreadyLen)
globalConfig.setTextColor(val) const newNotPersonList = allPersonList.slice(alreadyLen, notLen + alreadyLen)
}, { deep: true }) personConfig.deleteAllPerson()
personConfig.addNotPersonList(newNotPersonList)
personConfig.addAlreadyPersonList(newAlreadyPersonList, null)
watch(cardSizeValue, (val: { width: number, height: number }) => { isRowCountChange.value = 0
globalConfig.setCardSize(val) }, 1000)
}, { deep: true }) }
watch(isShowPrizeListValue, () => { function clearPattern() {
globalConfig.setIsShowPrizeList(isShowPrizeListValue.value) globalConfig.setPatternList([] as number[])
}) }
watch(backgroundImageValue, (val) => { function resetPattern() {
globalConfig.setBackground(val) globalConfig.resetPatternList()
}) }
watch(currentFontValue, (val) => {
globalConfig.setFont(val) function resetData() {
document.documentElement.style.setProperty('--app-font-family', `"${val}", -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif`) globalConfig.reset()
}) personConfig.reset()
watch(currentTitleFontValue, (val) => { prizeConfig.resetDefault()
globalConfig.setTitleFont(val) // 删除所有indexDb
}) clearAllDbStore()
watch(titleFontSyncGlobalValue, (val) => { // 刷新页面
globalConfig.setTitleFontSyncGlobal(val) window.location.reload()
}) }
watch(languageValue, (val: string) => {
globalConfig.setLanguage(val) function exportAllConfigData() {
}) // const globalConfigData = globalConfig.getGlobalConfig()
watch(isShowAvatarValue, () => { // console.log(globalConfigData.value)
globalConfig.setIsShowAvatar(isShowAvatarValue.value) // const globalConfigData = globalConfig.getGlobalConfig()
}) const dataStr = JSON.stringify(globalConfigData.value, null, 2)
watch(definiteTimeValue, () => { const dataUri = `data:application/json;charset=utf-8,${encodeURIComponent(dataStr)}`
globalConfig.setDefiniteTime(definiteTimeValue.value)
}) const exportFileDefaultName = 'global-config.json'
watch(isWinMusicValue, () => {
globalConfig.setIsPlayWinMusic(isWinMusicValue.value) const linkElement = document.createElement('a')
}) linkElement.setAttribute('href', dataUri)
watch(textSizeValue, (val: number) => { linkElement.setAttribute('download', exportFileDefaultName)
globalConfig.setTextSize(val) linkElement.click()
}) }
onMounted(() => { function importAllConfigData(data: any) {
}) globalConfig.setGlobalConfig(data)
return { window.location.reload()
resetData, }
topTitleValue,
languageValue, watch(() => formData.value.rowCount, () => {
textSizeValue, payload.rowCount = formData.value.rowCount
currentFontValue, parseSchema(payload).then((res) => {
currentTitleFontValue, if (res.rowCount) {
titleFontSyncGlobalValue, isRowCountChange.value = 1
languageList, globalConfig.setRowCount(res.rowCount)
formErr, }
formData, }).catch((err) => {
cardSizeValue, formErr.value.rowCount = err.issues[0].message
isShowPrizeListValue, })
isShowAvatarValue, })
resetPersonLayout,
isRowCountChange, watch(topTitleValue, (val) => {
themeValue, globalConfig.setTopTitle(val)
backgroundImageValue, })
cardColorValue, watch(themeValue, (val: any) => {
luckyCardColorValue, globalConfig.setTheme({ name: val })
textColorValue, themeChange(val)
patternColorValue, }, { deep: true })
imageList,
rowCount, watch(cardColorValue, (val: string) => {
cardColor, globalConfig.setCardColor(val)
patternColor, }, { deep: true })
patternList, watch(luckyCardColorValue, (val: string) => {
clearPattern, globalConfig.setLuckyCardColor(val)
resetPattern, }, { deep: true })
exportAllConfigData, watch(patternColorValue, (val: string) => {
importAllConfigData, globalConfig.setPatterColor(val)
definiteTimeValue, })
isWinMusicValue, watch(textColorValue, (val: string) => {
} globalConfig.setTextColor(val)
}, { deep: true })
watch(cardSizeValue, (val: { width: number, height: number }) => {
globalConfig.setCardSize(val)
}, { deep: true })
watch(isShowPrizeListValue, () => {
globalConfig.setIsShowPrizeList(isShowPrizeListValue.value)
})
watch(backgroundImageValue, (val) => {
globalConfig.setBackground(val)
})
watch(currentFontValue, (val) => {
globalConfig.setFont(val)
document.documentElement.style.setProperty('--app-font-family', `"${val}", -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif`)
})
watch(currentTitleFontValue, (val) => {
globalConfig.setTitleFont(val)
})
watch(titleFontSyncGlobalValue, (val) => {
globalConfig.setTitleFontSyncGlobal(val)
})
watch(languageValue, (val: string) => {
globalConfig.setLanguage(val)
})
watch(isShowAvatarValue, () => {
globalConfig.setIsShowAvatar(isShowAvatarValue.value)
})
watch(definiteTimeValue, () => {
globalConfig.setDefiniteTime(definiteTimeValue.value)
})
watch(isWinMusicValue, () => {
globalConfig.setIsPlayWinMusic(isWinMusicValue.value)
})
watch(textSizeValue, (val: number) => {
globalConfig.setTextSize(val)
})
onMounted(() => {
})
return {
resetData,
topTitleValue,
languageValue,
textSizeValue,
currentFontValue,
currentTitleFontValue,
titleFontSyncGlobalValue,
languageList,
formErr,
formData,
cardSizeValue,
isShowPrizeListValue,
isShowAvatarValue,
resetPersonLayout,
isRowCountChange,
themeValue,
backgroundImageValue,
cardColorValue,
luckyCardColorValue,
textColorValue,
patternColorValue,
imageList,
rowCount,
cardColor,
patternColor,
patternList,
clearPattern,
resetPattern,
exportAllConfigData,
importAllConfigData,
definiteTimeValue,
isWinMusicValue,
}
} }

View File

@@ -12,72 +12,72 @@ const { t } = useI18n()
const limitType = ref('image/*') const limitType = ref('image/*')
const imgUploadToast = ref(0) // 0是不显示1是成功2是失败,3是不是图片 const imgUploadToast = ref(0) // 0是不显示1是成功2是失败,3是不是图片
const visible = defineModel('visible', { const visible = defineModel('visible', {
type: Boolean, type: Boolean,
required: true, required: true,
}) })
const globalConfig = useStore().globalConfig const globalConfig = useStore().globalConfig
const imageDbStore = localforage.createInstance({ const imageDbStore = localforage.createInstance({
name: 'imgStore', name: 'imgStore',
}) })
const imageData = ref<IFileData | null>(null) const imageData = ref<IFileData | null>(null)
const fileName = computed({ const fileName = computed({
get() { get() {
return imageData.value?.fileName || null return imageData.value?.fileName || null
}, },
set(value) { set(value) {
if (imageData.value && value) { if (imageData.value && value) {
imageData.value.fileName = value imageData.value.fileName = value
} }
}, },
}) })
const uploadDialogRef = ref() const uploadDialogRef = ref()
async function uploadFile(fileData: IFileData | null) { async function uploadFile(fileData: IFileData | null) {
if (!fileData) { if (!fileData) {
imageData.value = null imageData.value = null
return return
} }
const isImage = /image*/.test(fileData?.type || '') const isImage = /image*/.test(fileData?.type || '')
if (!isImage) { if (!isImage) {
imgUploadToast.value = 3 imgUploadToast.value = 3
return return
} }
imageData.value = fileData imageData.value = fileData
} }
async function getImageDbStore() { async function getImageDbStore() {
const keys = await imageDbStore.keys() const keys = await imageDbStore.keys()
if (keys.length > 0) { if (keys.length > 0) {
imageDbStore.iterate((value: { fileName: string, data: Blob }, key: string) => { imageDbStore.iterate((value: { fileName: string, data: Blob }, key: string) => {
globalConfig.addImage({ globalConfig.addImage({
id: key, id: key,
name: value.fileName, name: value.fileName,
url: 'Storage', url: 'Storage',
}) })
}) })
} }
} }
function submitUpload() { function submitUpload() {
if (imageData.value) { if (imageData.value) {
const { data, fileName } = imageData.value const { data, fileName } = imageData.value
const uniqueId = uuidv4() const uniqueId = uuidv4()
imageDbStore.setItem(uniqueId, { imageDbStore.setItem(uniqueId, {
data, data,
fileName, fileName,
}) })
.then(() => { .then(() => {
imgUploadToast.value = 1 imgUploadToast.value = 1
getImageDbStore() getImageDbStore()
}) })
.catch(() => { .catch(() => {
imgUploadToast.value = 2 imgUploadToast.value = 2
}) })
} }
} }
watch(visible, (newVal) => { watch(visible, (newVal) => {
if (newVal) { if (newVal) {
uploadDialogRef.value.showDialog() uploadDialogRef.value.showDialog()
} }
}) })
</script> </script>

View File

@@ -14,25 +14,25 @@ const globalConfig = useStore().globalConfig
const { getImageList: localImageList } = storeToRefs(globalConfig) const { getImageList: localImageList } = storeToRefs(globalConfig)
const imgUploadToast = ref(0) // 0是不显示1是成功2是失败,3是不是图片 const imgUploadToast = ref(0) // 0是不显示1是成功2是失败,3是不是图片
const imageDbStore = localforage.createInstance({ const imageDbStore = localforage.createInstance({
name: 'imgStore', name: 'imgStore',
}) })
const uploadVisible = ref(false) const uploadVisible = ref(false)
function removeImage(item: IImage) { function removeImage(item: IImage) {
if (item.url === 'Storage') { if (item.url === 'Storage') {
imageDbStore.removeItem(item.id).then(() => { imageDbStore.removeItem(item.id).then(() => {
globalConfig.removeImage(item.id) globalConfig.removeImage(item.id)
}) })
} }
globalConfig.removeImage(item.id) globalConfig.removeImage(item.id)
} }
watch(() => imgUploadToast.value, (val) => { watch(() => imgUploadToast.value, (val) => {
if (val !== 0) { if (val !== 0) {
setTimeout(() => { setTimeout(() => {
imgUploadToast.value = 0 imgUploadToast.value = 0
}, 2000) }, 2000)
} }
}) })
</script> </script>

View File

@@ -13,84 +13,84 @@ const { t } = useI18n()
const toast = useToast() const toast = useToast()
const limitType = ref('audio/*') const limitType = ref('audio/*')
const visible = defineModel('visible', { const visible = defineModel('visible', {
type: Boolean, type: Boolean,
required: true, required: true,
}) })
const globalConfig = useStore().globalConfig const globalConfig = useStore().globalConfig
const audioDbStore = localforage.createInstance({ const audioDbStore = localforage.createInstance({
name: 'audioStore', name: 'audioStore',
}) })
const audioData = ref<IFileData | null>(null) const audioData = ref<IFileData | null>(null)
const fileName = computed({ const fileName = computed({
get() { get() {
return audioData.value?.fileName || null return audioData.value?.fileName || null
}, },
set(value) { set(value) {
if (audioData.value && value) { if (audioData.value && value) {
audioData.value.fileName = value audioData.value.fileName = value
} }
}, },
}) })
const uploadDialogRef = ref() const uploadDialogRef = ref()
async function uploadFile(fileData: IFileData | null) { async function uploadFile(fileData: IFileData | null) {
if (!fileData) { if (!fileData) {
audioData.value = null audioData.value = null
return return
} }
const isAudio = /audio*/.test(fileData?.type || '') const isAudio = /audio*/.test(fileData?.type || '')
if (!isAudio) { if (!isAudio) {
toast.open({ toast.open({
message: t('error.notAudioFile'), message: t('error.notAudioFile'),
type: 'error', type: 'error',
position: 'top-right', position: 'top-right',
}) })
return return
} }
audioData.value = fileData audioData.value = fileData
} }
async function getAudioDbStore() { async function getAudioDbStore() {
const keys = await audioDbStore.keys() const keys = await audioDbStore.keys()
if (keys.length > 0) { if (keys.length > 0) {
audioDbStore.iterate((value: { fileName: string, data: Blob }, key: string) => { audioDbStore.iterate((value: { fileName: string, data: Blob }, key: string) => {
globalConfig.addMusic({ globalConfig.addMusic({
id: key, id: key,
name: value.fileName, name: value.fileName,
url: 'Storage', url: 'Storage',
}) })
}) })
} }
} }
function submitUpload() { function submitUpload() {
if (audioData.value) { if (audioData.value) {
const { data, fileName } = audioData.value const { data, fileName } = audioData.value
const uniqueId = uuidv4() const uniqueId = uuidv4()
audioDbStore.setItem(uniqueId, { audioDbStore.setItem(uniqueId, {
data, data,
fileName, fileName,
})
.then(() => {
toast.open({
message: t('error.uploadSuccess'),
type: 'success',
position: 'top-right',
}) })
getAudioDbStore() .then(() => {
}) toast.open({
.catch(() => { message: t('error.uploadSuccess'),
toast.open({ type: 'success',
message: t('error.uploadFail'), position: 'top-right',
type: 'error', })
position: 'top-right', getAudioDbStore()
}) })
}) .catch(() => {
} toast.open({
message: t('error.uploadFail'),
type: 'error',
position: 'top-right',
})
})
}
} }
watch(visible, (newVal) => { watch(visible, (newVal) => {
if (newVal) { if (newVal) {
uploadDialogRef.value.showDialog() uploadDialogRef.value.showDialog()
} }
}) })
</script> </script>

View File

@@ -5,13 +5,12 @@ import { storeToRefs } from 'pinia'
import { ref } from 'vue' import { ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import PageHeader from '@/components/PageHeader/index.vue' import PageHeader from '@/components/PageHeader/index.vue'
import { sidebar } from '@/locales/modules'
import useStore from '@/store' import useStore from '@/store'
import UploadDialog from './components/UploadDialog.vue' import UploadDialog from './components/UploadDialog.vue'
const { t } = useI18n() const { t } = useI18n()
const audioDbStore = localforage.createInstance({ const audioDbStore = localforage.createInstance({
name: 'audioStore', name: 'audioStore',
}) })
const globalConfig = useStore().globalConfig const globalConfig = useStore().globalConfig
@@ -19,23 +18,23 @@ const { getMusicList: localMusicList } = storeToRefs(globalConfig)
const localMusicListValue = ref(localMusicList) const localMusicListValue = ref(localMusicList)
const uploadVisible = ref(false) const uploadVisible = ref(false)
async function play(item: IMusic) { async function play(item: IMusic) {
globalConfig.setCurrentMusic(item, false) globalConfig.setCurrentMusic(item, false)
} }
function deleteMusic(item: IMusic) { function deleteMusic(item: IMusic) {
globalConfig.removeMusic(item.id) globalConfig.removeMusic(item.id)
audioDbStore.removeItem(item.name) audioDbStore.removeItem(item.name)
// setTimeout(()=>{ // setTimeout(()=>{
// localMusicListValue.value=localMusicList // localMusicListValue.value=localMusicList
// },100) // },100)
} }
function resetMusic() { function resetMusic() {
globalConfig.resetMusicList() globalConfig.resetMusicList()
audioDbStore.clear() audioDbStore.clear()
} }
function deleteAll() { function deleteAll() {
globalConfig.clearMusicList() globalConfig.clearMusicList()
audioDbStore.clear() audioDbStore.clear()
} }
</script> </script>

View File

@@ -2,8 +2,8 @@
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
defineProps<{ defineProps<{
addOnePersonDrawerRef: any addOnePersonDrawerRef: any
addOnePerson: (addOnePersonDrawerRef: any, event: any) => void addOnePerson: (addOnePersonDrawerRef: any, event: any) => void
}>() }>()
const { t } = useI18n() const { t } = useI18n()

View File

@@ -11,43 +11,43 @@ let allData: any[] = []
function headersEqual(template: string[], actual: string[]): boolean { function headersEqual(template: string[], actual: string[]): boolean {
return template.length >= actual.length return template.length >= actual.length
&& actual.some(item => template.includes(item)) && actual.some(item => template.includes(item))
} }
// 接收主线程消息 // 接收主线程消息
globalThis.onmessage = async (e: MessageEvent<WorkerMessage>) => { globalThis.onmessage = async (e: MessageEvent<WorkerMessage>) => {
switch (e.data.type) { switch (e.data.type) {
case 'start': case 'start':
{ {
const fileData = e.data.data const fileData = e.data.data
const templateData = e.data.templateData const templateData = e.data.templateData
const workBook = XLSX.read(fileData, { type: 'binary', cellDates: true }) const workBook = XLSX.read(fileData, { type: 'binary', cellDates: true })
const workSheet = workBook.Sheets[workBook.SheetNames[0]] const workSheet = workBook.Sheets[workBook.SheetNames[0]]
const excelData: object[] = XLSX.utils.sheet_to_json(workSheet) const excelData: object[] = XLSX.utils.sheet_to_json(workSheet)
const templateWorkBook = XLSX.read(templateData, { type: 'array', cellDates: true }) const templateWorkBook = XLSX.read(templateData, { type: 'array', cellDates: true })
const templateWorkSheet = templateWorkBook.Sheets[templateWorkBook.SheetNames[0]] const templateWorkSheet = templateWorkBook.Sheets[templateWorkBook.SheetNames[0]]
const templateExcelData: object[] = XLSX.utils.sheet_to_json(templateWorkSheet) const templateExcelData: object[] = XLSX.utils.sheet_to_json(templateWorkSheet)
const templateHeader = Object.keys(templateExcelData[0]) const templateHeader = Object.keys(templateExcelData[0])
const header = Object.keys(excelData[0]) const header = Object.keys(excelData[0])
if (!headersEqual(templateHeader, header)) { if (!headersEqual(templateHeader, header)) {
globalThis.postMessage({
type: 'error',
data: null,
message: 'not right template',
})
return
}
allData = addOtherInfo(excelData)
globalThis.postMessage({ globalThis.postMessage({
type: 'done', type: 'error',
data: allData, data: null,
message: '读取完成', message: 'not right template',
}) })
break return
} }
allData = addOtherInfo(excelData)
globalThis.postMessage({
type: 'done',
data: allData,
message: '读取完成',
})
break
}
default: default:
globalThis.postMessage({ globalThis.postMessage({
type: 'fail', type: 'fail',

View File

@@ -14,16 +14,16 @@ const delAllDataDialogRef = ref()
const exportInputFileRef = ref() const exportInputFileRef = ref()
const addOnePersonDrawerRef = ref() const addOnePersonDrawerRef = ref()
const { const {
resetData, resetData,
deleteAll, deleteAll,
handleFileChange, handleFileChange,
exportData, exportData,
addOnePerson, addOnePerson,
singlePersonData, singlePersonData,
alreadyPersonList, alreadyPersonList,
allPersonList, allPersonList,
tableColumnList, tableColumnList,
downloadTemplate, downloadTemplate,
} = useViewModel({ exportInputFileRef }) } = useViewModel({ exportInputFileRef })
const { t } = useI18n() const { t } = useI18n()
const limitType = '.xlsx,.xls' const limitType = '.xlsx,.xls'

View File

@@ -17,185 +17,185 @@ import ImportExcelWorker from './importExcel.worker?worker'
type IBasePersonConfig = Pick<IPersonConfig, 'uid' | 'name' | 'department' | 'identity' | 'avatar'> type IBasePersonConfig = Pick<IPersonConfig, 'uid' | 'name' | 'department' | 'identity' | 'avatar'>
export function useViewModel({ exportInputFileRef }: { exportInputFileRef: Ref<HTMLInputElement> }) { export function useViewModel({ exportInputFileRef }: { exportInputFileRef: Ref<HTMLInputElement> }) {
const { t } = useI18n() const { t } = useI18n()
const baseUrl = import.meta.env.BASE_URL const baseUrl = import.meta.env.BASE_URL.replace('./', '/')
const toast = useToast() const toast = useToast()
const worker: Worker | null = new ImportExcelWorker() const worker: Worker | null = new ImportExcelWorker()
const loading = inject(loadingKey) const loading = inject(loadingKey)
const personConfig = useStore().personConfig const personConfig = useStore().personConfig
const { getAllPersonList: allPersonList, getAlreadyPersonList: alreadyPersonList } = storeToRefs(personConfig) const { getAllPersonList: allPersonList, getAlreadyPersonList: alreadyPersonList } = storeToRefs(personConfig)
const tableColumnList = tableColumns({ handleDeletePerson: delPersonItem }) const tableColumnList = tableColumns({ handleDeletePerson: delPersonItem })
const addPersonModalVisible = ref(false) const addPersonModalVisible = ref(false)
const singlePersonData = ref<IBasePersonConfig>({ const singlePersonData = ref<IBasePersonConfig>({
uid: '', uid: '',
name: '', name: '',
department: '', department: '',
avatar: '', avatar: '',
identity: '', identity: '',
}) })
async function getExcelTemplateContent() { async function getExcelTemplateContent() {
const locale = i18n.global.locale.value const locale = i18n.global.locale.value
if (locale === 'zhCn') { if (locale === 'zhCn') {
const templateData = await readLocalFileAsArraybuffer(`${import.meta.env.BASE_URL}人口登记表-zhCn.xlsx`) const templateData = await readLocalFileAsArraybuffer(`${baseUrl}人口登记表-zhCn.xlsx`)
return templateData return templateData
}
else {
const templateData = await readLocalFileAsArraybuffer(`${import.meta.env.BASE_URL}personListTemplate-en.xlsx`)
return templateData
}
}
/// 向worker发送消息
function sendWorkerMessage(message: any) {
if (worker) {
worker.postMessage(message)
}
}
/// 开始导入
async function startWorker(data: string) {
loading?.show()
getExcelTemplateContent()
sendWorkerMessage({ type: 'start', data, templateData: await getExcelTemplateContent() })
}
/**
* 获取用户数据
*/
async function handleFileChange(e: Event) {
if (worker) {
worker.onmessage = (e) => {
if (e.data.type === 'done') {
personConfig.resetPerson()
personConfig.addNotPersonList(e.data.data)
// 提示导入成功
toast.open({
message: t('error.importSuccess'),
type: 'success',
position: 'top-right',
})
// 导入成功后清空file input
clearFileInput()
} }
if (e.data.type === 'error') { else {
if (e.data.message === 'not right template') { const templateData = await readLocalFileAsArraybuffer(`${baseUrl}personListTemplate-en.xlsx`)
toast.open({ return templateData
message: t('error.excelFileError'),
type: 'error',
position: 'top-right',
})
return
}
toast.open({
message: e.data.message || t('error.importFail'),
type: 'error',
position: 'top-right',
})
// toast.warning(e.data.message || '导入错误')
} }
loading?.hide()
}
} }
const dataBinary = await readFileBinary(((e.target as HTMLInputElement).files as FileList)[0]!) /// 向worker发送消息
startWorker(dataBinary) function sendWorkerMessage(message: any) {
} if (worker) {
// 清空file input worker.postMessage(message)
function clearFileInput() { }
if (exportInputFileRef.value) {
exportInputFileRef.value.value = ''
} }
} /// 开始导入
function downloadTemplate() { async function startWorker(data: string) {
loading?.show()
getExcelTemplateContent()
sendWorkerMessage({ type: 'start', data, templateData: await getExcelTemplateContent() })
}
/**
* 获取用户数据
*/
async function handleFileChange(e: Event) {
if (worker) {
worker.onmessage = (e) => {
if (e.data.type === 'done') {
personConfig.resetPerson()
personConfig.addNotPersonList(e.data.data)
// 提示导入成功
toast.open({
message: t('error.importSuccess'),
type: 'success',
position: 'top-right',
})
// 导入成功后清空file input
clearFileInput()
}
if (e.data.type === 'error') {
if (e.data.message === 'not right template') {
toast.open({
message: t('error.excelFileError'),
type: 'error',
position: 'top-right',
})
return
}
toast.open({
message: e.data.message || t('error.importFail'),
type: 'error',
position: 'top-right',
})
// toast.warning(e.data.message || '导入错误')
}
loading?.hide()
}
}
const dataBinary = await readFileBinary(((e.target as HTMLInputElement).files as FileList)[0]!)
startWorker(dataBinary)
}
// 清空file input
function clearFileInput() {
if (exportInputFileRef.value) {
exportInputFileRef.value.value = ''
}
}
function downloadTemplate() {
// 下载 // 下载
const templateFileName = i18n.global.t('data.xlsxName') const templateFileName = i18n.global.t('data.xlsxName')
const fileUrl = `${baseUrl}${templateFileName}` const fileUrl = `${baseUrl}${templateFileName}`
fetch(fileUrl) fetch(fileUrl)
.then(res => res.blob()) .then(res => res.blob())
.then((blob) => { .then((blob) => {
const url = window.URL.createObjectURL(blob) const url = window.URL.createObjectURL(blob)
const a = document.createElement('a') const a = document.createElement('a')
a.href = url a.href = url
a.download = templateFileName a.download = templateFileName
a.click() a.click()
toast.open({ toast.open({
message: t('error.downloadSuccess'), message: t('error.downloadSuccess'),
type: 'success', type: 'success',
position: 'top-right', position: 'top-right',
}) })
}) })
}
// 导出数据
function exportData() {
let data = JSON.parse(JSON.stringify(allPersonList.value))
// 排除一些字段
for (let i = 0; i < data.length; i++) {
delete data[i].x
delete data[i].y
delete data[i].id
delete data[i].createTime
delete data[i].updateTime
delete data[i].prizeId
// 修改字段名称
if (data[i].isWin) {
data[i].isWin = i18n.global.t('data.yes')
}
else {
data[i].isWin = i18n.global.t('data.no')
}
// 格式化数组为
data[i].prizeTime = data[i].prizeTime.join(',')
data[i].prizeName = data[i].prizeName.join(',')
} }
let dataString = JSON.stringify(data) // 导出数据
dataString = dataString function exportData() {
.replaceAll(/uid/g, i18n.global.t('data.number')) let data = JSON.parse(JSON.stringify(allPersonList.value))
.replaceAll(/isWin/g, i18n.global.t('data.isWin')) // 排除一些字段
.replaceAll(/department/g, i18n.global.t('data.department')) for (let i = 0; i < data.length; i++) {
.replaceAll(/name/g, i18n.global.t('data.name')) delete data[i].x
.replaceAll(/identity/g, i18n.global.t('data.identity')) delete data[i].y
.replaceAll(/prizeName/g, i18n.global.t('data.prizeName')) delete data[i].id
.replaceAll(/prizeTime/g, i18n.global.t('data.prizeTime')) delete data[i].createTime
delete data[i].updateTime
delete data[i].prizeId
// 修改字段名称
if (data[i].isWin) {
data[i].isWin = i18n.global.t('data.yes')
}
else {
data[i].isWin = i18n.global.t('data.no')
}
// 格式化数组为
data[i].prizeTime = data[i].prizeTime.join(',')
data[i].prizeName = data[i].prizeName.join(',')
}
let dataString = JSON.stringify(data)
dataString = dataString
.replaceAll(/uid/g, i18n.global.t('data.number'))
.replaceAll(/isWin/g, i18n.global.t('data.isWin'))
.replaceAll(/department/g, i18n.global.t('data.department'))
.replaceAll(/name/g, i18n.global.t('data.name'))
.replaceAll(/identity/g, i18n.global.t('data.identity'))
.replaceAll(/prizeName/g, i18n.global.t('data.prizeName'))
.replaceAll(/prizeTime/g, i18n.global.t('data.prizeTime'))
data = JSON.parse(dataString) data = JSON.parse(dataString)
if (data.length > 0) { if (data.length > 0) {
const dataBinary = XLSX.utils.json_to_sheet(data) const dataBinary = XLSX.utils.json_to_sheet(data)
const dataBinaryBinary = XLSX.utils.book_new() const dataBinaryBinary = XLSX.utils.book_new()
XLSX.utils.book_append_sheet(dataBinaryBinary, dataBinary, 'Sheet1') XLSX.utils.book_append_sheet(dataBinaryBinary, dataBinary, 'Sheet1')
XLSX.writeFile(dataBinaryBinary, 'data.xlsx') XLSX.writeFile(dataBinaryBinary, 'data.xlsx')
}
} }
}
function resetData() { function resetData() {
personConfig.resetAlreadyPerson() personConfig.resetAlreadyPerson()
} }
function deleteAll() { function deleteAll() {
personConfig.deleteAllPerson() personConfig.deleteAllPerson()
} }
function delPersonItem(row: IPersonConfig) { function delPersonItem(row: IPersonConfig) {
personConfig.deletePerson(row) personConfig.deletePerson(row)
} }
function addOnePerson(addOnePersonDrawerRef: any, event: any) { function addOnePerson(addOnePersonDrawerRef: any, event: any) {
event.preventDefault() event.preventDefault()
// 表单中的验证信息清除 // 表单中的验证信息清除
const personData = addOtherInfo([toRaw(singlePersonData.value)]) const personData = addOtherInfo([toRaw(singlePersonData.value)])
personData[0].id = uuidv4() personData[0].id = uuidv4()
personConfig.addOnePerson(personData) personConfig.addOnePerson(personData)
// singlePersonData.value = {} as IBasePersonConfig // singlePersonData.value = {} as IBasePersonConfig
addOnePersonDrawerRef.closeDrawer() addOnePersonDrawerRef.closeDrawer()
singlePersonData.value = {} as IBasePersonConfig singlePersonData.value = {} as IBasePersonConfig
} }
return { return {
resetData, resetData,
deleteAll, deleteAll,
handleFileChange, handleFileChange,
exportData, exportData,
alreadyPersonList, alreadyPersonList,
allPersonList, allPersonList,
tableColumnList, tableColumnList,
addOnePerson, addOnePerson,
addPersonModalVisible, addPersonModalVisible,
singlePersonData, singlePersonData,
downloadTemplate, downloadTemplate,
} }
} }

View File

@@ -8,133 +8,133 @@ import i18n from '@/locales/i18n'
import useStore from '@/store' import useStore from '@/store'
export function usePrizeConfig() { export function usePrizeConfig() {
const toast = useToast() const toast = useToast()
const imageDbStore = localforage.createInstance({ const imageDbStore = localforage.createInstance({
name: 'imgStore', name: 'imgStore',
}) })
const prizeConfig = useStore().prizeConfig const prizeConfig = useStore().prizeConfig
const globalConfig = useStore().globalConfig const globalConfig = useStore().globalConfig
const { getPrizeConfig: localPrizeList, getCurrentPrize: currentPrize } = storeToRefs(prizeConfig) const { getPrizeConfig: localPrizeList, getCurrentPrize: currentPrize } = storeToRefs(prizeConfig)
const { getImageList: localImageList } = storeToRefs(globalConfig) const { getImageList: localImageList } = storeToRefs(globalConfig)
const imgList = ref<any[]>([]) const imgList = ref<any[]>([])
const prizeList = ref(cloneDeep(localPrizeList.value)) const prizeList = ref(cloneDeep(localPrizeList.value))
const selectedPrize = ref<IPrizeConfig | null>() const selectedPrize = ref<IPrizeConfig | null>()
function selectPrize(item: IPrizeConfig) { function selectPrize(item: IPrizeConfig) {
selectedPrize.value = item selectedPrize.value = item
selectedPrize.value.isUsedCount = 0 selectedPrize.value.isUsedCount = 0
selectedPrize.value.isUsed = false selectedPrize.value.isUsed = false
if (selectedPrize.value.separateCount.countList.length > 1) { if (selectedPrize.value.separateCount.countList.length > 1) {
return return
}
selectedPrize.value.separateCount = {
enable: true,
countList: [
{
id: '0',
count: item.count,
isUsedCount: 0,
},
],
}
} }
selectedPrize.value.separateCount = {
enable: true,
countList: [
{
id: '0',
count: item.count,
isUsedCount: 0,
},
],
}
}
function changePrizeStatus(item: IPrizeConfig) { function changePrizeStatus(item: IPrizeConfig) {
item.isUsed ? item.isUsedCount = 0 : item.isUsedCount = item.count item.isUsed ? item.isUsedCount = 0 : item.isUsedCount = item.count
item.separateCount.countList = [] item.separateCount.countList = []
item.isUsed = !item.isUsed item.isUsed = !item.isUsed
}
function changePrizePerson(item: IPrizeConfig) {
let indexPrize = -1
for (let i = 0; i < prizeList.value.length; i++) {
if (prizeList.value[i].id === item.id) {
indexPrize = i
break
}
} }
if (indexPrize > -1) {
prizeList.value[indexPrize].separateCount.countList = []
prizeList.value[indexPrize].isUsed ? prizeList.value[indexPrize].isUsedCount = prizeList.value[indexPrize].count : prizeList.value[indexPrize].isUsedCount = 0
}
}
function submitData(value: any) {
selectedPrize.value!.separateCount.countList = value
selectedPrize.value = null
}
async function getImageDbStore() { function changePrizePerson(item: IPrizeConfig) {
const keys = await imageDbStore.keys() let indexPrize = -1
if (keys.length > 0) { for (let i = 0; i < prizeList.value.length; i++) {
imageDbStore.iterate((value, key) => { if (prizeList.value[i].id === item.id) {
imgList.value.push({ indexPrize = i
key, break
value, }
}) }
}) if (indexPrize > -1) {
prizeList.value[indexPrize].separateCount.countList = []
prizeList.value[indexPrize].isUsed ? prizeList.value[indexPrize].isUsedCount = prizeList.value[indexPrize].count : prizeList.value[indexPrize].isUsedCount = 0
}
} }
} function submitData(value: any) {
selectedPrize.value!.separateCount.countList = value
function delItem(item: IPrizeConfig) { selectedPrize.value = null
prizeConfig.deletePrizeConfig(item.id)
toast.success(i18n.global.t('error.deleteSuccess'))
}
function addPrize() {
const defaultPrizeCOnfig: IPrizeConfig = {
id: new Date().getTime().toString(),
name: i18n.global.t('data.prizeName'),
sort: 0,
isAll: false,
count: 1,
isUsedCount: 0,
picture: {
id: '',
name: '',
url: '',
},
separateCount: {
enable: false,
countList: [],
},
desc: '',
isUsed: false,
isShow: true,
frequency: 1,
} }
prizeList.value.push(defaultPrizeCOnfig)
toast.success(i18n.global.t('error.success'))
}
function resetDefault() {
prizeConfig.resetDefault()
prizeList.value = cloneDeep(localPrizeList.value)
toast.success(i18n.global.t('error.success'))
}
async function delAll() {
prizeList.value = []
toast.success(i18n.global.t('error.success'))
}
onMounted(() => {
getImageDbStore()
})
watch(() => prizeList.value, (val: IPrizeConfig[]) => {
prizeConfig.setPrizeConfig(val)
}, { deep: true })
return { async function getImageDbStore() {
addPrize, const keys = await imageDbStore.keys()
resetDefault, if (keys.length > 0) {
delAll, imageDbStore.iterate((value, key) => {
delItem, imgList.value.push({
prizeList, key,
currentPrize, value,
selectedPrize, })
submitData, })
changePrizePerson, }
changePrizeStatus, }
selectPrize,
localImageList, function delItem(item: IPrizeConfig) {
} prizeConfig.deletePrizeConfig(item.id)
toast.success(i18n.global.t('error.deleteSuccess'))
}
function addPrize() {
const defaultPrizeCOnfig: IPrizeConfig = {
id: new Date().getTime().toString(),
name: i18n.global.t('data.prizeName'),
sort: 0,
isAll: false,
count: 1,
isUsedCount: 0,
picture: {
id: '',
name: '',
url: '',
},
separateCount: {
enable: false,
countList: [],
},
desc: '',
isUsed: false,
isShow: true,
frequency: 1,
}
prizeList.value.push(defaultPrizeCOnfig)
toast.success(i18n.global.t('error.success'))
}
function resetDefault() {
prizeConfig.resetDefault()
prizeList.value = cloneDeep(localPrizeList.value)
toast.success(i18n.global.t('error.success'))
}
async function delAll() {
prizeList.value = []
toast.success(i18n.global.t('error.success'))
}
onMounted(() => {
getImageDbStore()
})
watch(() => prizeList.value, (val: IPrizeConfig[]) => {
prizeConfig.setPrizeConfig(val)
}, { deep: true })
return {
addPrize,
resetDefault,
delAll,
delItem,
prizeList,
currentPrize,
selectedPrize,
submitData,
changePrizePerson,
changePrizeStatus,
selectPrize,
localImageList,
}
} }

Some files were not shown because too many files have changed in this diff Show More