Refactor frontend workspaces and centralize API contracts
Organize domain APIs and types under common/apis, shared components under named folders, and private workspace logic alongside each page. Use Bearer sessions, explicit response envelopes, session-aware routing and streams, and configurable login captcha presentation for the deployed backend contract. Preserve news, economic calendar, crawler, market, chat, Skills, settings, and demo trading workflows with responsive layouts and explicit configuration saves. Validate with frontend lint, type checking, 22 test groups, production builds, and deployed desktop/mobile page checks.
This commit is contained in:
1 parent
77b33e5e77
commit
bc480fb806
102 files changed
+4716
-1707
No files matched your search
@@ -0,0 +1,31 @@
|
||||
import { listSourcesApi } from "@@/apis/crawler"
|
||||
import { defineStore } from "pinia"
|
||||
import { ref, watch } from "vue"
|
||||
import { useUserStore } from "@/pinia/stores/user"
|
||||
|
||||
export const useSourcesStore = defineStore("sources", () => {
|
||||
const names = ref<Record<string, string>>({})
|
||||
const user = useUserStore()
|
||||
let loading: Promise<void> | undefined
|
||||
watch(() => user.sessionVersion, () => {
|
||||
names.value = {}
|
||||
loading = undefined
|
||||
}, { flush: "sync" })
|
||||
|
||||
function load() {
|
||||
if (Object.keys(names.value).length)
|
||||
return Promise.resolve()
|
||||
if (!loading) {
|
||||
const version = user.sessionVersion
|
||||
loading = listSourcesApi().then((info) => {
|
||||
if (version === user.sessionVersion)
|
||||
names.value = Object.fromEntries(info.sources.map(s => [s.code, s.name]))
|
||||
}).finally(() => {
|
||||
if (version === user.sessionVersion)
|
||||
loading = undefined
|
||||
})
|
||||
}
|
||||
return loading
|
||||
}
|
||||
return { names, load }
|
||||
})
|
||||
@@ -0,0 +1,53 @@
|
||||
import type { Identity } from "@@/apis/auth/type"
|
||||
import { getIdentityApi, revokeSessionApi } from "@@/apis/auth"
|
||||
import { CanceledError } from "axios"
|
||||
import { defineStore } from "pinia"
|
||||
import { computed, ref } from "vue"
|
||||
|
||||
export const TOKEN_KEY = "alphapulse.auth.token"
|
||||
|
||||
export const useUserStore = defineStore("user", () => {
|
||||
const token = ref(localStorage.getItem(TOKEN_KEY) ?? "")
|
||||
const sessionVersion = ref(0)
|
||||
const identity = ref<Identity>()
|
||||
const username = computed(() => identity.value?.username)
|
||||
let restoring: Promise<void> | undefined
|
||||
|
||||
function setToken(value: string, persist = true) {
|
||||
sessionVersion.value++
|
||||
token.value = value
|
||||
identity.value = undefined
|
||||
restoring = undefined
|
||||
if (persist) {
|
||||
if (value)
|
||||
localStorage.setItem(TOKEN_KEY, value)
|
||||
else
|
||||
localStorage.removeItem(TOKEN_KEY)
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchMe() {
|
||||
if (!restoring) {
|
||||
const version = sessionVersion.value
|
||||
restoring = getIdentityApi().then((me) => {
|
||||
if (version !== sessionVersion.value)
|
||||
throw new CanceledError("会话已切换")
|
||||
identity.value = me
|
||||
}).finally(() => {
|
||||
if (version === sessionVersion.value)
|
||||
restoring = undefined
|
||||
})
|
||||
}
|
||||
return restoring
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
const version = sessionVersion.value
|
||||
await revokeSessionApi()
|
||||
if (version !== sessionVersion.value)
|
||||
throw new CanceledError("会话已切换")
|
||||
setToken("")
|
||||
}
|
||||
|
||||
return { token, sessionVersion, identity, username, setToken, fetchMe, logout }
|
||||
})
|
||||
Reference in new issue
Block a user