Compare commits

...

2 Commits

Author SHA1 Message Date
expdsn d7557dc318 游戏不能拖入文件夹 2024-10-28 18:51:44 +08:00
expdsn 583b296ace 完成游戏添加,接入第三方游戏列表 2024-10-28 18:51:13 +08:00
10 changed files with 205 additions and 65 deletions

View File

@ -19,6 +19,7 @@
"ali-oss": "^6.21.0", "ali-oss": "^6.21.0",
"ant-design-vue": "4.x", "ant-design-vue": "4.x",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"gsap": "^3.12.5", "gsap": "^3.12.5",
"localforage": "^1.10.0", "localforage": "^1.10.0",
@ -41,6 +42,7 @@
"@rushstack/eslint-patch": "^1.8.0", "@rushstack/eslint-patch": "^1.8.0",
"@tsconfig/node20": "^20.1.4", "@tsconfig/node20": "^20.1.4",
"@types/ali-oss": "^6.16.11", "@types/ali-oss": "^6.16.11",
"@types/crypto-js": "^4.2.2",
"@types/node": "^20.14.5", "@types/node": "^20.14.5",
"@types/sortablejs": "^1.15.8", "@types/sortablejs": "^1.15.8",
"@types/ua-parser-js": "^0.7.39", "@types/ua-parser-js": "^0.7.39",

BIN
public/bg/game.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

BIN
public/icons/gameicon.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -2,8 +2,13 @@ export const aIUrl = 'https://metaso.cn/?s=uitab&referrer_s=uitab&q='
export const translateUrl = 'https://fanyi.baidu.com/mtpe-individual/multimodal?lang=zh2en&query=' export const translateUrl = 'https://fanyi.baidu.com/mtpe-individual/multimodal?lang=zh2en&query='
// oss地址 // oss地址
export const ossBase = import.meta.env.PROD export const ossBase = import.meta.env.PROD
? 'http://btab.oss-cn-hangzhou.aliyuncs.com' ? 'http://newfatfox.oss-cn-beijing.aliyuncs.com'
: 'http://btab.oss-cn-hangzhou.aliyuncs.com' : 'http://newfatfox.oss-cn-beijing.aliyuncs.com'
// 前端地址
export const frontAddress = import.meta.env.PROD
? 'http://goosetab.com'
: 'http://goosetab.com'
// oss cdn 加速地址 // oss cdn 加速地址
export const ossCdnBase = import.meta.env.PROD ? ossBase : ossBase export const ossCdnBase = import.meta.env.PROD ? ossBase : ossBase

View File

