diff --git a/src/app/content/App.tsx b/src/app/content/App.tsx
index 79f9b67..b484e34 100644
--- a/src/app/content/App.tsx
+++ b/src/app/content/App.tsx
@@ -9,6 +9,8 @@ import { stringToHex } from '@/utils'
import { Toaster } from '@/components/ui/Sonner'
import UserInfoDomain from '@/domain/UserInfo'
import Setup from '@/app/content/views/Setup'
+import MessageListDomain from '@/domain/MessageList'
+import { useEffect } from 'react'
const hostRoomId = stringToHex(document.location.host)
@@ -16,15 +18,31 @@ export default function App() {
const send = useRemeshSend()
const roomDomain = useRemeshDomain(RoomDomain())
const userInfoDomain = useRemeshDomain(UserInfoDomain())
- send(roomDomain.command.JoinRoomCommand(hostRoomId))
- const isLogin = useRemeshQuery(userInfoDomain.query.IsLoginQuery())
+ const messageListDomain = useRemeshDomain(MessageListDomain())
+ const joinRoomFinished = useRemeshQuery(roomDomain.query.IsFinishedQuery())
+ const userInfoFinished = useRemeshQuery(userInfoDomain.query.IsFinishedQuery())
+
+ const userInfo = useRemeshQuery(userInfoDomain.query.UserInfoQuery())
+
+ const notUserInfo = userInfoFinished && !userInfo
+
+ useEffect(() => {
+ if (userInfoFinished) {
+ if (userInfo) {
+ !joinRoomFinished && send(roomDomain.command.JoinRoomCommand(hostRoomId))
+ } else {
+ send(messageListDomain.command.ClearListCommand())
+ }
+ }
+ }, [userInfoFinished, userInfo, joinRoomFinished])
+
return (
<>
- {!isLogin && }
+ {notUserInfo && }
diff --git a/src/app/content/index.tsx b/src/app/content/index.tsx
index a15e0d5..4573c7c 100644
--- a/src/app/content/index.tsx
+++ b/src/app/content/index.tsx
@@ -18,7 +18,7 @@ export default defineContentScript({
async main(ctx) {
const store = Remesh.store({
externs: [IndexDBStorageImpl, BrowserSyncStorageImpl, PeerRoomImpl],
- inspectors: [RemeshLogger()]
+ inspectors: __DEV__ ? [RemeshLogger()] : []
})
const ui = await createShadowRootUi(ctx, {
diff --git a/src/app/content/views/Setup/index.tsx b/src/app/content/views/Setup/index.tsx
index 534f039..f915753 100644
--- a/src/app/content/views/Setup/index.tsx
+++ b/src/app/content/views/Setup/index.tsx
@@ -76,17 +76,18 @@ const Setup: FC = () => {
}
useEffect(() => {
- send(messageListDomain.command.ClearListCommand())
const timer = new Timer(
async () => {
- const userInfo = await refreshUserInfo()
- await createMessage(userInfo)
+ await createMessage(await refreshUserInfo())
},
{ delay: 2000, immediate: true, limit: mockTextList.length }
)
timer.start()
- return () => timer.stop()
+ return () => {
+ timer.stop()
+ send(messageListDomain.command.ClearListCommand())
+ }
}, [])
return (
diff --git a/src/domain/Room.ts b/src/domain/Room.ts
index 4a9ad47..c561f05 100644
--- a/src/domain/Room.ts
+++ b/src/domain/Room.ts
@@ -1,11 +1,12 @@
import { Remesh } from 'remesh'
-import { map, merge, tap } from 'rxjs'
+import { map, merge, switchMap, tap, defer, of, EMPTY, mergeMap } from 'rxjs'
import { type MessageUser } from './MessageList'
import { PeerRoomExtern } from '@/domain/externs/PeerRoom'
import MessageListDomain from '@/domain/MessageList'
import UserInfoDomain from '@/domain/UserInfo'
import { callbackToObservable, desert } from '@/utils'
import { nanoid } from 'nanoid'
+import StatusModule from './modules/Status'
export enum MessageType {
Like = 'like',
@@ -40,6 +41,10 @@ const RoomDomain = Remesh.domain({
const MessageListQuery = messageListDomain.query.ListQuery
+ const RoomStatusState = StatusModule(domain, {
+ name: 'Room.RoomStatusModule'
+ })
+
const PeerListState = domain.state({
name: 'Room.PeerListState',
default: [peerRoom.selfId]
@@ -56,15 +61,15 @@ const RoomDomain = Remesh.domain({
name: 'RoomJoinRoomCommand',
impl: (_, roomId: string) => {
peerRoom.joinRoom(roomId)
- return [JoinRoomEvent(peerRoom.selfId)]
+ return [JoinRoomEvent(roomId), RoomStatusState.command.SetFinishedCommand()]
}
})
const LeaveRoomCommand = domain.command({
name: 'RoomLeaveRoomCommand',
- impl: (_) => {
+ impl: (_, roomId: string) => {
peerRoom.leaveRoom()
- return [LeaveRoomEvent(peerRoom.selfId)]
+ return [LeaveRoomEvent(roomId), RoomStatusState.command.SetInitialCommand()]
}
})
@@ -156,6 +161,18 @@ const RoomDomain = Remesh.domain({
name: 'RoomLeaveRoomEvent'
})
+ const OnMessageEvent = domain.event({
+ name: 'RoomOnMessageEvent'
+ })
+
+ const OnJoinRoomEvent = domain.event({
+ name: 'RoomOnJoinRoomEvent'
+ })
+
+ const OnLeaveRoomEvent = domain.event({
+ name: 'RoomOnLeaveRoomEvent'
+ })
+
const UpdatePeerListCommand = domain.command({
name: 'RoomUpdatePeerListCommand',
impl: ({ get }, action: { type: 'create' | 'delete'; peerId: string }) => {
@@ -208,106 +225,111 @@ const RoomDomain = Remesh.domain({
domain.effect({
name: 'RoomOnMessageEffect',
- impl: ({ get }) => {
- const onMessage$ = callbackToObservable(peerRoom.onMessage.bind(peerRoom))
- return onMessage$.pipe(
- map((message) => {
- switch (message.type) {
- case 'text':
- return messageListDomain.command.CreateItemCommand({
- ...message,
- date: Date.now(),
- likeUsers: [],
- hateUsers: []
- })
- case 'like': {
- if (!get(messageListDomain.query.HasItemQuery(message.id))) {
- return null
- }
- const _message = get(messageListDomain.query.ItemQuery(message.id))
- return messageListDomain.command.UpdateItemCommand({
- ..._message,
- likeUsers: desert(
- _message.likeUsers,
- {
- userId: message.userId,
- username: message.username,
- userAvatar: message.userAvatar
- },
- 'userId'
+ impl: ({ fromEvent, get }) => {
+ const onMessage$ = fromEvent(JoinRoomEvent).pipe(
+ switchMap(() => callbackToObservable(peerRoom.onMessage.bind(peerRoom))),
+ mergeMap((message) => {
+ console.log('onMessage', message)
+
+ const messageEvent$ = of(OnMessageEvent(message))
+
+ const commandEvent$ = (() => {
+ switch (message.type) {
+ case 'text':
+ return of(
+ messageListDomain.command.CreateItemCommand({
+ ...message,
+ date: Date.now(),
+ likeUsers: [],
+ hateUsers: []
+ })
)
- })
- }
- case 'hate': {
- if (!get(messageListDomain.query.HasItemQuery(message.id))) {
- return null
- }
- const _message = get(messageListDomain.query.ItemQuery(message.id))
- return messageListDomain.command.UpdateItemCommand({
- ..._message,
- hateUsers: desert(
- _message.hateUsers,
- {
- userId: message.userId,
- username: message.username,
- userAvatar: message.userAvatar
- },
- 'userId'
+ case 'like':
+ case 'hate': {
+ if (!get(messageListDomain.query.HasItemQuery(message.id))) {
+ return EMPTY
+ }
+ const _message = get(messageListDomain.query.ItemQuery(message.id))
+ const users = message.type === 'like' ? 'likeUsers' : 'hateUsers'
+ return of(
+ messageListDomain.command.UpdateItemCommand({
+ ..._message,
+ [users]: desert(
+ _message[users],
+ {
+ userId: message.userId,
+ username: message.username,
+ userAvatar: message.userAvatar
+ },
+ 'userId'
+ )
+ })
)
- })
+ }
+ default:
+ console.warn('未知消息类型', message)
+ return EMPTY
}
- default:
- console.warn('unknown message type', message)
- return null
- }
+ })()
+ return merge(messageEvent$, commandEvent$)
})
)
+ return onMessage$
}
})
domain.effect({
name: 'RoomOnJoinRoomEffect',
- impl: () => {
- const onJoinRoom$ = callbackToObservable(peerRoom.onJoinRoom.bind(peerRoom))
- return onJoinRoom$.pipe(
+ impl: ({ fromEvent }) => {
+ const onJoinRoom$ = fromEvent(JoinRoomEvent).pipe(
+ switchMap(() => callbackToObservable(peerRoom.onJoinRoom.bind(peerRoom))),
map((peerId) => {
console.log('onJoinRoom', peerId)
- return [UpdatePeerListCommand({ type: 'create', peerId }), JoinRoomEvent(peerId)]
+ return [UpdatePeerListCommand({ type: 'create', peerId }), OnJoinRoomEvent(peerId)]
})
)
+ return onJoinRoom$
}
})
domain.effect({
name: 'RoomOnLeaveRoomEffect',
- impl: () => {
- const onLeaveRoom$ = callbackToObservable(peerRoom.onLeaveRoom.bind(peerRoom))
- return onLeaveRoom$.pipe(
+ impl: ({ fromEvent }) => {
+ const onLeaveRoom$ = fromEvent(JoinRoomEvent).pipe(
+ switchMap(() => callbackToObservable(peerRoom.onLeaveRoom.bind(peerRoom))),
map((peerId) => {
console.log('onLeaveRoom', peerId)
- return [UpdatePeerListCommand({ type: 'delete', peerId }), LeaveRoomEvent(peerId)]
+ return [UpdatePeerListCommand({ type: 'delete', peerId }), OnLeaveRoomEvent(peerId)]
})
)
+ return onLeaveRoom$
}
})
+
return {
query: {
PeerListQuery,
- MessageListQuery
+ MessageListQuery,
+ ...RoomStatusState.query
},
event: {
SendTextMessageEvent,
SendLikeMessageEvent,
SendHateMessageEvent,
JoinRoomEvent,
- LeaveRoomEvent
+ LeaveRoomEvent,
+ OnMessageEvent,
+ OnJoinRoomEvent,
+ OnLeaveRoomEvent,
+ ...RoomStatusState.event
},
command: {
JoinRoomCommand,
LeaveRoomCommand,
SendTextMessageCommand,
SendLikeMessageCommand,
- SendHateMessageCommand
+ SendHateMessageCommand,
+ ...RoomStatusState.command
}
}
}
diff --git a/src/domain/UserInfo.ts b/src/domain/UserInfo.ts
index 42cda85..503134c 100644
--- a/src/domain/UserInfo.ts
+++ b/src/domain/UserInfo.ts
@@ -1,6 +1,7 @@
import { Remesh } from 'remesh'
import { BrowserSyncStorageExtern } from '@/domain/externs/Storage'
import StorageEffect from '@/domain/modules/StorageEffect'
+import StatusModule from './modules/Status'
export interface UserInfo {
id: string
@@ -26,6 +27,10 @@ const UserInfoDomain = Remesh.domain({
default: null
})
+ const UserInfoStatusModule = StatusModule(domain, {
+ name: 'UserInfo.StatusModule'
+ })
+
const UserInfoQuery = domain.query({
name: 'UserInfo.UserInfoQuery',
impl: ({ get }) => {
@@ -33,17 +38,17 @@ const UserInfoDomain = Remesh.domain({
}
})
- const IsLoginQuery = domain.query({
- name: 'UserInfo.IsLoginQuery',
- impl: ({ get }) => {
- return !!get(UserInfoState())?.id
- }
- })
-
const UpdateUserInfoCommand = domain.command({
name: 'UserInfo.UpdateUserInfoCommand',
impl: (_, userInfo: UserInfo | null) => {
- return [UserInfoState().new(userInfo), UpdateUserInfoEvent(), SyncToStorageEvent()]
+ return [
+ UserInfoState().new(userInfo),
+ UpdateUserInfoEvent(),
+ SyncToStorageEvent(),
+ userInfo
+ ? UserInfoStatusModule.command.SetFinishedCommand()
+ : UserInfoStatusModule.command.SetInitialCommand()
+ ]
}
})
@@ -74,21 +79,25 @@ const UserInfoDomain = Remesh.domain({
storageEffect
.set(SyncToStorageEvent)
- .get((value) => SyncToStateCommand(value))
- .watch((value) => SyncToStateCommand(value!))
+ .get((value) => {
+ return [SyncToStateCommand(value), UserInfoStatusModule.command.SetFinishedCommand()]
+ })
+ .watch((value) => [SyncToStateCommand(value)])
return {
query: {
UserInfoQuery,
- IsLoginQuery
+ ...UserInfoStatusModule.query
},
command: {
- UpdateUserInfoCommand
+ UpdateUserInfoCommand,
+ ...UserInfoStatusModule.command
},
event: {
SyncToStateEvent,
SyncToStorageEvent,
- UpdateUserInfoEvent
+ UpdateUserInfoEvent,
+ ...UserInfoStatusModule.event
}
}
}
diff --git a/src/domain/modules/Status.ts b/src/domain/modules/Status.ts
new file mode 100644
index 0000000..f3129a5
--- /dev/null
+++ b/src/domain/modules/Status.ts
@@ -0,0 +1,102 @@
+import { DomainConceptName, RemeshDomainContext } from 'remesh'
+
+export enum Status {
+ Initial = 0b001, // 1
+ Loading = 0b010, // 2
+ Finished = 0b100 // 4
+}
+
+export interface StatusOptions {
+ name: DomainConceptName<'StatusModule'>
+ default?: Status
+}
+
+const StatusModule = (domain: RemeshDomainContext, options: StatusOptions) => {
+ const StatusState = domain.state({
+ name: `${options.name}.StatusState`,
+ default: options.default ?? Status.Initial
+ })
+
+ const StatusQuery = domain.query({
+ name: `${options.name}.StatusQuery`,
+ impl: ({ get }) => {
+ return get(StatusState())
+ }
+ })
+
+ const IsInitialQuery = domain.query({
+ name: `${options.name}.IsInitialQuery`,
+ impl: ({ get }) => {
+ const state = get(StatusState())
+ return (state & Status.Initial) !== 0
+ }
+ })
+
+ const IsLoadingQuery = domain.query({
+ name: `${options.name}.IsLoadingQuery`,
+ impl: ({ get }) => {
+ const state = get(StatusState())
+ return (state & Status.Loading) !== 0
+ }
+ })
+
+ const IsFinishedQuery = domain.query({
+ name: `${options.name}.IsFinishedQuery`,
+ impl: ({ get }) => {
+ const state = get(StatusState())
+ return (state & Status.Finished) !== 0
+ }
+ })
+
+ const UpdateStatusEvent = domain.event({
+ name: `${options.name}.UpdateStatusEvent`
+ })
+
+ const SetInitialCommand = domain.command({
+ name: `${options.name}.SetInitialCommand`,
+ impl: () => {
+ return [StatusState().new(Status.Initial), UpdateStatusEvent(Status.Initial)]
+ }
+ })
+
+ const SetLoadingCommand = domain.command({
+ name: `${options.name}.SetLoadingCommand`,
+ impl: () => {
+ return [StatusState().new(Status.Loading), UpdateStatusEvent(Status.Loading)]
+ }
+ })
+
+ const SetFinishedCommand = domain.command({
+ name: `${options.name}.SetFinishedCommand`,
+ impl: () => {
+ return [StatusState().new(Status.Finished), UpdateStatusEvent(Status.Finished)]
+ }
+ })
+
+ const UpdateStatusCommand = domain.command({
+ name: `${options.name}.UpdateStatusCommand`,
+ impl: (_, status: Status) => {
+ return [StatusState().new(status), UpdateStatusEvent(status)]
+ }
+ })
+
+ return {
+ query: {
+ StatusQuery,
+ IsInitialQuery,
+ IsLoadingQuery,
+ IsFinishedQuery
+ },
+ command: {
+ SetInitialCommand,
+ SetLoadingCommand,
+ SetFinishedCommand,
+ UpdateStatusCommand
+ },
+ event: {
+ UpdateStatusEvent
+ }
+ }
+}
+
+export default StatusModule
diff --git a/src/lib/uglyAvatar/index.js b/src/lib/uglyAvatar/index.js
index 57f4824..43defe0 100644
--- a/src/lib/uglyAvatar/index.js
+++ b/src/lib/uglyAvatar/index.js
@@ -57,8 +57,83 @@ function createAvatarSvg() {
'rgb(165, 42, 42)', // Red
'rgb(145, 85, 61)', // Auburn
'rgb(128, 128, 128)', // Grey
- 'rgb(185, 55, 55)' // Fire
- // ... 其他颜色
+ 'rgb(185, 55, 55)', // Fire
+ 'rgb(255, 192, 203)', // Pastel Pink
+ 'rgb(255, 105, 180)', // Bright Pink
+ 'rgb(230, 230, 250)', // Lavender
+ 'rgb(64, 224, 208)', // Turquoise
+ 'rgb(0, 191, 255)', // Bright Blue
+ 'rgb(148, 0, 211)', // Deep Purple
+ 'rgb(50, 205, 50)', // Lime Green
+ 'rgb(255, 165, 0)', // Vivid Orange
+ 'rgb(220, 20, 60)', // Crimson Red
+ 'rgb(192, 192, 192)', // Silver
+ 'rgb(255, 215, 0)', // Gold
+ 'rgb(255, 255, 255)', // White
+ 'rgb(124, 252, 0)', // Lawn Green
+ 'rgb(127, 255, 0)', // Chartreuse
+ 'rgb(0, 255, 127)', // Spring Green
+ 'rgb(72, 209, 204)', // Medium Turquoise
+ 'rgb(0, 255, 255)', // Cyan
+ 'rgb(0, 206, 209)', // Dark Turquoise
+ 'rgb(32, 178, 170)', // Light Sea Green
+ 'rgb(95, 158, 160)', // Cadet Blue
+ 'rgb(70, 130, 180)', // Steel Blue
+ 'rgb(176, 196, 222)', // Light Steel Blue
+ 'rgb(30, 144, 255)', // Dodger Blue
+ 'rgb(135, 206, 235)', // Sky Blue
+ 'rgb(0, 0, 139)', // Dark Blue
+ 'rgb(138, 43, 226)', // Blue Violet
+ 'rgb(75, 0, 130)', // Indigo
+ 'rgb(139, 0, 139)', // Dark Magenta
+ 'rgb(153, 50, 204)', // Dark Orchid
+ 'rgb(186, 85, 211)', // Medium Orchid
+ 'rgb(218, 112, 214)', // Orchid
+ 'rgb(221, 160, 221)', // Plum
+ 'rgb(238, 130, 238)', // Violet
+ 'rgb(255, 0, 255)', // Magenta
+ 'rgb(216, 191, 216)', // Thistle
+ 'rgb(255, 20, 147)', // Deep Pink
+ 'rgb(255, 69, 0)', // Orange Red
+ 'rgb(255, 140, 0)', // Dark Orange
+ 'rgb(255, 165, 0)', // Orange
+ 'rgb(250, 128, 114)', // Salmon
+ 'rgb(233, 150, 122)', // Dark Salmon
+ 'rgb(240, 128, 128)', // Light Coral
+ 'rgb(205, 92, 92)', // Indian Red
+ 'rgb(255, 99, 71)', // Tomato
+ 'rgb(255, 160, 122)', // Light Salmon
+ 'rgb(220, 20, 60)', // Crimson
+ 'rgb(139, 0, 0)', // Dark Red
+ 'rgb(178, 34, 34)', // Fire Brick
+ 'rgb(250, 235, 215)', // Antique White
+ 'rgb(255, 239, 213)', // Papaya Whip
+ 'rgb(255, 235, 205)', // Blanched Almond
+ 'rgb(255, 222, 173)', // Navajo White
+ 'rgb(245, 245, 220)', // Beige
+ 'rgb(255, 228, 196)', // Bisque
+ 'rgb(255, 218, 185)', // Peach Puff
+ 'rgb(244, 164, 96)', // Sandy Brown
+ 'rgb(210, 180, 140)', // Tan
+ 'rgb(222, 184, 135)', // Burly Wood
+ 'rgb(250, 250, 210)', // Light Goldenrod Yellow
+ 'rgb(255, 250, 205)', // Lemon Chiffon
+ 'rgb(255, 245, 238)', // Sea Shell
+ 'rgb(253, 245, 230)', // Old Lace
+ 'rgb(255, 228, 225)', // Misty Rose
+ 'rgb(255, 240, 245)', // Lavender Blush
+ 'rgb(250, 240, 230)', // Linen
+ 'rgb(255, 228, 181)', // Moccasin
+ 'rgb(255, 250, 250)', // Snow
+ 'rgb(240, 255, 255)', // Azure
+ 'rgb(240, 255, 240)', // Honeydew
+ 'rgb(245, 245, 245)', // White Smoke
+ 'rgb(245, 255, 250)', // Mint Cream
+ 'rgb(240, 248, 255)', // Alice Blue
+ 'rgb(240, 248, 255)', // Ghost White
+ 'rgb(248, 248, 255)', // Ghost White
+ 'rgb(255, 250, 240)', // Floral White
+ 'rgb(253, 245, 230)' // Old Lace
],
hairColor: 'black',
dyeColorOffset: '50%',
@@ -72,8 +147,54 @@ function createAvatarSvg() {
'rgb(188, 143, 143)', // Dusty Rose
'rgb(135, 206, 235)', // Sky Blue
'rgb(245, 255, 250)', // Mint Cream
- 'rgb(245, 222, 179)' // Wheat
- // ... 其他颜色
+ 'rgb(245, 222, 179)', // Wheat
+ 'rgb(47, 79, 79)', // Dark Slate Gray
+ 'rgb(72, 61, 139)', // Dark Slate Blue
+ 'rgb(60, 20, 20)', // Dark Brown
+ 'rgb(25, 25, 112)', // Midnight Blue
+ 'rgb(139, 0, 0)', // Dark Red
+ 'rgb(85, 107, 47)', // Olive Drab
+ 'rgb(128, 0, 128)', // Purple
+ 'rgb(0, 100, 0)', // Dark Green
+ 'rgb(0, 0, 139)', // Dark Blue
+ 'rgb(105, 105, 105)', // Dim Gray
+ 'rgb(240, 128, 128)', // Light Coral
+ 'rgb(255, 160, 122)', // Light Salmon
+ 'rgb(255, 218, 185)', // Peach Puff
+ 'rgb(255, 228, 196)', // Bisque
+ 'rgb(255, 222, 173)', // Navajo White
+ 'rgb(255, 250, 205)', // Lemon Chiffon
+ 'rgb(250, 250, 210)', // Light Goldenrod Yellow
+ 'rgb(255, 239, 213)', // Papaya Whip
+ 'rgb(255, 245, 238)', // Sea Shell
+ 'rgb(255, 248, 220)', // Cornsilk
+ 'rgb(255, 255, 240)', // Ivory
+ 'rgb(240, 255, 240)', // Honeydew
+ 'rgb(240, 255, 255)', // Azure
+ 'rgb(240, 248, 255)', // Alice Blue
+ 'rgb(248, 248, 255)', // Ghost White
+ 'rgb(255, 250, 250)', // Snow
+ 'rgb(255, 240, 245)', // Lavender Blush
+ 'rgb(255, 228, 225)', // Misty Rose
+ 'rgb(230, 230, 250)', // Lavender
+ 'rgb(216, 191, 216)', // Thistle
+ 'rgb(221, 160, 221)', // Plum
+ 'rgb(238, 130, 238)', // Violet
+ 'rgb(218, 112, 214)', // Orchid
+ 'rgb(186, 85, 211)', // Medium Orchid
+ 'rgb(147, 112, 219)', // Medium Purple
+ 'rgb(138, 43, 226)', // Blue Violet
+ 'rgb(148, 0, 211)', // Dark Violet
+ 'rgb(153, 50, 204)', // Dark Orchid
+ 'rgb(139, 69, 19)', // Saddle Brown
+ 'rgb(160, 82, 45)', // Sienna
+ 'rgb(210, 105, 30)', // Chocolate
+ 'rgb(205, 133, 63)', // Peru
+ 'rgb(244, 164, 96)', // Sandy Brown
+ 'rgb(222, 184, 135)', // Burly Wood
+ 'rgb(255, 250, 240)', // Floral White
+ 'rgb(253, 245, 230)', // Old Lace
+ 'rgb(250, 240, 230)' // Linen
],
mouthPoints: []
}
@@ -250,7 +371,6 @@ function createAvatarSvg() {
return createElement(svgTemplate)
}
-// 导出函数
export default function generateUglyAvatar() {
const svgElement = createAvatarSvg()
const serializer = new XMLSerializer()