Commit b4f8fe7e authored by Chunchi Che's avatar Chunchi Che

Merge branch 'optimize/online-deck' into 'main'

优化一部分在线卡组功能

See merge request !383
parents 5d3048b6 790d6d90
Pipeline #27796 passed with stages
in 7 minutes and 54 seconds
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
"classnames": "^2.3.2", "classnames": "^2.3.2",
"cookies-ts": "^1.0.5", "cookies-ts": "^1.0.5",
"eventemitter3": "^5.0.1", "eventemitter3": "^5.0.1",
"fuse.js": "^7.0.0",
"google-protobuf": "^3.21.2", "google-protobuf": "^3.21.2",
"i18next": "^23.11.4", "i18next": "^23.11.4",
"idb-keyval": "^6.2.1", "idb-keyval": "^6.2.1",
...@@ -3747,6 +3748,14 @@ ...@@ -3747,6 +3748,14 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/fuse.js": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.0.0.tgz",
"integrity": "sha512-14F4hBIxqKvD4Zz/XjDc3y94mNZN6pRv3U13Udo0lNLCWRBUsrMv2xwcF/y/Z5sV6+FQW+/ow68cHpm4sunt8Q==",
"engines": {
"node": ">=10"
}
},
"node_modules/gensync": { "node_modules/gensync": {
"version": "1.0.0-beta.2", "version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
...@@ -9671,6 +9680,11 @@ ...@@ -9671,6 +9680,11 @@
"dev": true, "dev": true,
"peer": true "peer": true
}, },
"fuse.js": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.0.0.tgz",
"integrity": "sha512-14F4hBIxqKvD4Zz/XjDc3y94mNZN6pRv3U13Udo0lNLCWRBUsrMv2xwcF/y/Z5sV6+FQW+/ow68cHpm4sunt8Q=="
},
"gensync": { "gensync": {
"version": "1.0.0-beta.2", "version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
......
import { useConfig } from "@/config";
import { MdproResp } from "./schema";
import { handleHttps, mdproHeaders } from "./util";
const { mdproServer } = useConfig();
const API_PATH = "/api/mdpro3/sync/single";
interface DeleteReq {
userId: number;
deck: {
deckId: string;
isDelete: boolean;
};
}
export async function deleteDeck(
userID: number,
token: string,
deckID: string,
): Promise<MdproResp<boolean> | undefined> {
const myHeaders = mdproHeaders();
myHeaders.append("Content-Type", "application/json");
myHeaders.append("token", token);
const req: DeleteReq = {
userId: userID,
deck: {
deckId: deckID,
isDelete: true,
},
};
const resp = await fetch(`${mdproServer}/${API_PATH}`, {
method: "POST",
headers: myHeaders,
body: JSON.stringify(req),
redirect: "follow",
});
return await handleHttps(resp, API_PATH);
}
import { useConfig } from "@/config";
import { MdproResp } from "./schema";
import { handleHttps, mdproHeaders } from "./util";
const { mdproServer } = useConfig();
const API_PATH = "/api/mdpro3/deck/deckId";
export async function generateDeck(): Promise<MdproResp<string> | undefined> {
const myHeaders = mdproHeaders();
const resp = await fetch(`${mdproServer}/${API_PATH}`, {
method: "GET",
headers: myHeaders,
redirect: "follow",
});
return await handleHttps(resp, API_PATH);
}
export * from "./delete";
export * from "./generate";
export * from "./mget"; export * from "./mget";
export * from "./personalList";
export * from "./pull"; export * from "./pull";
export * from "./update"; export * from "./sync";
export * from "./updatePulibc";
export * from "./upload"; export * from "./upload";
import { useConfig } from "@/config"; import { useConfig } from "@/config";
import { MdproDeck } from "./schema"; import { MdproDeck, MdproResp } from "./schema";
import { mdproHeaders } from "./util"; import { handleHttps, mdproHeaders } from "./util";
const { mdproServer } = useConfig(); const { mdproServer } = useConfig();
const API_PATH = "/api/mdpro3/deck"; const API_PATH = "/api/mdpro3/deck";
interface MgetResp { export async function mgetDeck(
code: number; id: string,
message: string; ): Promise<MdproResp<MdproDeck> | undefined> {
data?: MdproDeck;
}
export async function mgetDeck(id: string): Promise<MgetResp | undefined> {
const myHeaders = mdproHeaders(); const myHeaders = mdproHeaders();
const resp = await fetch(`${mdproServer}/${API_PATH}/${id}`, { const resp = await fetch(`${mdproServer}/${API_PATH}/${id}`, {
...@@ -22,10 +18,5 @@ export async function mgetDeck(id: string): Promise<MgetResp | undefined> { ...@@ -22,10 +18,5 @@ export async function mgetDeck(id: string): Promise<MgetResp | undefined> {
redirect: "follow", redirect: "follow",
}); });
if (!resp.ok) { return await handleHttps(resp, API_PATH);
console.error(`[Mget of Mdpro Decks] HTTPS error! status: ${resp.status}`);
return undefined;
} else {
return await resp.json();
}
} }
import { useConfig } from "@/config";
import { MdproDeck, MdproResp } from "./schema";
import { handleHttps, mdproHeaders } from "./util";
const { mdproServer } = useConfig();
const API_PATH = "/api/mdpro3/sync/";
export interface PersonalListReq {
/* ID of MyCard Account */
userID: number;
/* Token of MyCard Account */
token: string;
}
export async function getPersonalList(
req: PersonalListReq,
): Promise<MdproResp<MdproDeck[]> | undefined> {
const myHeaders = mdproHeaders();
myHeaders.append("token", req.token);
const resp = await fetch(`${mdproServer}/${API_PATH}/${req.userID}`, {
method: "GET",
headers: myHeaders,
redirect: "follow",
});
return await handleHttps(resp, API_PATH);
}
import { useConfig } from "@/config"; import { useConfig } from "@/config";
import { MdproDeck } from "./schema"; import { MdproDeckLike, MdproResp } from "./schema";
import { mdproHeaders } from "./util"; import { handleHttps, mdproHeaders } from "./util";
const { mdproServer } = useConfig(); const { mdproServer } = useConfig();
const API_PATH = "api/mdpro3/deck/list"; const API_PATH = "api/mdpro3/deck/list";
...@@ -20,21 +20,17 @@ const defaultPullReq: PullReq = { ...@@ -20,21 +20,17 @@ const defaultPullReq: PullReq = {
size: 20, size: 20,
}; };
interface PullResp { interface RespData {
code: number; current: number;
message: string; size: number;
data?: { total: number;
current: number; pages: number;
size: number; records: MdproDeckLike[];
total: number;
pages: number;
records: MdproDeck[];
};
} }
export async function pullDecks( export async function pullDecks(
req: PullReq = defaultPullReq, req: PullReq = defaultPullReq,
): Promise<PullResp | undefined> { ): Promise<MdproResp<RespData> | undefined> {
const myHeaders = mdproHeaders(); const myHeaders = mdproHeaders();
const params = new URLSearchParams(); const params = new URLSearchParams();
...@@ -53,10 +49,5 @@ export async function pullDecks( ...@@ -53,10 +49,5 @@ export async function pullDecks(
redirect: "follow", redirect: "follow",
}); });
if (!resp.ok) { return await handleHttps(resp, API_PATH);
console.error(`[Pull of Mdpro Decks] HTTPS error! status: ${resp.status}`);
return undefined;
} else {
return await resp.json();
}
} }
export interface MdproResp<T> {
code: number;
message: string;
data?: T;
}
export interface MdproDeck { export interface MdproDeck {
/* /*
*`ID` of the online deck. *`ID` of the online deck.
...@@ -18,4 +24,15 @@ export interface MdproDeck { ...@@ -18,4 +24,15 @@ export interface MdproDeck {
/* Content of the deck. */ /* Content of the deck. */
deckYdk?: string; deckYdk?: string;
deckCase: number; deckCase: number;
/* User ID of MyCard Account */
userId: number;
}
export interface MdproDeckLike {
deckId: string;
deckContributor: string;
deckName: string;
deckLike?: number;
deckCase: number;
lastDate?: string;
} }
import { useConfig } from "@/config";
import { MdproResp } from "./schema";
import { handleHttps, mdproHeaders } from "./util";
const { mdproServer } = useConfig();
const API_PATH = "/api/mdpro3/sync/single";
export interface SyncReq {
userId: number;
deckContributor: string;
deck: {
deckId: string;
deckName: string;
deckCase: number;
deckYdk: string;
};
}
export async function syncDeck(
req: SyncReq,
token: string,
): Promise<MdproResp<boolean> | undefined> {
const myHeaders = mdproHeaders();
myHeaders.append("Content-Type", "application/json");
myHeaders.append("token", token);
const resp = await fetch(`${mdproServer}/${API_PATH}`, {
method: "POST",
headers: myHeaders,
body: JSON.stringify(req),
redirect: "follow",
});
return await handleHttps(resp, API_PATH);
}
import { useConfig } from "@/config"; import { useConfig } from "@/config";
import { MdproDeck } from "./schema"; import { MdproResp } from "./schema";
import { mdproHeaders } from "./util"; import { handleHttps, mdproHeaders } from "./util";
const { mdproServer } = useConfig(); const { mdproServer } = useConfig();
const API_PATH = "api/mdpro3/deck/update"; const API_PATH = "/api/mdpro3/deck/public";
interface UpdateResp { export interface UpdatePublicReq {
code: number; userId: number;
message: string; deckId: string;
data: MdproDeck; isPublic: boolean;
} }
export async function updateDeck( export async function updatePublic(
req: MdproDeck, req: UpdatePublicReq,
): Promise<UpdateResp | undefined> { token: string,
): Promise<MdproResp<void> | undefined> {
const myHeaders = mdproHeaders(); const myHeaders = mdproHeaders();
myHeaders.append("Content-Type", "application/json");
myHeaders.append("token", token);
const resp = await fetch(`${mdproServer}/${API_PATH}`, { const resp = await fetch(`${mdproServer}/${API_PATH}`, {
method: "PUT", method: "POST",
headers: myHeaders, headers: myHeaders,
body: JSON.stringify(req), body: JSON.stringify(req),
redirect: "follow", redirect: "follow",
}); });
if (!resp.ok) { return await handleHttps(resp, API_PATH);
console.error(`[Update of MdproDeck] HTTPS error! status: ${resp.status}`);
return undefined;
} else {
return await resp.json();
}
} }
import { useConfig } from "@/config"; import { generateDeck } from "./generate";
import { MdproResp } from "./schema";
import { syncDeck } from "./sync";
import { updatePublic } from "./updatePulibc";
import { MdproDeck } from "./schema"; export interface UploadReq {
import { mdproHeaders } from "./util"; userId: number;
token: string;
const { mdproServer } = useConfig(); deckContributor: string;
const API_PATH = "api/mdpro3/deck/upload"; deck: {
deckName: string;
interface UploadResp { deckCase: number;
code: number; deckYdk: string;
message: string; };
data: MdproDeck;
} }
export async function uploadDeck( export async function uploadDeck(
req: MdproDeck, req: UploadReq,
): Promise<UploadResp | undefined> { ): Promise<MdproResp<void> | undefined> {
const myHeaders = mdproHeaders(); const generateResp = await generateDeck();
myHeaders.append("Content-Type", "application/json"); if (generateResp === undefined) return undefined;
const resp = await fetch(`${mdproServer}/${API_PATH}`, { if (generateResp.code !== 0 || generateResp.data === undefined)
method: "POST", return { code: generateResp.code, message: generateResp.message };
headers: myHeaders,
body: JSON.stringify(req), const deckId = generateResp.data;
redirect: "follow",
}); const syncRes = await syncDeck(
{
if (!resp.ok) { userId: req.userId,
console.error( deckContributor: req.deckContributor,
`[Upload of Mdpro Decks] HTTPS error! status: ${resp.status}`, deck: {
deckId,
deckName: req.deck.deckName,
deckCase: req.deck.deckCase,
deckYdk: req.deck.deckYdk,
},
},
req.token,
);
if (syncRes === undefined) return undefined;
if (syncRes.code === 0 && syncRes.data === true) {
// succeed in syncing
return await updatePublic(
{
userId: req.userId,
deckId,
isPublic: true,
},
req.token,
); );
return undefined;
} else { } else {
return await resp.json(); return { code: syncRes.code, message: syncRes.message };
} }
} }
...@@ -4,3 +4,17 @@ export function mdproHeaders(): Headers { ...@@ -4,3 +4,17 @@ export function mdproHeaders(): Headers {
return myHeaders; return myHeaders;
} }
export async function handleHttps<T>(
resp: Response,
api: string,
): Promise<T | undefined> {
if (!resp.ok) {
console.error(
`[Mdpro] Https error from api ${api}! status: ${resp.status}`,
);
return undefined;
} else {
return await resp.json();
}
}
...@@ -3,7 +3,7 @@ import { proxy } from "valtio"; ...@@ -3,7 +3,7 @@ import { proxy } from "valtio";
import { type NeosStore } from "./shared"; import { type NeosStore } from "./shared";
export interface User { export interface User {
id: string; id: number;
username: string; username: string;
name: string; name: string;
email: string; email: string;
......
import { message, Pagination } from "antd"; import { App, Dropdown, message, Pagination } from "antd";
import { MessageInstance } from "antd/es/message/interface";
import Fuse from "fuse.js";
import React, { memo, useEffect } from "react"; import React, { memo, useEffect } from "react";
import { type INTERNAL_Snapshot as Snapshot, proxy, useSnapshot } from "valtio"; import { type INTERNAL_Snapshot as Snapshot, proxy, useSnapshot } from "valtio";
import YGOProDeck from "ygopro-deck-encode"; import YGOProDeck from "ygopro-deck-encode";
import { mgetDeck, pullDecks } from "@/api"; import { deleteDeck, getPersonalList, mgetDeck, pullDecks } from "@/api";
import { MdproDeck } from "@/api/mdproDeck/schema"; import { MdproDeckLike } from "@/api/mdproDeck/schema";
import { useConfig } from "@/config"; import { useConfig } from "@/config";
import { accountStore } from "@/stores";
import { IconFont } from "@/ui/Shared"; import { IconFont } from "@/ui/Shared";
import { setSelectedDeck } from "../.."; import { setSelectedDeck } from "../..";
...@@ -18,39 +21,53 @@ const { assetsPath } = useConfig(); ...@@ -18,39 +21,53 @@ const { assetsPath } = useConfig();
interface Props { interface Props {
query: string; query: string;
page: number; page: number;
decks: MdproDeck[]; decks: MdproDeckLike[];
total: number; total: number;
onlyMine: boolean;
} }
// TODO: useConfig // TODO: useConfig
const PAGE_SIZE = 30; const PAGE_SIZE = 30;
const SORT_LIKE = true; const SORT_LIKE = true;
const store = proxy<Props>({ query: "", page: 1, decks: [], total: 0 }); const store = proxy<Props>({
query: "",
page: 1,
decks: [],
total: 0,
onlyMine: false,
});
export const DeckResults: React.FC = memo(() => { export const DeckResults: React.FC = memo(() => {
const snap = useSnapshot(store); const snap = useSnapshot(store);
const { message } = App.useApp();
useEffect(() => { useEffect(() => {
const update = async () => { if (snap.onlyMine) {
const resp = await pullDecks({ // show only decks uploaded by myself
page: snap.page,
size: PAGE_SIZE, updatePersonalList(message);
keyWord: snap.query !== "" ? snap.query : undefined, } else {
sortLike: SORT_LIKE, const update = async () => {
}); const resp = await pullDecks({
page: snap.page,
if (resp?.data) { size: PAGE_SIZE,
const { total, records: newDecks } = resp.data; keyWord: snap.query !== "" ? snap.query : undefined,
store.total = total; sortLike: SORT_LIKE,
store.decks = newDecks; });
} else {
store.decks = []; if (resp?.data) {
} const { total, records: newDecks } = resp.data;
}; store.total = total;
store.decks = newDecks;
} else {
store.decks = [];
}
};
update(); update();
}, [snap.query, snap.page]); }
}, [snap.query, snap.page, snap.onlyMine]);
const onChangePage = async (page: number) => { const onChangePage = async (page: number) => {
const resp = await pullDecks({ const resp = await pullDecks({
...@@ -78,7 +95,11 @@ export const DeckResults: React.FC = memo(() => { ...@@ -78,7 +95,11 @@ export const DeckResults: React.FC = memo(() => {
<div className={styles.container}> <div className={styles.container}>
<div className={styles["search-decks"]}> <div className={styles["search-decks"]}>
{snap.decks.map((deck) => ( {snap.decks.map((deck) => (
<MdproDeckBlock key={deck.deckId} {...deck} /> <MdproDeckBlock
key={deck.deckId}
deck={deck}
onlyMine={snap.onlyMine}
/>
))} ))}
</div> </div>
<div style={{ textAlign: "center", padding: "0.625rem 0 1.25rem" }}> <div style={{ textAlign: "center", padding: "0.625rem 0 1.25rem" }}>
...@@ -102,24 +123,116 @@ export const DeckResults: React.FC = memo(() => { ...@@ -102,24 +123,116 @@ export const DeckResults: React.FC = memo(() => {
); );
}); });
const MdproDeckBlock: React.FC<Snapshot<MdproDeck>> = (deck) => ( const MdproDeckBlock: React.FC<{
<div deck: Snapshot<MdproDeckLike>;
className={styles["mdpro-deck"]} onlyMine: boolean;
onClick={async () => await copyMdproDeckToEditing(deck)} }> = ({ deck, onlyMine }) => {
> const { message } = App.useApp();
<img const user = accountStore.user;
src={`${assetsPath}/deck-cases/DeckCase${deck.deckCase
.toString() const onDelete = async () => {
.slice(-4)}_L.png`} if (user) {
/> const resp = await deleteDeck(user.id, user.token, deck.deckId);
<div className={styles.text}>
<div>{truncateString(deck.deckName, 8)}</div> if (resp?.code === 0 && resp.data === true) {
<div>{`By ${truncateString(deck.deckContributor, 6)}`}</div> message.success(
</div> "删除卡组成功,由于缓存的原因请稍等片刻后重新刷新页面。",
</div> );
);
// fresh when deletion succeed
const copyMdproDeckToEditing = async (mdproDeck: MdproDeck) => { await updatePersonalList(message);
} else if (resp !== undefined && resp.message !== "") {
message.error(resp.message);
} else {
message.error("删除卡组失败,请检查自己的网络状况。");
}
} else {
message.error("需要先登录萌卡才能删除卡组。");
}
};
const items = [];
if (onlyMine) {
items.push({ key: 0, label: "删除", danger: true, onClick: onDelete });
}
return (
<Dropdown
menu={{
items,
}}
trigger={["contextMenu"]}
>
<div
className={styles["mdpro-deck"]}
onClick={async () => await copyMdproDeckToEditing(deck)}
>
<img
src={`${assetsPath}/deck-cases/DeckCase${deck.deckCase
.toString()
.slice(-4)}_L.png`}
/>
<div className={styles.text}>
<div>{truncateString(deck.deckName, 8)}</div>
<div>{`By ${truncateString(deck.deckContributor, 6)}`}</div>
</div>
</div>
</Dropdown>
);
};
const updatePersonalList = async (message: MessageInstance) => {
const user = accountStore.user;
if (user) {
const resp = await getPersonalList({
userID: user.id,
token: user.token,
});
if (resp) {
if (resp.code !== 0 || resp.data === undefined) {
message.error(resp.message);
} else {
let decks = resp.data;
if (store.query !== "") {
// use `fuse.js` to search
const fuse = new Fuse(decks, {
keys: ["deckName"],
includeScore: true,
threshold: 0.3,
});
const results = fuse.search(store.query);
decks = results.map((result) => result.item);
}
const total = decks.length;
store.total = total;
if (total === 0) {
store.page = 1;
store.decks = [];
} else {
if (total <= (store.page - 1) * PAGE_SIZE)
store.page = Math.floor((total - 1) / PAGE_SIZE) + 1;
store.decks = decks.slice(
(store.page - 1) * PAGE_SIZE,
store.page * PAGE_SIZE,
);
}
}
} else {
message.error("获取个人卡组列表失败,请检查自己的网络状况。");
}
} else {
message.error("需要先登录萌卡账号才能查看自己的在线卡组");
// set to default
store.page = 1;
store.decks = [];
store.total = 0;
}
};
const copyMdproDeckToEditing = async (mdproDeck: MdproDeckLike) => {
// currently the content of the deck, which we named `Ydk`, // currently the content of the deck, which we named `Ydk`,
// haven't been downloaded, so we need to fetch from server again by `mgetDeck` // haven't been downloaded, so we need to fetch from server again by `mgetDeck`
// API. // API.
...@@ -160,4 +273,8 @@ function truncateString(str: string, maxLen: number): string { ...@@ -160,4 +273,8 @@ function truncateString(str: string, maxLen: number): string {
return `${start}...${end}`; return `${start}...${end}`;
} }
export const freshMdrpoDecks = (query: string) => (store.query = query); export const freshMdrpoDecks = (query: string, onlyMine?: boolean) => {
store.query = query;
if (onlyMine !== undefined) store.onlyMine = onlyMine;
};
...@@ -15,7 +15,7 @@ import { useDrop } from "react-dnd"; ...@@ -15,7 +15,7 @@ import { useDrop } from "react-dnd";
import { CardMeta, searchCards } from "@/api"; import { CardMeta, searchCards } from "@/api";
import { isToken } from "@/common"; import { isToken } from "@/common";
import { emptySearchConditions, FtsConditions } from "@/middleware/sqlite/fts"; import { emptySearchConditions, FtsConditions } from "@/middleware/sqlite/fts";
import { ScrollableArea, Type } from "@/ui/Shared"; import { ScrollableArea, Select, Type } from "@/ui/Shared";
import { Filter } from "../Filter"; import { Filter } from "../Filter";
import styles from "../index.module.scss"; import styles from "../index.module.scss";
...@@ -149,19 +149,35 @@ export const DeckDatabase: React.FC = () => { ...@@ -149,19 +149,35 @@ export const DeckDatabase: React.FC = () => {
</Button> </Button>
</Space> </Space>
<div className={styles["select-btns"]}> <div className={styles["select-btns"]}>
<Button {showMdproDecks ? (
block <Select
type={ title=""
isEqual(emptySearchConditions, searchConditions) style={{ width: "18.90rem" }}
? "text" defaultValue={false}
: "primary" options={[
} { value: true, label: "只显示我上传的卡组" },
disabled={showMdproDecks} { value: false, label: "显示全部在线卡组" },
icon={<FilterOutlined />} ]}
onClick={showFilterModal} onChange={
> // @ts-ignore
筛选 (value) => freshMdrpoDecks(searchWord, value)
</Button> }
/>
) : (
<Button
block
type={
isEqual(emptySearchConditions, searchConditions)
? "text"
: "primary"
}
disabled={showMdproDecks}
icon={<FilterOutlined />}
onClick={showFilterModal}
>
筛选
</Button>
)}
<Dropdown <Dropdown
menu={{ items: dropdownOptions }} menu={{ items: dropdownOptions }}
disabled={showMdproDecks} disabled={showMdproDecks}
......
...@@ -11,7 +11,6 @@ import React, { useRef, useState } from "react"; ...@@ -11,7 +11,6 @@ import React, { useRef, useState } from "react";
import YGOProDeck from "ygopro-deck-encode"; import YGOProDeck from "ygopro-deck-encode";
import { uploadDeck } from "@/api"; import { uploadDeck } from "@/api";
import { MdproDeck } from "@/api/mdproDeck/schema";
import { accountStore, deckStore, IDeck } from "@/stores"; import { accountStore, deckStore, IDeck } from "@/stores";
import { Uploader } from "../../Shared"; import { Uploader } from "../../Shared";
...@@ -130,15 +129,16 @@ export const DeckSelect: React.FC<{ ...@@ -130,15 +129,16 @@ export const DeckSelect: React.FC<{
const user = accountStore.user; const user = accountStore.user;
if (user) { if (user) {
// TODO: Deck Case // TODO: Deck Case
const mdproDeck: MdproDeck = { const resp = await uploadDeck({
deckId: "", userId: user.id,
token: user.token,
deckContributor: user.username, deckContributor: user.username,
deckName: deck.deckName, deck: {
deckYdk: genYdkText(deck), deckName: deck.deckName,
deckCase: DEFAULT_DECK_CASE, deckCase: DEFAULT_DECK_CASE,
}; deckYdk: genYdkText(deck),
},
const resp = await uploadDeck(mdproDeck); });
if (resp) { if (resp) {
if (resp.code) { if (resp.code) {
message.error(resp.message); message.error(resp.message);
......
...@@ -82,6 +82,8 @@ export const handleSSOLogin = async (search: string) => { ...@@ -82,6 +82,8 @@ export const handleSSOLogin = async (search: string) => {
const sso = new URLSearchParams(search).get("sso"); const sso = new URLSearchParams(search).get("sso");
const user = sso ? getSSOUser(new URLSearchParams(atob(sso))) : undefined; const user = sso ? getSSOUser(new URLSearchParams(atob(sso))) : undefined;
if (user) { if (user) {
// Convert userID to [`Number`] here
user.id = Number(user.id);
accountStore.login(user); accountStore.login(user);
setCookie(CookieKeys.USER, JSON.stringify(user)); setCookie(CookieKeys.USER, JSON.stringify(user));
// TODO: toast显示登录成功 // TODO: toast显示登录成功
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment