Merge pull request #97 from LOG1997/91-optimize-algorithm
91 optimize algorithm
This commit is contained in:
@@ -1,41 +0,0 @@
|
|||||||
import Button from '@/components/Button/index.vue'
|
|
||||||
|
|
||||||
import { shallowMount } from '@vue/test-utils'
|
|
||||||
import { describe, expect, test } from 'vitest'
|
|
||||||
// 测试分组
|
|
||||||
describe('Button', () => {
|
|
||||||
// mount
|
|
||||||
test('Buttons slot text', () => {
|
|
||||||
// @vue/test-utils
|
|
||||||
const wrapper = shallowMount(Button, {
|
|
||||||
slots: {
|
|
||||||
default: 'Button',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
// 断言
|
|
||||||
expect(wrapper.text()).toBe('Button')
|
|
||||||
})
|
|
||||||
test('Button click', () => {
|
|
||||||
const wrapper = shallowMount(Button)
|
|
||||||
wrapper.trigger('click')
|
|
||||||
expect(wrapper.emitted('click')).toBeTruthy()
|
|
||||||
})
|
|
||||||
test('Button disabled', () => {
|
|
||||||
const wrapper = shallowMount(Button, {
|
|
||||||
props: {
|
|
||||||
disabled: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
wrapper.trigger('click')
|
|
||||||
expect(wrapper.emitted('click')).toBeFalsy()
|
|
||||||
})
|
|
||||||
test('Button not disabled', () => {
|
|
||||||
const wrapper = shallowMount(Button, {
|
|
||||||
props: {
|
|
||||||
disabled: false,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
wrapper.trigger('click')
|
|
||||||
expect(wrapper.emitted('click')).toBeTruthy()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
import Button from '@/components/Button/index.vue'
|
|
||||||
|
|
||||||
import { shallowMount } from '@vue/test-utils'
|
|
||||||
import { describe, expect, test } from 'vitest'
|
|
||||||
// 测试分组
|
|
||||||
describe('Button', () => {
|
|
||||||
// mount
|
|
||||||
test('Buttons slot text', () => {
|
|
||||||
// @vue/test-utils
|
|
||||||
const wrapper = shallowMount(Button, {
|
|
||||||
slots: {
|
|
||||||
default: 'Button',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
// 断言
|
|
||||||
expect(wrapper.text()).toBe('Button')
|
|
||||||
})
|
|
||||||
test('Button click', () => {
|
|
||||||
const wrapper = shallowMount(Button)
|
|
||||||
wrapper.trigger('click')
|
|
||||||
expect(wrapper.emitted('click')).toBeTruthy()
|
|
||||||
})
|
|
||||||
test('Button disabled', () => {
|
|
||||||
const wrapper = shallowMount(Button, {
|
|
||||||
props: {
|
|
||||||
disabled: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
wrapper.trigger('click')
|
|
||||||
expect(wrapper.emitted('click')).toBeFalsy()
|
|
||||||
})
|
|
||||||
test('Button not disabled', () => {
|
|
||||||
const wrapper = shallowMount(Button, {
|
|
||||||
props: {
|
|
||||||
disabled: false,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
wrapper.trigger('click')
|
|
||||||
expect(wrapper.emitted('click')).toBeTruthy()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
124
__test__/Random.test.ts
Normal file
124
__test__/Random.test.ts
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
import { getRandomElements } from '@/views/Home/utils/random'
|
||||||
|
|
||||||
|
describe('getRandomElements', () => {
|
||||||
|
// 测试基本功能:从数组中获取指定数量的元素
|
||||||
|
it('should return specified number of elements', () => {
|
||||||
|
const sourceArray = [1, 2, 3, 4, 5]
|
||||||
|
const result = getRandomElements(sourceArray, 3)
|
||||||
|
|
||||||
|
expect(result).toHaveLength(3)
|
||||||
|
result.forEach((element) => {
|
||||||
|
expect(sourceArray).toContain(element)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 测试边界情况:count为0
|
||||||
|
it('should return empty array when count is 0', () => {
|
||||||
|
const sourceArray = [1, 2, 3]
|
||||||
|
const result = getRandomElements(sourceArray, 0)
|
||||||
|
|
||||||
|
expect(result).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
// 测试边界情况:count为负数
|
||||||
|
it('should return empty array when count is negative', () => {
|
||||||
|
const sourceArray = [1, 2, 3]
|
||||||
|
const result = getRandomElements(sourceArray, -1)
|
||||||
|
|
||||||
|
expect(result).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
// 测试边界情况:count大于等于数组长度
|
||||||
|
it('should return shuffled array when count equals or exceeds array length', () => {
|
||||||
|
const sourceArray = [1, 2, 3]
|
||||||
|
const result1 = getRandomElements(sourceArray, 3)
|
||||||
|
const result2 = getRandomElements(sourceArray, 5)
|
||||||
|
|
||||||
|
expect(result1).toHaveLength(3)
|
||||||
|
expect(result2).toHaveLength(3)
|
||||||
|
|
||||||
|
// 验证返回的元素与原数组相同
|
||||||
|
expect(result1.sort()).toEqual(sourceArray.sort())
|
||||||
|
expect(result2.sort()).toEqual(sourceArray.sort())
|
||||||
|
})
|
||||||
|
|
||||||
|
// 测试空数组情况
|
||||||
|
it('should return empty array when source array is empty', () => {
|
||||||
|
const sourceArray: number[] = []
|
||||||
|
const result = getRandomElements(sourceArray, 3)
|
||||||
|
|
||||||
|
expect(result).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
// 测试单元素数组
|
||||||
|
it('should handle single element array', () => {
|
||||||
|
const sourceArray = [42]
|
||||||
|
const result = getRandomElements(sourceArray, 1)
|
||||||
|
|
||||||
|
expect(result).toEqual([42])
|
||||||
|
})
|
||||||
|
|
||||||
|
// 测试字符串数组
|
||||||
|
it('should work with string arrays', () => {
|
||||||
|
const sourceArray = ['a', 'b', 'c', 'd', 'e']
|
||||||
|
const result = getRandomElements(sourceArray, 2)
|
||||||
|
|
||||||
|
expect(result).toHaveLength(2)
|
||||||
|
result.forEach((element) => {
|
||||||
|
expect(sourceArray).toContain(element)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 测试对象数组
|
||||||
|
it('should work with object arrays', () => {
|
||||||
|
const sourceArray = [
|
||||||
|
{ id: 1, name: 'Alice' },
|
||||||
|
{ id: 2, name: 'Bob' },
|
||||||
|
{ id: 3, name: 'Charlie' },
|
||||||
|
]
|
||||||
|
const result = getRandomElements(sourceArray, 2)
|
||||||
|
|
||||||
|
expect(result).toHaveLength(2)
|
||||||
|
result.forEach((element) => {
|
||||||
|
expect(sourceArray).toContain(element)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 测试多次调用应产生不同结果(概率性测试)
|
||||||
|
it('should produce different results on multiple calls', () => {
|
||||||
|
const sourceArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
|
||||||
|
const results = new Set()
|
||||||
|
|
||||||
|
// 多次调用并收集结果
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
const result = getRandomElements(sourceArray, 5).sort().join(',')
|
||||||
|
results.add(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 虽然有极小概率会相同,但大多数情况下应该有不同的结果
|
||||||
|
expect(results.size).toBeGreaterThan(1)
|
||||||
|
})
|
||||||
|
// 多次调用,每个元素抽中的概率基本上相等
|
||||||
|
it('should have approximately equal probabilities for each element', () => {
|
||||||
|
const sourceArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
|
||||||
|
const times = 200000 // 次数
|
||||||
|
const count = 5 // 抽奖个数
|
||||||
|
const expectedProbability = count / sourceArray.length
|
||||||
|
const elementCounts = new Map()
|
||||||
|
|
||||||
|
// 多次调用并统计元素出现的次数
|
||||||
|
for (let i = 0; i < times; i++) {
|
||||||
|
const result = getRandomElements(sourceArray, count)
|
||||||
|
result.forEach((element) => {
|
||||||
|
const count = elementCounts.get(element) || 0
|
||||||
|
elementCounts.set(element, count + 1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
elementCounts.forEach((value) => {
|
||||||
|
// 验证每个元素出现的概率接近相等
|
||||||
|
const probability = value / times
|
||||||
|
expect(probability).toBeCloseTo(expectedProbability, 2)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
// test axios request
|
|
||||||
import Request from '@/api/request';
|
|
||||||
import { describe, it, expect, vi } from 'vitest';
|
|
||||||
import { mount, flushPromises } from '@vue/test-utils';
|
|
||||||
import axios from 'axios';
|
|
||||||
|
|
||||||
const fn = vi.fn();
|
|
||||||
const mockRes = {
|
|
||||||
data: {
|
|
||||||
code: 200,
|
|
||||||
success: true,
|
|
||||||
message: 'success',
|
|
||||||
data: {
|
|
||||||
name: 'test',
|
|
||||||
age: 18,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
fn(mockRes);
|
|
||||||
fn.mock.calls[0] === [mockRes];
|
|
||||||
|
|
||||||
describe('Request', () => {
|
|
||||||
it('should return data when request success', async () => {
|
|
||||||
const request = new Request();
|
|
||||||
const res = await request({
|
|
||||||
url: '/test',
|
|
||||||
method: 'GET',
|
|
||||||
});
|
|
||||||
expect(res).toEqual(mockRes.data);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
25
pnpm-lock.yaml
generated
25
pnpm-lock.yaml
generated
@@ -1392,36 +1392,42 @@ packages:
|
|||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@parcel/watcher-linux-arm-musl@2.5.0':
|
'@parcel/watcher-linux-arm-musl@2.5.0':
|
||||||
resolution: {integrity: sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==}
|
resolution: {integrity: sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@parcel/watcher-linux-arm64-glibc@2.5.0':
|
'@parcel/watcher-linux-arm64-glibc@2.5.0':
|
||||||
resolution: {integrity: sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==}
|
resolution: {integrity: sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@parcel/watcher-linux-arm64-musl@2.5.0':
|
'@parcel/watcher-linux-arm64-musl@2.5.0':
|
||||||
resolution: {integrity: sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==}
|
resolution: {integrity: sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@parcel/watcher-linux-x64-glibc@2.5.0':
|
'@parcel/watcher-linux-x64-glibc@2.5.0':
|
||||||
resolution: {integrity: sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==}
|
resolution: {integrity: sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@parcel/watcher-linux-x64-musl@2.5.0':
|
'@parcel/watcher-linux-x64-musl@2.5.0':
|
||||||
resolution: {integrity: sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==}
|
resolution: {integrity: sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@parcel/watcher-win32-arm64@2.5.0':
|
'@parcel/watcher-win32-arm64@2.5.0':
|
||||||
resolution: {integrity: sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==}
|
resolution: {integrity: sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==}
|
||||||
@@ -1492,56 +1498,67 @@ packages:
|
|||||||
resolution: {integrity: sha512-aL6hRwu0k7MTUESgkg7QHY6CoqPgr6gdQXRJI1/VbFlUMwsSzPGSR7sG5d+MCbYnJmJwThc2ol3nixj1fvI/zQ==}
|
resolution: {integrity: sha512-aL6hRwu0k7MTUESgkg7QHY6CoqPgr6gdQXRJI1/VbFlUMwsSzPGSR7sG5d+MCbYnJmJwThc2ol3nixj1fvI/zQ==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm-musleabihf@4.52.0':
|
'@rollup/rollup-linux-arm-musleabihf@4.52.0':
|
||||||
resolution: {integrity: sha512-BTs0M5s1EJejgIBJhCeiFo7GZZ2IXWkFGcyZhxX4+8usnIo5Mti57108vjXFIQmmJaRyDwmV59Tw64Ap1dkwMw==}
|
resolution: {integrity: sha512-BTs0M5s1EJejgIBJhCeiFo7GZZ2IXWkFGcyZhxX4+8usnIo5Mti57108vjXFIQmmJaRyDwmV59Tw64Ap1dkwMw==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm64-gnu@4.52.0':
|
'@rollup/rollup-linux-arm64-gnu@4.52.0':
|
||||||
resolution: {integrity: sha512-uj672IVOU9m08DBGvoPKPi/J8jlVgjh12C9GmjjBxCTQc3XtVmRkRKyeHSmIKQpvJ7fIm1EJieBUcnGSzDVFyw==}
|
resolution: {integrity: sha512-uj672IVOU9m08DBGvoPKPi/J8jlVgjh12C9GmjjBxCTQc3XtVmRkRKyeHSmIKQpvJ7fIm1EJieBUcnGSzDVFyw==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm64-musl@4.52.0':
|
'@rollup/rollup-linux-arm64-musl@4.52.0':
|
||||||
resolution: {integrity: sha512-/+IVbeDMDCtB/HP/wiWsSzduD10SEGzIZX2945KSgZRNi4TSkjHqRJtNTVtVb8IRwhJ65ssI56krlLik+zFWkw==}
|
resolution: {integrity: sha512-/+IVbeDMDCtB/HP/wiWsSzduD10SEGzIZX2945KSgZRNi4TSkjHqRJtNTVtVb8IRwhJ65ssI56krlLik+zFWkw==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rollup/rollup-linux-loong64-gnu@4.52.0':
|
'@rollup/rollup-linux-loong64-gnu@4.52.0':
|
||||||
resolution: {integrity: sha512-U1vVzvSWtSMWKKrGoROPBXMh3Vwn93TA9V35PldokHGqiUbF6erSzox/5qrSMKp6SzakvyjcPiVF8yB1xKr9Pg==}
|
resolution: {integrity: sha512-U1vVzvSWtSMWKKrGoROPBXMh3Vwn93TA9V35PldokHGqiUbF6erSzox/5qrSMKp6SzakvyjcPiVF8yB1xKr9Pg==}
|
||||||
cpu: [loong64]
|
cpu: [loong64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-ppc64-gnu@4.52.0':
|
'@rollup/rollup-linux-ppc64-gnu@4.52.0':
|
||||||
resolution: {integrity: sha512-X/4WfuBAdQRH8cK3DYl8zC00XEE6aM472W+QCycpQJeLWVnHfkv7RyBFVaTqNUMsTgIX8ihMjCvFF9OUgeABzw==}
|
resolution: {integrity: sha512-X/4WfuBAdQRH8cK3DYl8zC00XEE6aM472W+QCycpQJeLWVnHfkv7RyBFVaTqNUMsTgIX8ihMjCvFF9OUgeABzw==}
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-riscv64-gnu@4.52.0':
|
'@rollup/rollup-linux-riscv64-gnu@4.52.0':
|
||||||
resolution: {integrity: sha512-xIRYc58HfWDBZoLmWfWXg2Sq8VCa2iJ32B7mqfWnkx5mekekl0tMe7FHpY8I72RXEcUkaWawRvl3qA55og+cwQ==}
|
resolution: {integrity: sha512-xIRYc58HfWDBZoLmWfWXg2Sq8VCa2iJ32B7mqfWnkx5mekekl0tMe7FHpY8I72RXEcUkaWawRvl3qA55og+cwQ==}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-riscv64-musl@4.52.0':
|
'@rollup/rollup-linux-riscv64-musl@4.52.0':
|
||||||
resolution: {integrity: sha512-mbsoUey05WJIOz8U1WzNdf+6UMYGwE3fZZnQqsM22FZ3wh1N887HT6jAOjXs6CNEK3Ntu2OBsyQDXfIjouI4dw==}
|
resolution: {integrity: sha512-mbsoUey05WJIOz8U1WzNdf+6UMYGwE3fZZnQqsM22FZ3wh1N887HT6jAOjXs6CNEK3Ntu2OBsyQDXfIjouI4dw==}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rollup/rollup-linux-s390x-gnu@4.52.0':
|
'@rollup/rollup-linux-s390x-gnu@4.52.0':
|
||||||
resolution: {integrity: sha512-qP6aP970bucEi5KKKR4AuPFd8aTx9EF6BvutvYxmZuWLJHmnq4LvBfp0U+yFDMGwJ+AIJEH5sIP+SNypauMWzg==}
|
resolution: {integrity: sha512-qP6aP970bucEi5KKKR4AuPFd8aTx9EF6BvutvYxmZuWLJHmnq4LvBfp0U+yFDMGwJ+AIJEH5sIP+SNypauMWzg==}
|
||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-x64-gnu@4.52.0':
|
'@rollup/rollup-linux-x64-gnu@4.52.0':
|
||||||
resolution: {integrity: sha512-nmSVN+F2i1yKZ7rJNKO3G7ZzmxJgoQBQZ/6c4MuS553Grmr7WqR7LLDcYG53Z2m9409z3JLt4sCOhLdbKQ3HmA==}
|
resolution: {integrity: sha512-nmSVN+F2i1yKZ7rJNKO3G7ZzmxJgoQBQZ/6c4MuS553Grmr7WqR7LLDcYG53Z2m9409z3JLt4sCOhLdbKQ3HmA==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-x64-musl@4.52.0':
|
'@rollup/rollup-linux-x64-musl@4.52.0':
|
||||||
resolution: {integrity: sha512-2d0qRo33G6TfQVjaMR71P+yJVGODrt5V6+T0BDYH4EMfGgdC/2HWDVjSSFw888GSzAZUwuska3+zxNUCDco6rQ==}
|
resolution: {integrity: sha512-2d0qRo33G6TfQVjaMR71P+yJVGODrt5V6+T0BDYH4EMfGgdC/2HWDVjSSFw888GSzAZUwuska3+zxNUCDco6rQ==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rollup/rollup-openharmony-arm64@4.52.0':
|
'@rollup/rollup-openharmony-arm64@4.52.0':
|
||||||
resolution: {integrity: sha512-A1JalX4MOaFAAyGgpO7XP5khquv/7xKzLIyLmhNrbiCxWpMlnsTYr8dnsWM7sEeotNmxvSOEL7F65j0HXFcFsw==}
|
resolution: {integrity: sha512-A1JalX4MOaFAAyGgpO7XP5khquv/7xKzLIyLmhNrbiCxWpMlnsTYr8dnsWM7sEeotNmxvSOEL7F65j0HXFcFsw==}
|
||||||
@@ -1623,24 +1640,28 @@ packages:
|
|||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@tailwindcss/oxide-linux-arm64-musl@4.1.13':
|
'@tailwindcss/oxide-linux-arm64-musl@4.1.13':
|
||||||
resolution: {integrity: sha512-hZQrmtLdhyqzXHB7mkXfq0IYbxegaqTmfa1p9MBj72WPoDD3oNOh1Lnxf6xZLY9C3OV6qiCYkO1i/LrzEdW2mg==}
|
resolution: {integrity: sha512-hZQrmtLdhyqzXHB7mkXfq0IYbxegaqTmfa1p9MBj72WPoDD3oNOh1Lnxf6xZLY9C3OV6qiCYkO1i/LrzEdW2mg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@tailwindcss/oxide-linux-x64-gnu@4.1.13':
|
'@tailwindcss/oxide-linux-x64-gnu@4.1.13':
|
||||||
resolution: {integrity: sha512-uaZTYWxSXyMWDJZNY1Ul7XkJTCBRFZ5Fo6wtjrgBKzZLoJNrG+WderJwAjPzuNZOnmdrVg260DKwXCFtJ/hWRQ==}
|
resolution: {integrity: sha512-uaZTYWxSXyMWDJZNY1Ul7XkJTCBRFZ5Fo6wtjrgBKzZLoJNrG+WderJwAjPzuNZOnmdrVg260DKwXCFtJ/hWRQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@tailwindcss/oxide-linux-x64-musl@4.1.13':
|
'@tailwindcss/oxide-linux-x64-musl@4.1.13':
|
||||||
resolution: {integrity: sha512-oXiPj5mi4Hdn50v5RdnuuIms0PVPI/EG4fxAfFiIKQh5TgQgX7oSuDWntHW7WNIi/yVLAiS+CRGW4RkoGSSgVQ==}
|
resolution: {integrity: sha512-oXiPj5mi4Hdn50v5RdnuuIms0PVPI/EG4fxAfFiIKQh5TgQgX7oSuDWntHW7WNIi/yVLAiS+CRGW4RkoGSSgVQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@tailwindcss/oxide-wasm32-wasi@4.1.13':
|
'@tailwindcss/oxide-wasm32-wasi@4.1.13':
|
||||||
resolution: {integrity: sha512-+LC2nNtPovtrDwBc/nqnIKYh/W2+R69FA0hgoeOn64BdCX522u19ryLh3Vf3F8W49XBcMIxSe665kwy21FkhvA==}
|
resolution: {integrity: sha512-+LC2nNtPovtrDwBc/nqnIKYh/W2+R69FA0hgoeOn64BdCX522u19ryLh3Vf3F8W49XBcMIxSe665kwy21FkhvA==}
|
||||||
@@ -3854,24 +3875,28 @@ packages:
|
|||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
lightningcss-linux-arm64-musl@1.30.1:
|
lightningcss-linux-arm64-musl@1.30.1:
|
||||||
resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==}
|
resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
lightningcss-linux-x64-gnu@1.30.1:
|
lightningcss-linux-x64-gnu@1.30.1:
|
||||||
resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==}
|
resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
lightningcss-linux-x64-musl@1.30.1:
|
lightningcss-linux-x64-musl@1.30.1:
|
||||||
resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==}
|
resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
lightningcss-win32-arm64-msvc@1.30.1:
|
lightningcss-win32-arm64-msvc@1.30.1:
|
||||||
resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==}
|
resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==}
|
||||||
|
|||||||
@@ -1,53 +1,54 @@
|
|||||||
export interface IPersonConfig {
|
export interface IPersonConfig {
|
||||||
id: number;
|
id: number
|
||||||
uid: string;
|
uid: string
|
||||||
name: string;
|
uuid: string
|
||||||
department: string;
|
name: string
|
||||||
identity: string;
|
department: string
|
||||||
avatar: string;
|
identity: string
|
||||||
isWin: boolean;
|
avatar: string
|
||||||
x: number;
|
isWin: boolean
|
||||||
|
x: number
|
||||||
y: number
|
y: number
|
||||||
createTime: string;
|
createTime: string
|
||||||
updateTime: string;
|
updateTime: string
|
||||||
prizeName: string[];
|
prizeName: string[]
|
||||||
prizeId: string[];
|
prizeId: string[]
|
||||||
prizeTime: string[];
|
prizeTime: string[]
|
||||||
}
|
}
|
||||||
export interface Separate {
|
export interface Separate {
|
||||||
id: string
|
id: string
|
||||||
count: number
|
count: number
|
||||||
isUsedCount: number
|
isUsedCount: number
|
||||||
}
|
}
|
||||||
export interface IPrizeConfig {
|
export interface IPrizeConfig {
|
||||||
id: number | string
|
id: number | string
|
||||||
name: string
|
|
||||||
sort: number
|
|
||||||
isAll: boolean
|
|
||||||
count: number
|
|
||||||
isUsedCount: number
|
|
||||||
picture: {
|
|
||||||
id: string | number
|
|
||||||
name: string
|
name: string
|
||||||
url: string
|
sort: number
|
||||||
}
|
isAll: boolean
|
||||||
separateCount: {
|
count: number
|
||||||
enable: boolean
|
isUsedCount: number
|
||||||
countList: Separate[]
|
picture: {
|
||||||
}
|
id: string | number
|
||||||
desc: string
|
name: string
|
||||||
isShow: boolean
|
url: string
|
||||||
isUsed: boolean
|
}
|
||||||
frequency: number
|
separateCount: {
|
||||||
|
enable: boolean
|
||||||
|
countList: Separate[]
|
||||||
|
}
|
||||||
|
desc: string
|
||||||
|
isShow: boolean
|
||||||
|
isUsed: boolean
|
||||||
|
frequency: number
|
||||||
}
|
}
|
||||||
export interface IMusic {
|
export interface IMusic {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
url: string
|
url: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IImage {
|
export interface IImage {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
url: string
|
url: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
/**
|
/**
|
||||||
* @description: 处理表格数据,添加x,y,id等信息
|
* @description: 处理表格数据,添加x,y,id等信息
|
||||||
* @param tableData 表格数据
|
* @param tableData 表格数据
|
||||||
@@ -32,6 +32,7 @@ export function addOtherInfo(personList: any[]) {
|
|||||||
personList[i].prizeTime = [] as string[]
|
personList[i].prizeTime = [] as string[]
|
||||||
personList[i].prizeId = []
|
personList[i].prizeId = []
|
||||||
personList[i].isWin = false
|
personList[i].isWin = false
|
||||||
|
personList[i].uuid = uuidv4()
|
||||||
}
|
}
|
||||||
|
|
||||||
return personList
|
return personList
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ interface Props {
|
|||||||
topTitle: string
|
topTitle: string
|
||||||
tableData: any[]
|
tableData: any[]
|
||||||
setDefaultPersonList: () => void
|
setDefaultPersonList: () => void
|
||||||
|
isInitialDone: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<Props>()
|
const props = defineProps<Props>()
|
||||||
@@ -26,7 +27,7 @@ const { t } = useI18n()
|
|||||||
>
|
>
|
||||||
{{ topTitle }}
|
{{ topTitle }}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="flex gap-3">
|
<div v-if="isInitialDone" class="flex gap-3">
|
||||||
<button
|
<button
|
||||||
v-if="tableData.length <= 0" class="cursor-pointer btn btn-outline btn-secondary btn-lg"
|
v-if="tableData.length <= 0" class="cursor-pointer btn btn-outline btn-secondary btn-lg"
|
||||||
@click="router.push('config')"
|
@click="router.push('config')"
|
||||||
@@ -40,6 +41,11 @@ const { t } = useI18n()
|
|||||||
{{ t('button.useDefault') }}
|
{{ t('button.useDefault') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 加载中 -->
|
||||||
|
<div v-else class="flex gap-3 items-center">
|
||||||
|
<span class="loading loading-spinner loading-xl" />
|
||||||
|
<span>加载中</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { useViewModel } from './useViewModel'
|
|||||||
import 'vue-toast-notification/dist/theme-sugar.css'
|
import 'vue-toast-notification/dist/theme-sugar.css'
|
||||||
|
|
||||||
const viewModel = useViewModel()
|
const viewModel = useViewModel()
|
||||||
const { setDefaultPersonList, tableData, currentStatus, enterLottery, stopLottery, containerRef, startLottery, continueLottery, quitLottery } = viewModel
|
const { setDefaultPersonList, tableData, currentStatus, enterLottery, stopLottery, containerRef, startLottery, continueLottery, quitLottery, isInitialDone } = viewModel
|
||||||
const globalConfig = useStore().globalConfig
|
const globalConfig = useStore().globalConfig
|
||||||
|
|
||||||
const { getTopTitle: topTitle, getTextColor: textColor, getTextSize: textSize, getBackground: homeBackground } = storeToRefs(globalConfig)
|
const { getTopTitle: topTitle, getTextColor: textColor, getTextSize: textSize, getBackground: homeBackground } = storeToRefs(globalConfig)
|
||||||
@@ -22,6 +22,7 @@ const { getTopTitle: topTitle, getTextColor: textColor, getTextSize: textSize, g
|
|||||||
:text-color="textColor"
|
:text-color="textColor"
|
||||||
:top-title="topTitle"
|
:top-title="topTitle"
|
||||||
:set-default-person-list="setDefaultPersonList"
|
:set-default-person-list="setDefaultPersonList"
|
||||||
|
:is-initial-done="isInitialDone"
|
||||||
/>
|
/>
|
||||||
<div id="container" ref="containerRef" class="3dContainer">
|
<div id="container" ref="containerRef" class="3dContainer">
|
||||||
<OptionButton
|
<OptionButton
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import useStore from '@/store'
|
|||||||
import { selectCard } from '@/utils'
|
import { selectCard } from '@/utils'
|
||||||
import { rgba } from '@/utils/color'
|
import { rgba } from '@/utils/color'
|
||||||
import { LotteryStatus } from './type'
|
import { LotteryStatus } from './type'
|
||||||
import { confettiFire, createSphereVertices, createTableVertices, initTableData } from './util'
|
import { confettiFire, createSphereVertices, createTableVertices, getRandomElements, initTableData } from './utils'
|
||||||
|
|
||||||
export function useViewModel() {
|
export function useViewModel() {
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
@@ -51,8 +51,9 @@ export function useViewModel() {
|
|||||||
const luckyCount = ref(10)
|
const luckyCount = ref(10)
|
||||||
const personPool = ref<IPersonConfig[]>([])
|
const personPool = ref<IPersonConfig[]>([])
|
||||||
const intervalTimer = ref<any>(null)
|
const intervalTimer = ref<any>(null)
|
||||||
|
const isInitialDone = ref<boolean>(false)
|
||||||
|
|
||||||
function init() {
|
function initThreeJs() {
|
||||||
const felidView = 40
|
const felidView = 40
|
||||||
const width = window.innerWidth
|
const width = window.innerWidth
|
||||||
const height = window.innerHeight
|
const height = window.innerHeight
|
||||||
@@ -358,14 +359,22 @@ export function useViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
luckyCount.value = leftover < luckyCount.value ? leftover : luckyCount.value
|
luckyCount.value = leftover < luckyCount.value ? leftover : luckyCount.value
|
||||||
for (let i = 0; i < luckyCount.value; i++) {
|
// 重构抽奖函数
|
||||||
if (personPool.value.length > 0) {
|
luckyTargets.value = getRandomElements(personPool.value, luckyCount.value)
|
||||||
// 解决随机元素概率过于不均等问题
|
luckyTargets.value.forEach((item) => {
|
||||||
const randomIndex = Math.floor(Math.random() * (personPool.value.length - 1))
|
const index = personPool.value.findIndex(person => person.id === item.id)
|
||||||
luckyTargets.value.push(personPool.value[randomIndex])
|
if (index > -1) {
|
||||||
personPool.value.splice(randomIndex, 1)
|
personPool.value.splice(index, 1)
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
// for (let i = 0; i < luckyCount.value; i++) {
|
||||||
|
// if (personPool.value.length > 0) {
|
||||||
|
// // 解决随机元素概率过于不均等问题
|
||||||
|
// const randomIndex = Math.floor(Math.random() * (personPool.value.length - 1))
|
||||||
|
// luckyTargets.value.push(personPool.value[randomIndex])
|
||||||
|
// personPool.value.splice(randomIndex, 1)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
toast.open({
|
toast.open({
|
||||||
// message: `现在抽取${currentPrize.value.name} ${leftover}人`,
|
// message: `现在抽取${currentPrize.value.name} ${leftover}人`,
|
||||||
@@ -575,13 +584,33 @@ export function useViewModel() {
|
|||||||
// 刷新页面
|
// 刷新页面
|
||||||
window.location.reload()
|
window.location.reload()
|
||||||
}
|
}
|
||||||
|
const init = () => {
|
||||||
|
const startTime = Date.now()
|
||||||
|
const maxWaitTime = 2000 // 2秒
|
||||||
|
|
||||||
|
const checkAndInit = () => {
|
||||||
|
// 如果人员列表有数据或者等待时间超过2秒,则执行初始化
|
||||||
|
if (allPersonList.value.length > 0 || (Date.now() - startTime) >= maxWaitTime) {
|
||||||
|
console.log('初始化完成')
|
||||||
|
tableData.value = initTableData({ allPersonList: allPersonList.value, rowCount: rowCount.value })
|
||||||
|
initThreeJs()
|
||||||
|
animation()
|
||||||
|
containerRef.value!.style.color = `${textColor}`
|
||||||
|
randomBallData()
|
||||||
|
window.addEventListener('keydown', listenKeyboard)
|
||||||
|
isInitialDone.value = true
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log('等待人员列表数据...')
|
||||||
|
// 继续等待
|
||||||
|
setTimeout(checkAndInit, 100) // 每100毫秒检查一次
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkAndInit()
|
||||||
|
}
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
tableData.value = initTableData({ allPersonList: allPersonList.value, rowCount: rowCount.value })
|
|
||||||
init()
|
init()
|
||||||
animation()
|
|
||||||
containerRef.value!.style.color = `${textColor}`
|
|
||||||
randomBallData()
|
|
||||||
window.addEventListener('keydown', listenKeyboard)
|
|
||||||
})
|
})
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
@@ -591,6 +620,11 @@ export function useViewModel() {
|
|||||||
intervalTimer.value = null
|
intervalTimer.value = null
|
||||||
window.removeEventListener('keydown', listenKeyboard)
|
window.removeEventListener('keydown', listenKeyboard)
|
||||||
})
|
})
|
||||||
|
// watch(() => allPersonList.value, (newVal) => {
|
||||||
|
// if (newVal.length) {
|
||||||
|
// init()
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
|
||||||
return {
|
return {
|
||||||
setDefaultPersonList,
|
setDefaultPersonList,
|
||||||
@@ -602,5 +636,6 @@ export function useViewModel() {
|
|||||||
enterLottery,
|
enterLottery,
|
||||||
tableData,
|
tableData,
|
||||||
currentStatus,
|
currentStatus,
|
||||||
|
isInitialDone,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2
src/views/Home/utils/index.ts
Normal file
2
src/views/Home/utils/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './random'
|
||||||
|
export * from './table'
|
||||||
53
src/views/Home/utils/random.ts
Normal file
53
src/views/Home/utils/random.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
/**
|
||||||
|
* 浏览器端加密安全洗牌(无需指定抽取数量)
|
||||||
|
* @param array 要洗牌的数组
|
||||||
|
* @returns 洗牌后的新数组
|
||||||
|
*/
|
||||||
|
function shuffleBrowserCrypto<T>(array: T[]): T[] {
|
||||||
|
const newArray = [...array]
|
||||||
|
if (newArray.length <= 1)
|
||||||
|
return newArray
|
||||||
|
|
||||||
|
// 遍历数组,每轮生成一个随机索引
|
||||||
|
for (let i = newArray.length - 1; i > 0; i--) {
|
||||||
|
// 步骤1:生成一个 32 位无符号加密随机数(仅需1个)
|
||||||
|
const randomBuffer = new Uint32Array(1) // 长度1表示只生成1个随机数
|
||||||
|
crypto.getRandomValues(randomBuffer)
|
||||||
|
|
||||||
|
// 步骤2:将随机数映射到 [0, i] 范围(核心:动态适配当前i的范围)
|
||||||
|
const randomIndex = randomBuffer[0] % (i + 1);
|
||||||
|
|
||||||
|
// 步骤3:交换元素
|
||||||
|
[newArray[i], newArray[randomIndex]] = [newArray[randomIndex], newArray[i]]
|
||||||
|
}
|
||||||
|
return newArray
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 从源数组中随机获取指定数量的元素
|
||||||
|
* @param {Array} sourceArray 源数组
|
||||||
|
* @param {number} count 要获取的元素数量
|
||||||
|
* @returns {Array} 随机获取的元素
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function getRandomElements<T>(sourceArray: T[], count: number): T[] {
|
||||||
|
if (count <= 0)
|
||||||
|
return []
|
||||||
|
if (count >= sourceArray.length) {
|
||||||
|
return shuffleBrowserCrypto([...sourceArray])
|
||||||
|
} // 抽全部=洗牌
|
||||||
|
|
||||||
|
const newArray = [...sourceArray]
|
||||||
|
const result: T[] = []
|
||||||
|
|
||||||
|
// 抽取 count 个元素,每轮选一个随机索引加入结果,然后从原数组移除
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const randomBuffer = new Uint32Array(1)
|
||||||
|
crypto.getRandomValues(randomBuffer)
|
||||||
|
const randomIndex = randomBuffer[0] % newArray.length
|
||||||
|
|
||||||
|
result.push(newArray[randomIndex])
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
131
src/views/Home/utils/table.ts
Normal file
131
src/views/Home/utils/table.ts
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
import type { IPersonConfig } from '@/types/storeType'
|
||||||
|
import confetti from 'canvas-confetti'
|
||||||
|
import { Object3D, Vector3 } from 'three'
|
||||||
|
import { filterData } from '@/utils'
|
||||||
|
/**
|
||||||
|
* @description 初始化表格数据
|
||||||
|
* @param0 allPersonList 所有人的列表
|
||||||
|
* @param1 rowCount 行数,默认是7行
|
||||||
|
* @returns 表格数据
|
||||||
|
*/
|
||||||
|
export function initTableData({ allPersonList, rowCount }: { allPersonList: IPersonConfig[], rowCount: number }): IPersonConfig[] {
|
||||||
|
let tableData: IPersonConfig[] = []
|
||||||
|
if (allPersonList.length <= 0) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
const totalCount = rowCount * 7
|
||||||
|
const allPersonLength = allPersonList.length
|
||||||
|
if (allPersonLength < totalCount) {
|
||||||
|
tableData = Array.from({ length: totalCount }, () => JSON.parse(JSON.stringify(allPersonList))).flat()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tableData = allPersonList.slice(0, totalCount)
|
||||||
|
}
|
||||||
|
tableData = filterData(tableData.slice(0, totalCount), rowCount)
|
||||||
|
return tableData
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 横铺图形:处理数据,把每个卡片在界面的位置写入
|
||||||
|
* @param0 tableData 表格数据
|
||||||
|
* @param1 rowCount 每行有多少个元素
|
||||||
|
* @param2 cardSize 卡片的大小
|
||||||
|
* @returns Object3D[]
|
||||||
|
*/
|
||||||
|
export function createTableVertices({ tableData, rowCount, cardSize }: { tableData: IPersonConfig[], rowCount: number, cardSize: { width: number, height: number } }): Object3D[] {
|
||||||
|
const tableLen = tableData.length
|
||||||
|
const objects: Object3D[] = []
|
||||||
|
for (let i = 0; i < tableLen; i++) {
|
||||||
|
const object = new Object3D()
|
||||||
|
|
||||||
|
object.position.x = tableData[i].x * (cardSize.width + 40) - rowCount * 90
|
||||||
|
object.position.y = -tableData[i].y * (cardSize.height + 20) + 1000
|
||||||
|
object.position.z = 0
|
||||||
|
objects.push(object)
|
||||||
|
// targets.table.push(object)
|
||||||
|
}
|
||||||
|
return objects
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @description 创建球体
|
||||||
|
* @param0 objectsLength 物体的个数
|
||||||
|
* @returns Object3D[]
|
||||||
|
*/
|
||||||
|
export function createSphereVertices({ objectsLength }: { objectsLength: number }): Object3D[] {
|
||||||
|
let i = 0
|
||||||
|
const resObjects: Object3D[] = []
|
||||||
|
// const objLength = objects.value.length
|
||||||
|
const vector = new Vector3()
|
||||||
|
|
||||||
|
for (; i < objectsLength; ++i) {
|
||||||
|
const phi = Math.acos(-1 + (2 * i) / objectsLength)
|
||||||
|
const theta = Math.sqrt(objectsLength * Math.PI) * phi
|
||||||
|
const object = new Object3D()
|
||||||
|
|
||||||
|
object.position.x = 800 * Math.cos(theta) * Math.sin(phi)
|
||||||
|
object.position.y = 800 * Math.sin(theta) * Math.sin(phi)
|
||||||
|
object.position.z = -800 * Math.cos(phi)
|
||||||
|
|
||||||
|
// rotation object
|
||||||
|
vector.copy(object.position).multiplyScalar(2)
|
||||||
|
object.lookAt(vector)
|
||||||
|
resObjects.push(object)
|
||||||
|
}
|
||||||
|
return resObjects
|
||||||
|
}
|
||||||
|
|
||||||
|
export function confettiFire() {
|
||||||
|
const duration = 3 * 1000
|
||||||
|
const end = Date.now() + duration;
|
||||||
|
(function frame() {
|
||||||
|
// launch a few confetti from the left edge
|
||||||
|
confetti({
|
||||||
|
particleCount: 2,
|
||||||
|
angle: 60,
|
||||||
|
spread: 55,
|
||||||
|
origin: { x: 0 },
|
||||||
|
})
|
||||||
|
// and launch a few from the right edge
|
||||||
|
confetti({
|
||||||
|
particleCount: 2,
|
||||||
|
angle: 120,
|
||||||
|
spread: 55,
|
||||||
|
origin: { x: 1 },
|
||||||
|
})
|
||||||
|
|
||||||
|
// keep going until we are out of time
|
||||||
|
if (Date.now() < end) {
|
||||||
|
requestAnimationFrame(frame)
|
||||||
|
}
|
||||||
|
}())
|
||||||
|
centerFire(0.25, {
|
||||||
|
spread: 26,
|
||||||
|
startVelocity: 55,
|
||||||
|
})
|
||||||
|
centerFire(0.2, {
|
||||||
|
spread: 60,
|
||||||
|
})
|
||||||
|
centerFire(0.35, {
|
||||||
|
spread: 100,
|
||||||
|
decay: 0.91,
|
||||||
|
scalar: 0.8,
|
||||||
|
})
|
||||||
|
centerFire(0.1, {
|
||||||
|
spread: 120,
|
||||||
|
startVelocity: 25,
|
||||||
|
decay: 0.92,
|
||||||
|
scalar: 1.2,
|
||||||
|
})
|
||||||
|
centerFire(0.1, {
|
||||||
|
spread: 120,
|
||||||
|
startVelocity: 45,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function centerFire(particleRatio: number, opts: any) {
|
||||||
|
const count = 200
|
||||||
|
confetti({
|
||||||
|
origin: { y: 0.7 },
|
||||||
|
...opts,
|
||||||
|
particleCount: Math.floor(count * particleRatio),
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user