Merge branch 'dev' into release

This commit is contained in:
log1997
2026-01-04 15:25:12 +08:00
113 changed files with 2150 additions and 2153 deletions

View File

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

2
src-tauri/Cargo.lock generated
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,38 +1,38 @@
import type { VariantProps } from "class-variance-authority"
import { cva } from "class-variance-authority"
import type { VariantProps } 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(
"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: {
variant: {
default:
"bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
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",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
"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",
"lg": "h-10 rounded-md px-6 has-[>svg]:px-4",
"icon": "size-9",
"icon-sm": "size-8",
"icon-lg": "size-10",
},
'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: {
variant: {
default:
'bg-primary text-primary-foreground hover:bg-primary/90',
destructive:
'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
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',
secondary:
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost:
'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
'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',
'lg': 'h-10 rounded-md px-6 has-[>svg]:px-4',
'icon': 'size-9',
'icon-sm': 'size-8',
'icon-lg': 'size-10',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
)
export type ButtonVariants = VariantProps<typeof buttonVariants>

View File

@@ -1,78 +1,78 @@
<script setup lang="ts">
import type { ListboxRootEmits, ListboxRootProps } from "reka-ui"
import type { HTMLAttributes } from "vue"
import { reactiveOmit } from "@vueuse/core"
import { ListboxRoot, useFilter, useForwardPropsEmits } from "reka-ui"
import { reactive, ref, watch } from "vue"
import { cn } from "@/lib/utils"
import { provideCommandContext } from "."
import type { ListboxRootEmits, ListboxRootProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { ListboxRoot, useFilter, useForwardPropsEmits } from 'reka-ui'
import { reactive, ref, watch } from 'vue'
import { cn } from '@/lib/utils'
import { provideCommandContext } from '.'
const props = withDefaults(defineProps<ListboxRootProps & { class?: HTMLAttributes["class"] }>(), {
modelValue: "",
const props = withDefaults(defineProps<ListboxRootProps & { class?: HTMLAttributes['class'] }>(), {
modelValue: '',
})
const emits = defineEmits<ListboxRootEmits>()
const delegatedProps = reactiveOmit(props, "class")
const delegatedProps = reactiveOmit(props, 'class')
const forwarded = useForwardPropsEmits(delegatedProps, emits)
const allItems = ref<Map<string, 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({
search: "",
filtered: {
search: '',
filtered: {
/** The count of all visible items. */
count: 0,
/** Map from visible item id to its search score. */
items: new Map() as Map<string, number>,
/** Set of groups with at least one visible item. */
groups: new Set() as Set<string>,
},
count: 0,
/** Map from visible item id to its search score. */
items: new Map() as Map<string, number>,
/** Set of groups with at least one visible item. */
groups: new Set() as Set<string>,
},
})
function filterItems() {
if (!filterState.search) {
filterState.filtered.count = allItems.value.size
// Do nothing, each item will know to show itself because search is empty
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
}
if (!filterState.search) {
filterState.filtered.count = allItems.value.size
// Do nothing, each item will know to show itself because search is empty
return
}
}
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, () => {
filterItems()
filterItems()
})
provideCommandContext({
allItems,
allGroups,
filterState,
allItems,
allGroups,
filterState,
})
</script>

View File

@@ -1,15 +1,15 @@
<script setup lang="ts">
import type { DialogRootEmits, DialogRootProps } from "reka-ui"
import { useForwardPropsEmits } from "reka-ui"
import type { DialogRootEmits, DialogRootProps } from 'reka-ui'
import { useForwardPropsEmits } from 'reka-ui'
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 & {
title?: string
description?: string
title?: string
description?: string
}>(), {
title: "Command Palette",
description: "Search for a command to run...",
title: 'Command Palette',
description: 'Search for a command to run...',
})
const emits = defineEmits<DialogRootEmits>()

View File

@@ -1,15 +1,15 @@
<script setup lang="ts">
import type { PrimitiveProps } from "reka-ui"
import type { HTMLAttributes } from "vue"
import { reactiveOmit } from "@vueuse/core"
import { Primitive } from "reka-ui"
import { computed } from "vue"
import { cn } from "@/lib/utils"
import { useCommand } from "."
import type { PrimitiveProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { Primitive } from 'reka-ui'
import { computed } from 'vue'
import { cn } from '@/lib/utils'
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 isRender = computed(() => !!filterState.search && filterState.filtered.count === 0,

View File

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

View File

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

View File

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

View File

@@ -1,13 +1,13 @@
<script setup lang="ts">
import type { ListboxContentProps } from "reka-ui"
import type { HTMLAttributes } from "vue"
import { reactiveOmit } from "@vueuse/core"
import { ListboxContent, useForwardProps } from "reka-ui"
import { cn } from "@/lib/utils"
import type { ListboxContentProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { ListboxContent, useForwardProps } from 'reka-ui'
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)
</script>

View File

@@ -1,13 +1,13 @@
<script setup lang="ts">
import type { SeparatorProps } from "reka-ui"
import type { HTMLAttributes } from "vue"
import { reactiveOmit } from "@vueuse/core"
import { Separator } from "reka-ui"
import { cn } from "@/lib/utils"
import type { SeparatorProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { Separator } from 'reka-ui'
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>
<template>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,13 +1,13 @@
<script setup lang="ts">
import type { DialogDescriptionProps } from "reka-ui"
import type { HTMLAttributes } from "vue"
import { reactiveOmit } from "@vueuse/core"
import { DialogDescription, useForwardProps } from "reka-ui"
import { cn } from "@/lib/utils"
import type { DialogDescriptionProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { DialogDescription, useForwardProps } from 'reka-ui'
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)
</script>

View File

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

View File

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

View File

@@ -1,13 +1,13 @@
<script setup lang="ts">
import type { DialogOverlayProps } from "reka-ui"
import type { HTMLAttributes } from "vue"
import { reactiveOmit } from "@vueuse/core"
import { DialogOverlay } from "reka-ui"
import { cn } from "@/lib/utils"
import type { DialogOverlayProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { DialogOverlay } from 'reka-ui'
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>
<template>

View File

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

View File

@@ -1,13 +1,13 @@
<script setup lang="ts">
import type { DialogTitleProps } from "reka-ui"
import type { HTMLAttributes } from "vue"
import { reactiveOmit } from "@vueuse/core"
import { DialogTitle, useForwardProps } from "reka-ui"
import { cn } from "@/lib/utils"
import type { DialogTitleProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { DialogTitle, useForwardProps } from 'reka-ui'
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)
</script>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,13 +1,13 @@
<script setup lang="ts">
import type { DropdownMenuLabelProps } from "reka-ui"
import type { HTMLAttributes } from "vue"
import { reactiveOmit } from "@vueuse/core"
import { DropdownMenuLabel, useForwardProps } from "reka-ui"
import { cn } from "@/lib/utils"
import type { DropdownMenuLabelProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { DropdownMenuLabel, useForwardProps } from 'reka-ui'
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)
</script>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,17 +1,17 @@
<script setup lang="ts">
import type { DropdownMenuSubTriggerProps } from "reka-ui"
import type { HTMLAttributes } from "vue"
import { reactiveOmit } from "@vueuse/core"
import { ChevronRight } from "lucide-vue-next"
import type { DropdownMenuSubTriggerProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { ChevronRight } from 'lucide-vue-next'
import {
DropdownMenuSubTrigger,
useForwardProps,
} from "reka-ui"
import { cn } from "@/lib/utils"
DropdownMenuSubTrigger,
useForwardProps,
} from 'reka-ui'
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)
</script>

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import type { DropdownMenuTriggerProps } from "reka-ui"
import { DropdownMenuTrigger, useForwardProps } from "reka-ui"
import type { DropdownMenuTriggerProps } from 'reka-ui'
import { DropdownMenuTrigger, useForwardProps } from 'reka-ui'
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 DropdownMenuContent } from "./DropdownMenuContent.vue"
export { default as DropdownMenuGroup } from "./DropdownMenuGroup.vue"
export { default as DropdownMenuItem } from "./DropdownMenuItem.vue"
export { default as DropdownMenuLabel } from "./DropdownMenuLabel.vue"
export { default as DropdownMenuRadioGroup } from "./DropdownMenuRadioGroup.vue"
export { default as DropdownMenuRadioItem } from "./DropdownMenuRadioItem.vue"
export { default as DropdownMenuSeparator } from "./DropdownMenuSeparator.vue"
export { default as DropdownMenuShortcut } from "./DropdownMenuShortcut.vue"
export { default as DropdownMenuSub } from "./DropdownMenuSub.vue"
export { default as DropdownMenuSubContent } from "./DropdownMenuSubContent.vue"
export { default as DropdownMenuSubTrigger } from "./DropdownMenuSubTrigger.vue"
export { default as DropdownMenuTrigger } from "./DropdownMenuTrigger.vue"
export { DropdownMenuPortal } from "reka-ui"
export { default as DropdownMenuCheckboxItem } from './DropdownMenuCheckboxItem.vue'
export { default as DropdownMenuContent } from './DropdownMenuContent.vue'
export { default as DropdownMenuGroup } from './DropdownMenuGroup.vue'
export { default as DropdownMenuItem } from './DropdownMenuItem.vue'
export { default as DropdownMenuLabel } from './DropdownMenuLabel.vue'
export { default as DropdownMenuRadioGroup } from './DropdownMenuRadioGroup.vue'
export { default as DropdownMenuRadioItem } from './DropdownMenuRadioItem.vue'
export { default as DropdownMenuSeparator } from './DropdownMenuSeparator.vue'
export { default as DropdownMenuShortcut } from './DropdownMenuShortcut.vue'
export { default as DropdownMenuSub } from './DropdownMenuSub.vue'
export { default as DropdownMenuSubContent } from './DropdownMenuSubContent.vue'
export { default as DropdownMenuSubTrigger } from './DropdownMenuSubTrigger.vue'
export { default as DropdownMenuTrigger } from './DropdownMenuTrigger.vue'
export { DropdownMenuPortal } from 'reka-ui'

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
<script lang="ts" setup>
import type { ToasterProps } from "vue-sonner"
import { CircleCheckIcon, InfoIcon, Loader2Icon, OctagonXIcon, TriangleAlertIcon, XIcon } from "lucide-vue-next"
import { Toaster as Sonner } from "vue-sonner"
import { cn } from "@/lib/utils"
import type { ToasterProps } from 'vue-sonner'
import { CircleCheckIcon, InfoIcon, Loader2Icon, OctagonXIcon, TriangleAlertIcon, XIcon } from 'lucide-vue-next'
import { Toaster as Sonner } from 'vue-sonner'
import { cn } from '@/lib/utils'
const props = defineProps<ToasterProps>()
</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">
import type { SwitchRootEmits, SwitchRootProps } from "reka-ui"
import type { HTMLAttributes } from "vue"
import { reactiveOmit } from "@vueuse/core"
import type { SwitchRootEmits, SwitchRootProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import {
SwitchRoot,
SwitchThumb,
useForwardPropsEmits,
} from "reka-ui"
import { cn } from "@/lib/utils"
SwitchRoot,
SwitchThumb,
useForwardPropsEmits,
} from 'reka-ui'
import { cn } from '@/lib/utils'
const props = defineProps<SwitchRootProps & { class?: HTMLAttributes["class"] }>()
const props = defineProps<SwitchRootProps & { class?: HTMLAttributes['class'] }>()
const emits = defineEmits<SwitchRootEmits>()
const delegatedProps = reactiveOmit(props, "class")
const delegatedProps = reactiveOmit(props, 'class')
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</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 = {
data: [
{
id: 0,
name: 'Github',
url: 'https://github.com/LOG1997',
icon: 'github',
},
],
data: [
{
id: 0,
name: 'Github',
url: 'https://github.com/LOG1997',
icon: 'github',
},
],
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
import type { ClassValue } from "clsx"
import { clsx } from "clsx"
import { twMerge } from "tailwind-merge"
import type { ClassValue } from 'clsx'
import { clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
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 const languageList = [
{
key: 'zhCn',
name: '中文',
flag: 'zh-cn',
},
{
key: 'en',
name: 'English',
flag: 'en-us',
},
{
key: 'zhCn',
name: '中文',
flag: 'zh-cn',
},
{
key: 'en',
name: 'English',
flag: 'en-us',
},
]
export const browserLanguage = navigator.language.toLowerCase().includes('zh') ? 'zhCn' : 'en'
const globalConfig = JSON.parse(localStorage.getItem('globalConfig') || '{}').globalConfig || {}
// 创建i18n
const i18n = createI18n({
locale: globalConfig.language || browserLanguage,
legacy: false,
messages: {
zhCn,
en,
},
locale: globalConfig.language || browserLanguage,
legacy: false,
messages: {
zhCn,
en,
},
})
export default i18n

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,175 +3,175 @@ import { defineStore } from 'pinia'
import { defaultCurrentPrize, defaultPrizeList } from './data'
export const usePrizeConfig = defineStore('prize', {
state() {
return {
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,
},
}
},
getters: {
state() {
return {
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,
},
}
},
getters: {
// 获取全部配置
getPrizeConfigAll(state) {
return state.prizeConfig
},
// 获取奖品列表
getPrizeConfig(state) {
return state.prizeConfig.prizeList
},
// 根据id获取配置
getPrizeConfigById(state) {
return (id: number | string) => {
return state.prizeConfig.prizeList.find(item => item.id === id)
}
},
// 获取当前奖项
getCurrentPrize(state) {
return state.prizeConfig.currentPrize
},
// 获取临时的奖项
getTemporaryPrize(state) {
return state.prizeConfig.temporaryPrize
},
getPrizeConfigAll(state) {
return state.prizeConfig
},
// 获取奖品列表
getPrizeConfig(state) {
return state.prizeConfig.prizeList
},
// 根据id获取配置
getPrizeConfigById(state) {
return (id: number | string) => {
return state.prizeConfig.prizeList.find(item => item.id === id)
}
},
// 获取当前奖项
getCurrentPrize(state) {
return state.prizeConfig.currentPrize
},
// 获取临时的奖项
getTemporaryPrize(state) {
return state.prizeConfig.temporaryPrize
},
},
actions: {
},
actions: {
// 设置奖项
setPrizeConfig(prizeList: IPrizeConfig[]) {
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: '',
setPrizeConfig(prizeList: IPrizeConfig[]) {
this.prizeConfig.prizeList = prizeList
},
separateCount: {
enable: true,
countList: [],
// 添加奖项
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,
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
},
// 重置所有配置
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,
}
persist: {
enabled: true,
strategies: [
{
// 如果要存储在localStorage中
storage: localStorage,
key: 'prizeConfig',
},
],
},
},
persist: {
enabled: true,
strategies: [
{
// 如果要存储在localStorage中
storage: localStorage,
key: 'prizeConfig',
},
],
},
})

View File

@@ -1,37 +1,37 @@
import { defineStore } from 'pinia'
// import { IPrizeConfig } from '@/types/storeType';
export const useSystem = defineStore('system', {
state() {
return {
isMobile: false,
isChrome: true,
}
},
getters: {
getIsMobile(state) {
return state.isMobile
state() {
return {
isMobile: false,
isChrome: true,
}
},
getIsChrome(state) {
return state.isChrome
getters: {
getIsMobile(state) {
return state.isMobile
},
getIsChrome(state) {
return state.isChrome
},
},
},
actions: {
setIsMobile(isMobile: boolean) {
this.isMobile = isMobile
actions: {
setIsMobile(isMobile: boolean) {
this.isMobile = isMobile
},
setIsChrome(isChrome: boolean) {
this.isChrome = isChrome
},
},
setIsChrome(isChrome: boolean) {
this.isChrome = isChrome
persist: {
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() {
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) {
// 去掉字符串中的空格
color = color.replace(/\s+/g, '');
color = color.replace(/\s+/g, '')
if (isHex(color)) {
return color
}
// 匹配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) {
throw new Error('Invalid color format');
throw new Error('Invalid color format')
}
const r = parseInt(rgbaMatch[1], 10);
const g = parseInt(rgbaMatch[2], 10);
const b = parseInt(rgbaMatch[3], 10);
const a = rgbaMatch[4] !== undefined ? parseFloat(rgbaMatch[4]) : undefined;
const r = Number.parseInt(rgbaMatch[1], 10)
const g = Number.parseInt(rgbaMatch[2], 10)
const b = Number.parseInt(rgbaMatch[3], 10)
const a = rgbaMatch[4] !== undefined ? Number.parseFloat(rgbaMatch[4]) : undefined
// 将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值则将其转换为十六进制并附加到结果中
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) {
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 }> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
const reader = new FileReader()
reader.onload = () => {
// 直接使用原始文件作为 Blob
resolve({ data: file, fileName: file.name });
};
resolve({ data: file, fileName: file.name })
}
reader.onerror = () => {
reject(new Error('文件读取失败'));
};
reject(new Error('文件读取失败'))
}
reader.readAsArrayBuffer(file);
});
reader.readAsArrayBuffer(file)
})
}
export async function readLocalFileAsArraybuffer(path: string): Promise<ArrayBuffer> {

View File

@@ -1,10 +1,10 @@
// 提取有哪些字段
export function extractFields(data: any) {
const item = data[0]
// 排除id x y其他都加入数组
const keys = Object.keys(item).filter(key => key !== 'id' && key !== 'x' && key !== 'y')
if (keys.length > 0) {
const item = data[0]
// 排除id x y其他都加入数组
const keys = Object.keys(item).filter(key => key !== 'id' && key !== 'x' && key !== 'y')
if (keys.length > 0) {
// 返回数组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'
const props = defineProps({
rowCount: {
type: Number,
default: 17,
},
cardColor: {
type: String,
default: '#fff',
},
patternColor: {
type: String,
default: '#000',
},
patternList: {
type: Array,
default: () => [],
},
rowCount: {
type: Number,
default: 17,
},
cardColor: {
type: String,
default: '#fff',
},
patternColor: {
type: String,
default: '#000',
},
patternList: {
type: Array,
default: () => [],
},
})
const data = computed(() => {
return props
return props
})
function updatePatternList(event: Event, item: number) {
if (data.value.patternList.includes(item)) {
const index = data.value.patternList.indexOf(item)
data.value.patternList.splice(index, 1)
}
else {
data.value.patternList.push(item)
}
// emits
if (data.value.patternList.includes(item)) {
const index = data.value.patternList.indexOf(item)
data.value.patternList.splice(index, 1)
}
else {
data.value.patternList.push(item)
}
// emits
}
</script>

View File

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

View File

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

View File

@@ -6,38 +6,38 @@ import { useViewModel } from './useViewModel'
const { t } = useI18n()
const {
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,
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,
} = useViewModel()
</script>

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,235 +7,235 @@ import { themeChange } from '@/utils'
import { clearAllDbStore } from '@/utils/localforage'
export function useViewModel() {
type ValidatePayload = zod.infer<typeof schema>
const globalConfig = useStore().globalConfig
const personConfig = useStore().personConfig
const prizeConfig = useStore().prizeConfig
const {
getGlobalConfig: globalConfigData,
getTopTitle: topTitle,
getTheme: localTheme,
getPatterColor: patternColor,
getPatternList: patternList,
getCardColor: cardColor,
getLuckyColor: luckyCardColor,
getTextColor: textColor,
getCardSize: cardSize,
getTextSize: textSize,
getRowCount: rowCount,
getIsShowPrizeList: isShowPrizeList,
getLanguage: userLanguage,
getBackground: backgroundImage,
getFont: currentFont,
getTitleFont: currentTitleFont,
getTitleFontSyncGlobal: titleFontSyncGlobal,
getImageList: imageList,
getIsShowAvatar: isShowAvatar,
getDefiniteTime: definiteTime,
getWinMusic: isWinMusic,
} = storeToRefs(globalConfig)
const { getAlreadyPersonList: alreadyPersonList, getNotPersonList: notPersonList } = storeToRefs(personConfig)
type ValidatePayload = zod.infer<typeof schema>
const globalConfig = useStore().globalConfig
const personConfig = useStore().personConfig
const prizeConfig = useStore().prizeConfig
const {
getGlobalConfig: globalConfigData,
getTopTitle: topTitle,
getTheme: localTheme,
getPatterColor: patternColor,
getPatternList: patternList,
getCardColor: cardColor,
getLuckyColor: luckyCardColor,
getTextColor: textColor,
getCardSize: cardSize,
getTextSize: textSize,
getRowCount: rowCount,
getIsShowPrizeList: isShowPrizeList,
getLanguage: userLanguage,
getBackground: backgroundImage,
getFont: currentFont,
getTitleFont: currentTitleFont,
getTitleFontSyncGlobal: titleFontSyncGlobal,
getImageList: imageList,
getIsShowAvatar: isShowAvatar,
getDefiniteTime: definiteTime,
getWinMusic: isWinMusic,
} = storeToRefs(globalConfig)
const { getAlreadyPersonList: alreadyPersonList, getNotPersonList: notPersonList } = storeToRefs(personConfig)
const isRowCountChange = ref(0) // 0未改变1改变,2加载中
const themeValue = ref(localTheme.value.name)
const topTitleValue = ref(structuredClone(topTitle.value))
const cardColorValue = ref(structuredClone(cardColor.value))
const luckyCardColorValue = ref(structuredClone(luckyCardColor.value))
const textColorValue = ref(structuredClone(textColor.value))
const cardSizeValue = ref(structuredClone(cardSize.value))
const textSizeValue = ref(structuredClone(textSize.value))
const rowCountValue = ref(structuredClone(rowCount.value))
const languageValue = ref(structuredClone(userLanguage.value))
const isShowPrizeListValue = ref(structuredClone(isShowPrizeList.value))
const isShowAvatarValue = ref(structuredClone(isShowAvatar.value))
const patternColorValue = ref(structuredClone(patternColor.value))
const backgroundImageValue = ref(backgroundImage.value)
const currentFontValue = ref(structuredClone(currentFont.value))
const currentTitleFontValue = ref(structuredClone(currentTitleFont.value))
const titleFontSyncGlobalValue = ref(structuredClone(titleFontSyncGlobal.value))
const definiteTimeValue = ref(structuredClone(definiteTime.value))
const isWinMusicValue = ref(structuredClone(isWinMusic.value))
const formData = ref({
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'),
const isRowCountChange = ref(0) // 0未改变1改变,2加载中
const themeValue = ref(localTheme.value.name)
const topTitleValue = ref(structuredClone(topTitle.value))
const cardColorValue = ref(structuredClone(cardColor.value))
const luckyCardColorValue = ref(structuredClone(luckyCardColor.value))
const textColorValue = ref(structuredClone(textColor.value))
const cardSizeValue = ref(structuredClone(cardSize.value))
const textSizeValue = ref(structuredClone(textSize.value))
const rowCountValue = ref(structuredClone(rowCount.value))
const languageValue = ref(structuredClone(userLanguage.value))
const isShowPrizeListValue = ref(structuredClone(isShowPrizeList.value))
const isShowAvatarValue = ref(structuredClone(isShowAvatar.value))
const patternColorValue = ref(structuredClone(patternColor.value))
const backgroundImageValue = ref(backgroundImage.value)
const currentFontValue = ref(structuredClone(currentFont.value))
const currentTitleFontValue = ref(structuredClone(currentTitleFont.value))
const titleFontSyncGlobalValue = ref(structuredClone(titleFontSyncGlobal.value))
const definiteTimeValue = ref(structuredClone(definiteTime.value))
const isWinMusicValue = ref(structuredClone(isWinMusic.value))
const formData = ref({
rowCount: rowCountValue,
})
.min(1, i18n.global.t('error.minNumber1'))
.max(100, i18n.global.t('error.maxNumber100')),
// 格式化
})
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 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'))
.max(100, i18n.global.t('error.maxNumber100')),
// 格式化
watch(topTitleValue, (val) => {
globalConfig.setTopTitle(val)
})
watch(themeValue, (val: any) => {
globalConfig.setTheme({ name: val })
themeChange(val)
}, { deep: true })
})
const payload: ValidatePayload = {
rowCount: formData.value.rowCount,
}
function parseSchema(props: ValidatePayload) {
return schema.parseAsync(props)
}
watch(cardColorValue, (val: string) => {
globalConfig.setCardColor(val)
}, { deep: true })
watch(luckyCardColorValue, (val: string) => {
globalConfig.setLuckyCardColor(val)
}, { deep: true })
watch(patternColorValue, (val: string) => {
globalConfig.setPatterColor(val)
})
watch(textColorValue, (val: string) => {
globalConfig.setTextColor(val)
}, { deep: true })
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)
watch(cardSizeValue, (val: { width: number, height: number }) => {
globalConfig.setCardSize(val)
}, { deep: true })
isRowCountChange.value = 0
}, 1000)
}
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,
}
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
})
})
watch(topTitleValue, (val) => {
globalConfig.setTopTitle(val)
})
watch(themeValue, (val: any) => {
globalConfig.setTheme({ name: val })
themeChange(val)
}, { deep: true })
watch(cardColorValue, (val: string) => {
globalConfig.setCardColor(val)
}, { deep: true })
watch(luckyCardColorValue, (val: string) => {
globalConfig.setLuckyCardColor(val)
}, { deep: true })
watch(patternColorValue, (val: string) => {
globalConfig.setPatterColor(val)
})
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 imgUploadToast = ref(0) // 0是不显示1是成功2是失败,3是不是图片
const visible = defineModel('visible', {
type: Boolean,
required: true,
type: Boolean,
required: true,
})
const globalConfig = useStore().globalConfig
const imageDbStore = localforage.createInstance({
name: 'imgStore',
name: 'imgStore',
})
const imageData = ref<IFileData | null>(null)
const fileName = computed({
get() {
return imageData.value?.fileName || null
},
set(value) {
if (imageData.value && value) {
imageData.value.fileName = value
}
},
get() {
return imageData.value?.fileName || null
},
set(value) {
if (imageData.value && value) {
imageData.value.fileName = value
}
},
})
const uploadDialogRef = ref()
async function uploadFile(fileData: IFileData | null) {
if (!fileData) {
imageData.value = null
return
}
const isImage = /image*/.test(fileData?.type || '')
if (!isImage) {
imgUploadToast.value = 3
return
}
imageData.value = fileData
if (!fileData) {
imageData.value = null
return
}
const isImage = /image*/.test(fileData?.type || '')
if (!isImage) {
imgUploadToast.value = 3
return
}
imageData.value = fileData
}
async function getImageDbStore() {
const keys = await imageDbStore.keys()
if (keys.length > 0) {
imageDbStore.iterate((value: { fileName: string, data: Blob }, key: string) => {
globalConfig.addImage({
id: key,
name: value.fileName,
url: 'Storage',
})
})
}
const keys = await imageDbStore.keys()
if (keys.length > 0) {
imageDbStore.iterate((value: { fileName: string, data: Blob }, key: string) => {
globalConfig.addImage({
id: key,
name: value.fileName,
url: 'Storage',
})
})
}
}
function submitUpload() {
if (imageData.value) {
const { data, fileName } = imageData.value
const uniqueId = uuidv4()
imageDbStore.setItem(uniqueId, {
data,
fileName,
})
.then(() => {
imgUploadToast.value = 1
getImageDbStore()
})
.catch(() => {
imgUploadToast.value = 2
})
}
if (imageData.value) {
const { data, fileName } = imageData.value
const uniqueId = uuidv4()
imageDbStore.setItem(uniqueId, {
data,
fileName,
})
.then(() => {
imgUploadToast.value = 1
getImageDbStore()
})
.catch(() => {
imgUploadToast.value = 2
})
}
}
watch(visible, (newVal) => {
if (newVal) {
uploadDialogRef.value.showDialog()
}
if (newVal) {
uploadDialogRef.value.showDialog()
}
})
</script>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -14,16 +14,16 @@ const delAllDataDialogRef = ref()
const exportInputFileRef = ref()
const addOnePersonDrawerRef = ref()
const {
resetData,
deleteAll,
handleFileChange,
exportData,
addOnePerson,
singlePersonData,
alreadyPersonList,
allPersonList,
tableColumnList,
downloadTemplate,
resetData,
deleteAll,
handleFileChange,
exportData,
addOnePerson,
singlePersonData,
alreadyPersonList,
allPersonList,
tableColumnList,
downloadTemplate,
} = useViewModel({ exportInputFileRef })
const { t } = useI18n()
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'>
export function useViewModel({ exportInputFileRef }: { exportInputFileRef: Ref<HTMLInputElement> }) {
const { t } = useI18n()
const baseUrl = import.meta.env.BASE_URL
const toast = useToast()
const worker: Worker | null = new ImportExcelWorker()
const loading = inject(loadingKey)
const personConfig = useStore().personConfig
const { getAllPersonList: allPersonList, getAlreadyPersonList: alreadyPersonList } = storeToRefs(personConfig)
const tableColumnList = tableColumns({ handleDeletePerson: delPersonItem })
const addPersonModalVisible = ref(false)
const singlePersonData = ref<IBasePersonConfig>({
uid: '',
name: '',
department: '',
avatar: '',
identity: '',
})
async function getExcelTemplateContent() {
const locale = i18n.global.locale.value
if (locale === 'zhCn') {
const templateData = await readLocalFileAsArraybuffer(`${import.meta.env.BASE_URL}人口登记表-zhCn.xlsx`)
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()
const { t } = useI18n()
const baseUrl = import.meta.env.BASE_URL.replace('./', '/')
const toast = useToast()
const worker: Worker | null = new ImportExcelWorker()
const loading = inject(loadingKey)
const personConfig = useStore().personConfig
const { getAllPersonList: allPersonList, getAlreadyPersonList: alreadyPersonList } = storeToRefs(personConfig)
const tableColumnList = tableColumns({ handleDeletePerson: delPersonItem })
const addPersonModalVisible = ref(false)
const singlePersonData = ref<IBasePersonConfig>({
uid: '',
name: '',
department: '',
avatar: '',
identity: '',
})
async function getExcelTemplateContent() {
const locale = i18n.global.locale.value
if (locale === 'zhCn') {
const templateData = await readLocalFileAsArraybuffer(`${baseUrl}人口登记表-zhCn.xlsx`)
return templateData
}
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 || '导入错误')
else {
const templateData = await readLocalFileAsArraybuffer(`${baseUrl}personListTemplate-en.xlsx`)
return templateData
}
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 = ''
/// 向worker发送消息
function sendWorkerMessage(message: any) {
if (worker) {
worker.postMessage(message)
}
}
}
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 fileUrl = `${baseUrl}${templateFileName}`
fetch(fileUrl)
.then(res => res.blob())
.then((blob) => {
const url = window.URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = templateFileName
a.click()
toast.open({
message: t('error.downloadSuccess'),
type: 'success',
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(',')
const templateFileName = i18n.global.t('data.xlsxName')
const fileUrl = `${baseUrl}${templateFileName}`
fetch(fileUrl)
.then(res => res.blob())
.then((blob) => {
const url = window.URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = templateFileName
a.click()
toast.open({
message: t('error.downloadSuccess'),
type: 'success',
position: 'top-right',
})
})
}
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'))
// 导出数据
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
.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) {
const dataBinary = XLSX.utils.json_to_sheet(data)
const dataBinaryBinary = XLSX.utils.book_new()
XLSX.utils.book_append_sheet(dataBinaryBinary, dataBinary, 'Sheet1')
XLSX.writeFile(dataBinaryBinary, 'data.xlsx')
if (data.length > 0) {
const dataBinary = XLSX.utils.json_to_sheet(data)
const dataBinaryBinary = XLSX.utils.book_new()
XLSX.utils.book_append_sheet(dataBinaryBinary, dataBinary, 'Sheet1')
XLSX.writeFile(dataBinaryBinary, 'data.xlsx')
}
}
}
function resetData() {
personConfig.resetAlreadyPerson()
}
function resetData() {
personConfig.resetAlreadyPerson()
}
function deleteAll() {
personConfig.deleteAllPerson()
}
function deleteAll() {
personConfig.deleteAllPerson()
}
function delPersonItem(row: IPersonConfig) {
personConfig.deletePerson(row)
}
function addOnePerson(addOnePersonDrawerRef: any, event: any) {
event.preventDefault()
// 表单中的验证信息清除
function delPersonItem(row: IPersonConfig) {
personConfig.deletePerson(row)
}
function addOnePerson(addOnePersonDrawerRef: any, event: any) {
event.preventDefault()
// 表单中的验证信息清除
const personData = addOtherInfo([toRaw(singlePersonData.value)])
personData[0].id = uuidv4()
personConfig.addOnePerson(personData)
// singlePersonData.value = {} as IBasePersonConfig
addOnePersonDrawerRef.closeDrawer()
singlePersonData.value = {} as IBasePersonConfig
}
return {
resetData,
deleteAll,
handleFileChange,
exportData,
alreadyPersonList,
allPersonList,
tableColumnList,
addOnePerson,
addPersonModalVisible,
singlePersonData,
downloadTemplate,
}
const personData = addOtherInfo([toRaw(singlePersonData.value)])
personData[0].id = uuidv4()
personConfig.addOnePerson(personData)
// singlePersonData.value = {} as IBasePersonConfig
addOnePersonDrawerRef.closeDrawer()
singlePersonData.value = {} as IBasePersonConfig
}
return {
resetData,
deleteAll,
handleFileChange,
exportData,
alreadyPersonList,
allPersonList,
tableColumnList,
addOnePerson,
addPersonModalVisible,
singlePersonData,
downloadTemplate,
}
}

View File

@@ -8,133 +8,133 @@ import i18n from '@/locales/i18n'
import useStore from '@/store'
export function usePrizeConfig() {
const toast = useToast()
const imageDbStore = localforage.createInstance({
name: 'imgStore',
})
const prizeConfig = useStore().prizeConfig
const globalConfig = useStore().globalConfig
const { getPrizeConfig: localPrizeList, getCurrentPrize: currentPrize } = storeToRefs(prizeConfig)
const toast = useToast()
const imageDbStore = localforage.createInstance({
name: 'imgStore',
})
const prizeConfig = useStore().prizeConfig
const globalConfig = useStore().globalConfig
const { getPrizeConfig: localPrizeList, getCurrentPrize: currentPrize } = storeToRefs(prizeConfig)
const { getImageList: localImageList } = storeToRefs(globalConfig)
const imgList = ref<any[]>([])
const { getImageList: localImageList } = storeToRefs(globalConfig)
const imgList = ref<any[]>([])
const prizeList = ref(cloneDeep(localPrizeList.value))
const selectedPrize = ref<IPrizeConfig | null>()
const prizeList = ref(cloneDeep(localPrizeList.value))
const selectedPrize = ref<IPrizeConfig | null>()
function selectPrize(item: IPrizeConfig) {
selectedPrize.value = item
selectedPrize.value.isUsedCount = 0
selectedPrize.value.isUsed = false
function selectPrize(item: IPrizeConfig) {
selectedPrize.value = item
selectedPrize.value.isUsedCount = 0
selectedPrize.value.isUsed = false
if (selectedPrize.value.separateCount.countList.length > 1) {
return
if (selectedPrize.value.separateCount.countList.length > 1) {
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) {
item.isUsed ? item.isUsedCount = 0 : item.isUsedCount = item.count
item.separateCount.countList = []
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
}
function changePrizeStatus(item: IPrizeConfig) {
item.isUsed ? item.isUsedCount = 0 : item.isUsedCount = item.count
item.separateCount.countList = []
item.isUsed = !item.isUsed
}
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() {
const keys = await imageDbStore.keys()
if (keys.length > 0) {
imageDbStore.iterate((value, key) => {
imgList.value.push({
key,
value,
})
})
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 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,
function submitData(value: any) {
selectedPrize.value!.separateCount.countList = value
selectedPrize.value = null
}
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,
}
async function getImageDbStore() {
const keys = await imageDbStore.keys()
if (keys.length > 0) {
imageDbStore.iterate((value, key) => {
imgList.value.push({
key,
value,
})
})
}
}
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