@ -154,17 +154,15 @@ export default defineComponent(() => {
</Form> </Form>
<div class="w-full h-0 flex-grow p-6"> <div class="w-full h-0 flex-grow p-6">
<div class="w-full h-full relative"> <div class="w-full h-full relative">
<Transition> {type.value === 0 ? (
{type.value === 0 ? ( <WidgetAdder />
<WidgetAdder /> ) : type.value === 1 ? (
) : type.value === 1 ? ( <HotAdder />
<HotAdder /> ) : type.value === 2 ? (
) : type.value === 2 ? ( <CustomAdder />
<CustomAdder /> ) : (
) : ( <GameAdder />
<GameAdder /> )}
)}
</Transition>
</div> </div>
</div> </div>
</div> </div>

View File

@ -7,6 +7,11 @@ import useLayoutStore from '../useLayoutStore'
import { AddToToken } from './AdderPage' import { AddToToken } from './AdderPage'
import { v4 as uuid } from 'uuid' import { v4 as uuid } from 'uuid'
import { Button } from 'ant-design-vue' import { Button } from 'ant-design-vue'
import { frontAddress, ossBase } from '@/config'
import dayjs from 'dayjs'
import { generateRandomString } from '@/utils/tool'
import MD5 from 'crypto-js/md5'
const SECRET = 'A1Cv12olxT12dOE3xA1vPA=='
const URL_ADDRESS = 'http://newfatfox.oss-cn-beijing.aliyuncs.com' const URL_ADDRESS = 'http://newfatfox.oss-cn-beijing.aliyuncs.com'
interface GameType { interface GameType {
id: string id: string
@ -18,6 +23,18 @@ interface GameType {
despt: string despt: string
icon: string icon: string
} }
interface OtherGame {
id: number // 游戏ID
category_ids: number[] // 分类ID数组
rank: number // 排名
name: string // 游戏名称
short_description: string // 简短描述
description: string // 详细描述
url: string // 游戏详情链接
icon: string // 图标URL
cover_url: string // 封面URL
corner_mark: number // 角标标识
}
export const GameItem = defineComponent({ export const GameItem = defineComponent({
props: { props: {
content: { content: {
@ -31,7 +48,7 @@ export const GameItem = defineComponent({
const addTo = inject(AddToToken) const addTo = inject(AddToToken)
return () => ( return () => (
<div <div
class={clsx(' w-full h-full rounded-lg flex flex-col justify-between shadow p-4', { class={clsx(' w-full h-full rounded-lg flex flex-col justify-between shadow p-4 relative', {
'bg-white/20': isGame.value, 'bg-white/20': isGame.value,
'bg-white/80': !isGame.value 'bg-white/80': !isGame.value
})} })}
@ -39,7 +56,12 @@ export const GameItem = defineComponent({
> >
<div class="flex"> <div class="flex">
<img <img
src={props.content.icon.replace('res/game', URL_ADDRESS)} loading="lazy"
src={
props.content.icon.startsWith('http')
? props.content.icon
: URL_ADDRESS + '/' + props.content.icon
}
class="w-[58px] h-[58px] bg-cover rounded-lg shadow-lg" class="w-[58px] h-[58px] bg-cover rounded-lg shadow-lg"
style={{ style={{
background: props.content.icon background: props.content.icon
@ -48,7 +70,7 @@ export const GameItem = defineComponent({
<div class="px-2 w-0 flex-grow"> <div class="px-2 w-0 flex-grow">
<div <div
class={clsx('text text-sm', { class={clsx('text text-sm text-ellipsis overflow-hidden whitespace-nowrap', {
'text-white': isGame.value, 'text-white': isGame.value,
'text-black/80': !isGame.value 'text-black/80': !isGame.value
})} })}
@ -78,14 +100,21 @@ export const GameItem = defineComponent({
layout.addBlock( layout.addBlock(
{ {
id: uuid(), id: uuid(),
link: '', link: !props.content.rom.startsWith('http')
name: props.content.name, ? `${frontAddress}/emu/#/home?params=${JSON.stringify({
...props.content,
rom: ossBase + '/' + props.content.rom
})}`
: props.content.rom,
name: '',
label: props.content.name, label: props.content.name,
icon: '', icon: props.content.icon.startsWith('http')
? props.content.icon
: URL_ADDRESS + '/' + props.content.icon,
text: '', text: '',
background: '', background: '',
color: '', color: '',
w: 1, w: props.content.rom.startsWith('http') ? 1 : 2,
h: 1 h: 1
}, },
addTo?.value addTo?.value
@ -106,21 +135,53 @@ export default defineComponent(() => {
const isGame = computed(() => layout.state.current === 0) const isGame = computed(() => layout.state.current === 0)
const appList = ref<GameType[]>([]) const appList = ref<GameType[]>([])
const selectType = ref('fc') const selectType = ref('fc')
const fetchGame = async (page: number) => {
const parems = `nonce=${generateRandomString(8)}&pid=PIDc8uT24mpo&timestamp=${dayjs().unix()}`
const sign = MD5(parems + SECRET).toString()
const response = await fetch(
`https://ge.yiqiyoo.com/game/v2/third-part/games?${parems}&sign=${sign}&paginate.limit=99&paginate.page=${page}`
)
const res = await response.json()
return res.data.items
}
watch( watch(
selectType, selectType,
(val) => { (val) => {
loading.value = true loading.value = true
if (val !== 'yiqiyoo') {
request<GameType[]>('GET', `/api/games?type=${val}`) request<GameType[]>('GET', `/api/games?type=${val}`)
.then((res) => { .then((res) => {
appList.value = res appList.value = res
}) })
.finally(() => { .finally(() => {
setTimeout(() => {
loading.value = false
}, 200)
})
} else {
try {
Promise.all([fetchGame(1), fetchGame(2), fetchGame(3)]).then((res) => {
console.log(res.flat())
const resData = res.flat() as OtherGame[]
appList.value = resData.map((el) => ({
id: el.id.toString(),
name: el.name,
despt: el.short_description,
icon: el.icon,
rom: el.url,
playstation: el.url,
hot: el.rank,
category: el.category_ids[0].toString()
}))
})
} catch (err) {
console.error(err)
} finally {
setTimeout(() => { setTimeout(() => {
loading.value = false loading.value = false
}, 200) }, 200)
}) }
}
}, },
{ {
immediate: true immediate: true
@ -144,7 +205,13 @@ export default defineComponent(() => {
oridinal: 1 oridinal: 1
}, },
{ {
id: 'gma', id: 'yiqiyoo',
type: '休闲游戏',
attr: 1,
oridinal: 2
},
{
id: 'gba',
type: '经典GBA', type: '经典GBA',
attr: 3, attr: 3,
oridinal: 3 oridinal: 3
@ -158,10 +225,10 @@ export default defineComponent(() => {
select: (text: string) => ( select: (text: string) => (
<button <button
class={clsx( class={clsx(
'px-[20px] py-1 items-center justify-center duration-75 shrink-0 flex rounded-2xl whitespace-nowrap ', 'px-[20px] text-[14px] py-1 items-center justify-center duration-75 shrink-0 flex rounded-2xl whitespace-nowrap ',
isGame.value isGame.value
? 'bg-white/30 text-white bg-gradient-to-b from-[#ffaa4e] to-[#ff6227]' ? 'bg-white/30 text-white bg-gradient-to-b from-[#ffaa4e] to-[#ff6227]'
: 'text-[#000] bg-[#D6D6D6]' : 'text-white bg-[#D6D6D6] bg-gradient-to-b from-[#ffaa4e] to-[#ff6227]'
)} )}
> >
{text} {text}
@ -170,10 +237,10 @@ export default defineComponent(() => {
unSelect: (text: string) => ( unSelect: (text: string) => (
<button <button
class={clsx( class={clsx(
'px-[20px] py-1 items-center justify-center duration-75 shrink-0 flex rounded-2xl whitespace-nowrap', 'px-[20px] text-[14px] py-1 items-center justify-center duration-75 shrink-0 flex rounded-2xl whitespace-nowrap',
isGame.value isGame.value
? ' text-[#999] bg-white/10 hover:bg-white/20' ? ' text-[#999] bg-white/10 hover:bg-white/20'
: 'text-[#000] hover:bg-[#f0ecec]' : 'text-[#666] hover:bg-black/10 bg-black/[0.05] hover:bg-[#f0ecec]'
)} )}
> >
{text} {text}
@ -196,9 +263,10 @@ export default defineComponent(() => {
.fill(0) .fill(0)
.map((el, idx) => ( .map((el, idx) => (
<div <div
class={ class={clsx(
' relative cursor-pointer bg-gray-500 group w-full flex-grow-0 rounded-xl overflow-hidden' ' relative cursor-pointer bg-white/20 group w-full flex-grow-0 rounded-lg overflow-hidden',
} isGame.value ? 'bg-white/30 ' : ' bg-[#fff]/70'
)}
key={idx} key={idx}
/> />
))} ))}

View File

@ -35,18 +35,35 @@ export const LinkItem = defineComponent({
const addTo = inject(AddToToken) const addTo = inject(AddToToken)
return () => ( return () => (
<div <div
class={clsx(' w-full h-full rounded-lg flex flex-col justify-between shadow p-4', { class={clsx(' w-full h-full rounded-lg relative flex flex-col justify-between shadow p-4', {
'bg-white/20': isGame.value, 'bg-white/20': isGame.value,
'bg-white/80': !isGame.value 'bg-white/80': !isGame.value
})} })}
key={props.content.name} key={props.content.name}
> >
<div
onClick={() => {
window.open(props.content.url, '_blank')
}}
class={
'h-0 w-0 absolute right-0 top-0 border-t-[38px] cursor-pointer border-l-[38px] group border-t-[#fff1e2] hover:border-t-[#ffaa4e] border-l-transparent'
}
>
<p
class={
'rotate-45 top-[-35px] scale-90 text-[#999] group-hover:text-white text-[12px] absolute right-0 whitespace-nowrap'
}
>
</p>
</div>
<div class="flex"> <div class="flex">
<img <img
src={props.content.icon} src={props.content.icon}
loading="lazy"
class="w-[58px] h-[58px] bg-cover rounded-lg shadow-lg" class="w-[58px] h-[58px] bg-cover rounded-lg shadow-lg"
style={{ style={{
background: props.content.background, background: props.content.background
}} }}
/> />
@ -82,10 +99,10 @@ export const LinkItem = defineComponent({
layout.addBlock( layout.addBlock(
{ {
id: uuid(), id: uuid(),
link: '', link: props.content.url,
name: props.content.name, name: props.content.name,
label: props.content.name, label: props.content.name,
icon: '', icon: props.content.icon,
text: '', text: '',
background: '', background: '',
color: '', color: '',
@ -150,10 +167,10 @@ export default defineComponent(() => {
select: (text: string) => ( select: (text: string) => (
<button <button
class={clsx( class={clsx(
'px-[20px] py-1 items-center justify-center duration-75 shrink-0 flex rounded-2xl whitespace-nowrap ', 'px-[20px] text-[14px] py-1 items-center justify-center duration-75 shrink-0 flex rounded-2xl whitespace-nowrap ',
isGame.value isGame.value
? 'bg-white/30 text-white bg-gradient-to-b from-[#ffaa4e] to-[#ff6227]' ? 'bg-white/30 text-white bg-gradient-to-b from-[#ffaa4e] to-[#ff6227]'
: 'text-[#000] bg-[#D6D6D6]' : 'text-white bg-[#D6D6D6] bg-gradient-to-b from-[#ffaa4e] to-[#ff6227]'
)} )}
> >
{text} {text}
@ -162,10 +179,10 @@ export default defineComponent(() => {
unSelect: (text: string) => ( unSelect: (text: string) => (
<button <button
class={clsx( class={clsx(
'px-[20px] py-1 items-center justify-center duration-75 shrink-0 flex rounded-2xl whitespace-nowrap', 'px-[20px] text-[14px] py-1 items-center justify-center duration-75 shrink-0 flex rounded-2xl whitespace-nowrap',
isGame.value isGame.value
? ' text-[#999] bg-white/10 hover:bg-white/20' ? ' text-[#999] bg-white/10 hover:bg-white/20'
: 'text-[#000] hover:bg-[#f0ecec]' : 'text-[#666] hover:bg-black/10 bg-black/[0.05] hover:bg-[#f0ecec]'
)} )}
> >
{text} {text}
@ -188,9 +205,10 @@ export default defineComponent(() => {
.fill(0) .fill(0)
.map((el, idx) => ( .map((el, idx) => (
<div <div
class={ class={clsx(
' relative cursor-pointer bg-gray-500 group w-full flex-grow-0 rounded-xl overflow-hidden' ' relative cursor-pointer bg-white/20 group w-full flex-grow-0 rounded-lg overflow-hidden',
} isGame.value ? 'bg-white/30 ' : ' bg-[#fff]/70'
)}
key={idx} key={idx}
/> />
))} ))}

View File

@ -61,9 +61,13 @@ export default defineComponent({
const link = props.block.link const link = props.block.link
// 小组件无法合并 // 小组件无法合并
if (!link) return if (!link) return
//游戏不能合并
if (props.block.w !== 1) return
const oldIdx = layout.currentPage.list.findIndex((el) => el.id === dragging.id) const oldIdx = layout.currentPage.list.findIndex((el) => el.id === dragging.id)
if (oldIdx < 0) return if (oldIdx < 0) return
const oldBlock = layout.currentPage.list[oldIdx] const oldBlock = layout.currentPage.list[oldIdx]
//游戏不能合并
if (oldBlock.w !== 1) return
// 文件夹不能移入文件夹 // 文件夹不能移入文件夹
if (!oldBlock || oldBlock.link.startsWith('id:')) return if (!oldBlock || oldBlock.link.startsWith('id:')) return
if (link.startsWith('id:')) { if (link.startsWith('id:')) {
@ -132,7 +136,7 @@ export default defineComponent({
</div> </div>
{settings.state.showBlockLabel && ( {settings.state.showBlockLabel && (
<div <div
class="absolute left-0 -bottom-3 text-sm text-white text-center w-full overflow-hidden text-ellipsis whitespace-nowrap break-all font-bold" class="absolute left-1/2 -translate-x-1/2 -bottom-3 text-sm text-white text-center w-[172px] overflow-hidden text-ellipsis whitespace-nowrap break-all font-bold"
style="text-shadow: 0 0 4px rgba(0,0,0,.6)" style="text-shadow: 0 0 4px rgba(0,0,0,.6)"
> >
{layout.getLabel(props.block)} {layout.getLabel(props.block)}

View File

@ -19,22 +19,51 @@ export default defineComponent({
}, },
setup(props) { setup(props) {
const menu = useMenuStore() const menu = useMenuStore()
return () => ( if (props.block.w === 1 && props.block.h === 1) {
<div return () => (
class="w-full h-full flex justify-center items-center font-bold bg-cover bg-center bg-no-repeat" <div
onContextmenu={(e) => { class="w-full h-full flex justify-center items-center font-bold bg-cover bg-center bg-no-repeat"
e.preventDefault() onContextmenu={(e) => {
menu.open(props.block) e.preventDefault()
}} menu.open(props.block)
style={{ }}
backgroundColor: props.block.background || 'white', onClick={() => {
color: props.block.color || 'black', window.open(props.block.link, '_blank')
backgroundImage: props.block.icon ? `url('${props.block.icon}')` : '', }}
fontSize: props.dock ? '16px' : props.brief ? '12px' : 'calc(var(--block-size) / 5)' style={{
}} backgroundColor: props.block.background || 'white',
> color: props.block.color || 'black',
<div>{props.brief ? props.block.text[0] : props.block.text}</div> backgroundImage: props.block.icon ? `url('${props.block.icon}')` : '',
</div> fontSize: props.dock ? '16px' : props.brief ? '12px' : 'calc(var(--block-size) / 5)'
) }}
>
<div>{props.brief ? props.block.text[0] : props.block.text}</div>
</div>
)
} else {
return () => (
<div
class="w-full h-full flex justify-between px-3 items-center font-bold bg-cover bg-center bg-no-repeat"
onContextmenu={(e) => {
e.preventDefault()
menu.open(props.block)
}}
onClick={() => {
window.open(props.block.link, '_blank')
}}
style={{
backgroundImage: `url('/bg/game.webp')`
}}
>
<img src={props.block.icon} class={'w-[50px] h-[50px] rounded-lg'} alt="game icon" />
<div class={'flex-1 text-white flex justify-center'}>
<span class={'w-[70px] text-ellipsis overflow-hidden whitespace-nowrap'}>
{props.block.label}
</span>
</div>
<img src="/icons/gameicon.webp" alt="game_icon" class={'absolute right-0 bottom-0 w-[36px]'} />
</div>
)
}
} }
}) })

View File

@ -7,3 +7,19 @@
export function randomNum(min: number, max: number) { export function randomNum(min: number, max: number) {
return Math.floor(Math.random() * (max - min + 1)) + min; return Math.floor(Math.random() * (max - min + 1)) + min;
} }
/**
* 0 n
* @param n
* @returns
*/
export function generateRandomString(n: number): string {
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const length = Math.floor(Math.random() * (n + 1));
let result = '';
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * characters.length));
}
return result;
}