更新首页列表
This commit is contained in:
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "oj-next",
|
"name": "oj-next",
|
||||||
"version": "0.0.1",
|
"version": "1.0.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "oj-next",
|
"name": "oj-next",
|
||||||
"version": "0.0.1",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/lang-cpp": "^6.0.2",
|
"@codemirror/lang-cpp": "^6.0.2",
|
||||||
"@codemirror/lang-python": "^6.1.3",
|
"@codemirror/lang-python": "^6.1.3",
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "oj-next",
|
"name": "oj-next",
|
||||||
"private": true,
|
"version": "1.0.0",
|
||||||
"version": "0.0.1",
|
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "vite",
|
"start": "vite",
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export async function getProblemList(
|
|||||||
limit = 10,
|
limit = 10,
|
||||||
keyword: string,
|
keyword: string,
|
||||||
contestID?: string,
|
contestID?: string,
|
||||||
ruleType?: "ACM" | "OI"
|
ruleType?: "ACM" | "OI",
|
||||||
) {
|
) {
|
||||||
const endpoint = !!contestID ? "admin/contest/problem" : "admin/problem"
|
const endpoint = !!contestID ? "admin/contest/problem" : "admin/problem"
|
||||||
const res = await http.get(endpoint, {
|
const res = await http.get(endpoint, {
|
||||||
@@ -143,7 +143,7 @@ export function getContest(id: string) {
|
|||||||
export function addProblemForContest(
|
export function addProblemForContest(
|
||||||
contestID: string,
|
contestID: string,
|
||||||
problemID: number,
|
problemID: number,
|
||||||
displayID: string
|
displayID: string,
|
||||||
) {
|
) {
|
||||||
return http.post("admin/contest/add_problem_from_public", {
|
return http.post("admin/contest/add_problem_from_public", {
|
||||||
contest_id: contestID,
|
contest_id: contestID,
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ const columns: DataTableColumn<Contest>[] = [
|
|||||||
h(
|
h(
|
||||||
NTag,
|
NTag,
|
||||||
{ type: CONTEST_STATUS[row.status]["type"], size: "small" },
|
{ type: CONTEST_STATUS[row.status]["type"], size: "small" },
|
||||||
() => CONTEST_STATUS[row.status]["name"]
|
() => CONTEST_STATUS[row.status]["name"],
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ async function addProblem() {
|
|||||||
await addProblemForContest(
|
await addProblemForContest(
|
||||||
props.contestID,
|
props.contestID,
|
||||||
props.problemID,
|
props.problemID,
|
||||||
displayID.value
|
displayID.value,
|
||||||
)
|
)
|
||||||
emit("added")
|
emit("added")
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ async function getList() {
|
|||||||
query.limit,
|
query.limit,
|
||||||
query.keyword,
|
query.keyword,
|
||||||
"",
|
"",
|
||||||
"ACM"
|
"ACM",
|
||||||
)
|
)
|
||||||
total.value = res.total
|
total.value = res.total
|
||||||
problems.value = res.results
|
problems.value = res.results
|
||||||
@@ -56,7 +56,7 @@ watch(
|
|||||||
() => props.show,
|
() => props.show,
|
||||||
(value) => {
|
(value) => {
|
||||||
if (value) getList()
|
if (value) getList()
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
watch(query, getList, { deep: true })
|
watch(query, getList, { deep: true })
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ const title = computed(
|
|||||||
"admin problem edit": "编辑题目",
|
"admin problem edit": "编辑题目",
|
||||||
"admin contest problem create": "新建比赛题目",
|
"admin contest problem create": "新建比赛题目",
|
||||||
"admin contest problem edit": "编辑比赛题目",
|
"admin contest problem edit": "编辑比赛题目",
|
||||||
}[<string>route.name])
|
})[<string>route.name],
|
||||||
)
|
)
|
||||||
const problem = reactive<BlankProblem>({
|
const problem = reactive<BlankProblem>({
|
||||||
_id: "",
|
_id: "",
|
||||||
@@ -91,7 +91,7 @@ const languageOptions = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
const tagOptions = computed(() =>
|
const tagOptions = computed(() =>
|
||||||
existingTags.value.map((tag) => ({ label: tag.name, value: tag.name }))
|
existingTags.value.map((tag) => ({ label: tag.name, value: tag.name })),
|
||||||
)
|
)
|
||||||
|
|
||||||
async function getProblemDetail() {
|
async function getProblemDetail() {
|
||||||
@@ -233,7 +233,7 @@ function detectProblemCompletion() {
|
|||||||
// 样例是空的
|
// 样例是空的
|
||||||
else if (
|
else if (
|
||||||
problem.samples.some(
|
problem.samples.some(
|
||||||
(sample) => sample.output === "" || sample.input === ""
|
(sample) => sample.output === "" || sample.input === "",
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
message.error("空样例没有删干净")
|
message.error("空样例没有删干净")
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ const title = computed(
|
|||||||
({
|
({
|
||||||
"admin problem list": "题目列表",
|
"admin problem list": "题目列表",
|
||||||
"admin contest problem list": "比赛题目列表",
|
"admin contest problem list": "比赛题目列表",
|
||||||
}[<string>route.name])
|
})[<string>route.name],
|
||||||
)
|
)
|
||||||
const isContestProblemList = computed(
|
const isContestProblemList = computed(
|
||||||
() => route.name === "admin contest problem list"
|
() => route.name === "admin contest problem list",
|
||||||
)
|
)
|
||||||
|
|
||||||
const [show, toggleShow] = useToggle()
|
const [show, toggleShow] = useToggle()
|
||||||
@@ -77,7 +77,7 @@ async function listProblems() {
|
|||||||
offset,
|
offset,
|
||||||
query.limit,
|
query.limit,
|
||||||
query.keyword,
|
query.keyword,
|
||||||
props.contestID
|
props.contestID,
|
||||||
)
|
)
|
||||||
total.value = res.total
|
total.value = res.total
|
||||||
problems.value = res.results
|
problems.value = res.results
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ const testcaseColumns: DataTableColumn<Testcase>[] = [
|
|||||||
h(
|
h(
|
||||||
NButton,
|
NButton,
|
||||||
{ size: "small", onClick: () => deleteTestcase(row.id) },
|
{ size: "small", onClick: () => deleteTestcase(row.id) },
|
||||||
() => "删除"
|
() => "删除",
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -48,7 +48,7 @@ const serverColumns: DataTableColumn<Server>[] = [
|
|||||||
h(
|
h(
|
||||||
NTag,
|
NTag,
|
||||||
{ type: statusMap[row.status].color, size: "small" },
|
{ type: statusMap[row.status].color, size: "small" },
|
||||||
() => statusMap[row.status].label
|
() => statusMap[row.status].label,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -63,7 +63,7 @@ const serverColumns: DataTableColumn<Server>[] = [
|
|||||||
disabled: row.status === "normal",
|
disabled: row.status === "normal",
|
||||||
onClick: () => delJudgeServer(row.hostname),
|
onClick: () => delJudgeServer(row.hostname),
|
||||||
},
|
},
|
||||||
() => "删除"
|
() => "删除",
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{ title: "主机", key: "hostname", width: 130 },
|
{ title: "主机", key: "hostname", width: 130 },
|
||||||
@@ -94,7 +94,7 @@ const testcases = ref<Testcase[]>([])
|
|||||||
const token = ref("")
|
const token = ref("")
|
||||||
const servers = ref<Server[]>([])
|
const servers = ref<Server[]>([])
|
||||||
const abnormalServers = computed(() =>
|
const abnormalServers = computed(() =>
|
||||||
servers.value.filter((item) => item.status === "abnormal")
|
servers.value.filter((item) => item.status === "abnormal"),
|
||||||
)
|
)
|
||||||
|
|
||||||
const websiteConfig = reactive({
|
const websiteConfig = reactive({
|
||||||
@@ -144,7 +144,7 @@ async function delJudgeServer(hostname: string) {
|
|||||||
|
|
||||||
async function deleteAbnormalServers() {
|
async function deleteAbnormalServers() {
|
||||||
const dels = abnormalServers.value.map((item) =>
|
const dels = abnormalServers.value.map((item) =>
|
||||||
deleteJudgeServer(item.hostname)
|
deleteJudgeServer(item.hostname),
|
||||||
)
|
)
|
||||||
await Promise.all(dels)
|
await Promise.all(dels)
|
||||||
message.success("删除成功")
|
message.success("删除成功")
|
||||||
|
|||||||
1
src/components.d.ts
vendored
1
src/components.d.ts
vendored
@@ -24,6 +24,7 @@ declare module 'vue' {
|
|||||||
NCheckbox: typeof import('naive-ui')['NCheckbox']
|
NCheckbox: typeof import('naive-ui')['NCheckbox']
|
||||||
NCheckboxGroup: typeof import('naive-ui')['NCheckboxGroup']
|
NCheckboxGroup: typeof import('naive-ui')['NCheckboxGroup']
|
||||||
NCode: typeof import('naive-ui')['NCode']
|
NCode: typeof import('naive-ui')['NCode']
|
||||||
|
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
|
||||||
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
||||||
NDataTable: typeof import('naive-ui')['NDataTable']
|
NDataTable: typeof import('naive-ui')['NDataTable']
|
||||||
NDatePicker: typeof import('naive-ui')['NDatePicker']
|
NDatePicker: typeof import('naive-ui')['NDatePicker']
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const router = useRouter()
|
|||||||
const learnStore = useLearnStore()
|
const learnStore = useLearnStore()
|
||||||
|
|
||||||
const Mds = Array.from({ length: learnStore.total }, (_, i) => i + 1).map((v) =>
|
const Mds = Array.from({ length: learnStore.total }, (_, i) => i + 1).map((v) =>
|
||||||
defineAsyncComponent(() => import(`./step-${v}/index.md`))
|
defineAsyncComponent(() => import(`./step-${v}/index.md`)),
|
||||||
)
|
)
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@@ -24,7 +24,7 @@ watch(
|
|||||||
router.replace("/learn/step-1")
|
router.replace("/learn/step-1")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true },
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export function getWebsiteConfig() {
|
|||||||
export async function getProblemList(
|
export async function getProblemList(
|
||||||
offset = 0,
|
offset = 0,
|
||||||
limit = 10,
|
limit = 10,
|
||||||
searchParams: any = {}
|
searchParams: any = {},
|
||||||
) {
|
) {
|
||||||
let params: any = {
|
let params: any = {
|
||||||
paging: true,
|
paging: true,
|
||||||
@@ -129,7 +129,7 @@ export async function getContestProblems(contestID: string) {
|
|||||||
|
|
||||||
export function getContestRank(
|
export function getContestRank(
|
||||||
contestID: string,
|
contestID: string,
|
||||||
query: { limit: number; offset: number; force_refresh: "1" | "0" }
|
query: { limit: number; offset: number; force_refresh: "1" | "0" },
|
||||||
) {
|
) {
|
||||||
return http.get("contest_rank", {
|
return http.get("contest_rank", {
|
||||||
params: {
|
params: {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const passwordFormVisible = computed(
|
|||||||
() =>
|
() =>
|
||||||
contestStore.isPrivate &&
|
contestStore.isPrivate &&
|
||||||
!contestStore.access &&
|
!contestStore.access &&
|
||||||
!contestStore.isContestAdmin
|
!contestStore.isContestAdmin,
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ const columns: DataTableColumn<Contest>[] = [
|
|||||||
h(
|
h(
|
||||||
NTag,
|
NTag,
|
||||||
{ type: CONTEST_STATUS[row.status]["type"] },
|
{ type: CONTEST_STATUS[row.status]["type"] },
|
||||||
() => CONTEST_STATUS[row.status]["name"]
|
() => CONTEST_STATUS[row.status]["name"],
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -95,13 +95,13 @@ watch(
|
|||||||
() => {
|
() => {
|
||||||
query.page = 1
|
query.page = 1
|
||||||
routerPush()
|
routerPush()
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
watch(
|
watch(
|
||||||
() => route.name === "contests" && route.query,
|
() => route.name === "contests" && route.query,
|
||||||
(newVal) => {
|
(newVal) => {
|
||||||
if (newVal) listContests()
|
if (newVal) listContests()
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
function rowProps(row: Contest) {
|
function rowProps(row: Contest) {
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ const { resume, pause } = useIntervalFn(
|
|||||||
10000,
|
10000,
|
||||||
{
|
{
|
||||||
immediate: false,
|
immediate: false,
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
const query = reactive({
|
const query = reactive({
|
||||||
@@ -64,7 +64,7 @@ const columns = ref<DataTableColumn<ContestRank>[]>([
|
|||||||
type: "info",
|
type: "info",
|
||||||
onClick: () => router.push("/user?name=" + row.user.username),
|
onClick: () => router.push("/user?name=" + row.user.username),
|
||||||
},
|
},
|
||||||
() => row.user.username
|
() => row.user.username,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -119,7 +119,7 @@ async function addColumns() {
|
|||||||
window.open(data.href, "_blank")
|
window.open(data.href, "_blank")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
() => problem.title
|
() => problem.title,
|
||||||
),
|
),
|
||||||
render: (row) => {
|
render: (row) => {
|
||||||
if (row.submission_info[problem.id]) {
|
if (row.submission_info[problem.id]) {
|
||||||
@@ -134,7 +134,7 @@ async function addColumns() {
|
|||||||
h(
|
h(
|
||||||
NIcon,
|
NIcon,
|
||||||
{ size: 16, style: "transform: translate(-2px, 3px)" },
|
{ size: 16, style: "transform: translate(-2px, 3px)" },
|
||||||
() => h(GoldMedal)
|
() => h(GoldMedal),
|
||||||
),
|
),
|
||||||
secondsToDuration(status.ac_time),
|
secondsToDuration(status.ac_time),
|
||||||
]
|
]
|
||||||
@@ -143,7 +143,7 @@ async function addColumns() {
|
|||||||
errorNumber = h(
|
errorNumber = h(
|
||||||
"p",
|
"p",
|
||||||
{ style: "margin: 0" },
|
{ style: "margin: 0" },
|
||||||
`(-${status.error_number})`
|
`(-${status.error_number})`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return h("div", [acTime, errorNumber])
|
return h("div", [acTime, errorNumber])
|
||||||
@@ -182,7 +182,7 @@ watch(
|
|||||||
() => {
|
() => {
|
||||||
query.page = 1
|
query.page = 1
|
||||||
listRanks()
|
listRanks()
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
watch(autoRefresh, (checked) => (checked ? resume() : pause()))
|
watch(autoRefresh, (checked) => (checked ? resume() : pause()))
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const contestID = !!route.params.contestID ? route.params.contestID : null
|
|||||||
|
|
||||||
const storageKey = computed(
|
const storageKey = computed(
|
||||||
() =>
|
() =>
|
||||||
`problem_${problem.value!._id}_contest_${contestID}_lang_${code.language}`
|
`problem_${problem.value!._id}_contest_${contestID}_lang_${code.language}`,
|
||||||
)
|
)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -26,7 +26,7 @@ onMounted(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const editorHeight = computed(() =>
|
const editorHeight = computed(() =>
|
||||||
isDesktop.value ? "calc(100vh - 133px)" : "calc(100vh - 172px)"
|
isDesktop.value ? "calc(100vh - 133px)" : "calc(100vh - 172px)",
|
||||||
)
|
)
|
||||||
|
|
||||||
function changeCode(v: string) {
|
function changeCode(v: string) {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const samples = ref<Sample[]>(
|
|||||||
msg: "",
|
msg: "",
|
||||||
status: "not_test",
|
status: "not_test",
|
||||||
loading: false,
|
loading: false,
|
||||||
}))
|
})),
|
||||||
)
|
)
|
||||||
|
|
||||||
async function test(sample: Sample, index: number) {
|
async function test(sample: Sample, index: number) {
|
||||||
@@ -202,7 +202,8 @@ function type(status: ProblemStatus) {
|
|||||||
border: 1px solid rgb(239, 239, 245);
|
border: 1px solid rgb(239, 239, 245);
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
transition: background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
transition:
|
||||||
|
background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||||||
border-color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
border-color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,13 +220,15 @@ function type(status: ProblemStatus) {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
border-spacing: 0;
|
border-spacing: 0;
|
||||||
border: 1px solid rgba(239, 239, 245, 1);
|
border: 1px solid rgba(239, 239, 245, 1);
|
||||||
transition: background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
transition:
|
||||||
|
background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||||||
border-color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
border-color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.problemContent > .content > table th {
|
.problemContent > .content > table th {
|
||||||
background-color: rgba(250, 250, 252, 1);
|
background-color: rgba(250, 250, 252, 1);
|
||||||
transition: background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
transition:
|
||||||
|
background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||||||
border-color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
border-color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ const columns: DataTableColumn<Submission>[] = [
|
|||||||
render: (row) =>
|
render: (row) =>
|
||||||
parseTime(
|
parseTime(
|
||||||
row.create_time,
|
row.create_time,
|
||||||
isDesktop ? "YYYY-MM-DD HH:mm:ss" : "M-D hh:mm"
|
isDesktop ? "YYYY-MM-DD HH:mm:ss" : "M-D hh:mm",
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -40,7 +40,7 @@ const columns: DataTableColumn<Submission>[] = [
|
|||||||
window.open(data.href, "_blank")
|
window.open(data.href, "_blank")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
() => row.id.slice(0, 12)
|
() => row.id.slice(0, 12),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
return row.id.slice(0, 12)
|
return row.id.slice(0, 12)
|
||||||
|
|||||||
@@ -39,17 +39,21 @@ const { start: fetchSubmission } = useTimeoutFn(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
2000,
|
2000,
|
||||||
{ immediate: false }
|
{ immediate: false },
|
||||||
)
|
)
|
||||||
|
|
||||||
const judging = computed(
|
const judging = computed(
|
||||||
() =>
|
() =>
|
||||||
!!(submission.value && submission.value.result === SubmissionStatus.judging)
|
!!(
|
||||||
|
submission.value && submission.value.result === SubmissionStatus.judging
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
const pending = computed(
|
const pending = computed(
|
||||||
() =>
|
() =>
|
||||||
!!(submission.value && submission.value.result === SubmissionStatus.pending)
|
!!(
|
||||||
|
submission.value && submission.value.result === SubmissionStatus.pending
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
const submitting = computed(
|
const submitting = computed(
|
||||||
@@ -57,7 +61,7 @@ const submitting = computed(
|
|||||||
!!(
|
!!(
|
||||||
submission.value &&
|
submission.value &&
|
||||||
submission.value.result === SubmissionStatus.submitting
|
submission.value.result === SubmissionStatus.submitting
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
const submitDisabled = computed(() => {
|
const submitDisabled = computed(() => {
|
||||||
@@ -186,7 +190,7 @@ watch(
|
|||||||
size: party.variation.skew(2, 0.3),
|
size: party.variation.skew(2, 0.3),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import Pagination from "~/shared/Pagination.vue"
|
|||||||
import { NSpace, NTag } from "naive-ui"
|
import { NSpace, NTag } from "naive-ui"
|
||||||
import ProblemStatus from "./components/ProblemStatus.vue"
|
import ProblemStatus from "./components/ProblemStatus.vue"
|
||||||
import { getProblemTagList } from "~/shared/api"
|
import { getProblemTagList } from "~/shared/api"
|
||||||
|
import { isDesktop } from "~/shared/composables/breakpoints"
|
||||||
|
|
||||||
interface Tag {
|
interface Tag {
|
||||||
id: number
|
id: number
|
||||||
@@ -36,6 +37,7 @@ const userStore = useUserStore()
|
|||||||
const problems = ref<ProblemFiltered[]>([])
|
const problems = ref<ProblemFiltered[]>([])
|
||||||
const total = ref(0)
|
const total = ref(0)
|
||||||
const tags = ref<Tag[]>([])
|
const tags = ref<Tag[]>([])
|
||||||
|
const [showTag, toggleShowTag] = useToggle(isDesktop.value)
|
||||||
|
|
||||||
const query = reactive<Query>({
|
const query = reactive<Query>({
|
||||||
keyword: <string>route.query.keyword ?? "",
|
keyword: <string>route.query.keyword ?? "",
|
||||||
@@ -112,14 +114,14 @@ watch(
|
|||||||
() => {
|
() => {
|
||||||
query.page = 1
|
query.page = 1
|
||||||
routerPush()
|
routerPush()
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => route.path === "/" && route.query,
|
() => route.path === "/" && route.query,
|
||||||
(newVal) => {
|
(newVal) => {
|
||||||
if (newVal) listProblems()
|
if (newVal) listProblems()
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: 这里会在登录时候执行两次,有BUG
|
// TODO: 这里会在登录时候执行两次,有BUG
|
||||||
@@ -130,7 +132,7 @@ onMounted(() => {
|
|||||||
listTags()
|
listTags()
|
||||||
})
|
})
|
||||||
|
|
||||||
const columns: DataTableColumn<ProblemFiltered>[] = [
|
const baseColumns: DataTableColumn<ProblemFiltered>[] = [
|
||||||
{
|
{
|
||||||
title: "状态",
|
title: "状态",
|
||||||
key: "status",
|
key: "status",
|
||||||
@@ -157,6 +159,12 @@ const columns: DataTableColumn<ProblemFiltered>[] = [
|
|||||||
{ title: "通过率", key: "rate", width: 100 },
|
{ title: "通过率", key: "rate", width: 100 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const columns = computed(() =>
|
||||||
|
userStore.isAuthed
|
||||||
|
? baseColumns
|
||||||
|
: baseColumns.filter((c: any) => c.key !== "status"),
|
||||||
|
)
|
||||||
|
|
||||||
function rowProps(row: ProblemFiltered) {
|
function rowProps(row: ProblemFiltered) {
|
||||||
return {
|
return {
|
||||||
style: "cursor: pointer",
|
style: "cursor: pointer",
|
||||||
@@ -191,20 +199,22 @@ function rowProps(row: ProblemFiltered) {
|
|||||||
</n-space>
|
</n-space>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-form>
|
</n-form>
|
||||||
|
<n-button @click="toggleShowTag()" quaternary>标签</n-button>
|
||||||
</n-space>
|
</n-space>
|
||||||
<n-space>
|
<n-collapse-transition :show="showTag">
|
||||||
<div class="tagTitle">标签</div>
|
<n-space>
|
||||||
<n-button
|
<n-button
|
||||||
@click="chooseTag(tag)"
|
@click="chooseTag(tag)"
|
||||||
v-for="tag in tags"
|
v-for="tag in tags"
|
||||||
:key="tag.id"
|
:key="tag.id"
|
||||||
size="small"
|
size="small"
|
||||||
secondary
|
secondary
|
||||||
:type="tag.checked ? 'success' : 'default'"
|
:type="tag.checked ? 'success' : 'default'"
|
||||||
>
|
>
|
||||||
{{ tag.name }}
|
{{ tag.name }}
|
||||||
</n-button>
|
</n-button>
|
||||||
</n-space>
|
</n-space>
|
||||||
|
</n-collapse-transition>
|
||||||
<n-data-table
|
<n-data-table
|
||||||
striped
|
striped
|
||||||
:data="problems"
|
:data="problems"
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ const columns: DataTableColumn<Rank>[] = [
|
|||||||
type: "info",
|
type: "info",
|
||||||
onClick: () => router.push("/user?name=" + row.user.username),
|
onClick: () => router.push("/user?name=" + row.user.username),
|
||||||
},
|
},
|
||||||
() => row.user.username
|
() => row.user.username,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{ title: "自我介绍", key: "mood", minWidth: 200 },
|
{ title: "自我介绍", key: "mood", minWidth: 200 },
|
||||||
@@ -67,7 +67,7 @@ watch(
|
|||||||
() => {
|
() => {
|
||||||
query.page = 1
|
query.page = 1
|
||||||
listRanks()
|
listRanks()
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
onMounted(listRanks)
|
onMounted(listRanks)
|
||||||
|
|||||||
@@ -48,11 +48,11 @@ export const useContestStore = defineStore("contest", () => {
|
|||||||
() =>
|
() =>
|
||||||
userStore.isSuperAdmin ||
|
userStore.isSuperAdmin ||
|
||||||
(userStore.isAuthed &&
|
(userStore.isAuthed &&
|
||||||
contest.value?.created_by.id === userStore.user!.id)
|
contest.value?.created_by.id === userStore.user!.id),
|
||||||
)
|
)
|
||||||
|
|
||||||
const isPrivate = computed(
|
const isPrivate = computed(
|
||||||
() => contest.value!.contest_type === ContestType.private
|
() => contest.value!.contest_type === ContestType.private,
|
||||||
)
|
)
|
||||||
|
|
||||||
async function init(contestID: string) {
|
async function init(contestID: string) {
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ watch(
|
|||||||
() => {
|
() => {
|
||||||
query.page = 1
|
query.page = 1
|
||||||
routerPush()
|
routerPush()
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@@ -111,7 +111,7 @@ watch(
|
|||||||
route.query,
|
route.query,
|
||||||
(newVal) => {
|
(newVal) => {
|
||||||
if (newVal) listSubmissions()
|
if (newVal) listSubmissions()
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
const columns = computed(() => {
|
const columns = computed(() => {
|
||||||
@@ -123,7 +123,7 @@ const columns = computed(() => {
|
|||||||
render: (row) =>
|
render: (row) =>
|
||||||
parseTime(
|
parseTime(
|
||||||
row.create_time,
|
row.create_time,
|
||||||
isDesktop ? "YYYY-MM-DD HH:mm:ss" : "M-D hh:mm"
|
isDesktop ? "YYYY-MM-DD HH:mm:ss" : "M-D hh:mm",
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -139,7 +139,7 @@ const columns = computed(() => {
|
|||||||
type: "info",
|
type: "info",
|
||||||
onClick: () => router.push("/submission/" + row.id),
|
onClick: () => router.push("/submission/" + row.id),
|
||||||
},
|
},
|
||||||
() => row.id.slice(0, 12)
|
() => row.id.slice(0, 12),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
return row.id.slice(0, 12)
|
return row.id.slice(0, 12)
|
||||||
@@ -175,7 +175,7 @@ const columns = computed(() => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
() => row.problem
|
() => row.problem,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -208,7 +208,7 @@ const columns = computed(() => {
|
|||||||
type: "info",
|
type: "info",
|
||||||
onClick: () => router.push("/user?name=" + row.username),
|
onClick: () => router.push("/user?name=" + row.username),
|
||||||
},
|
},
|
||||||
() => row.username
|
() => row.username,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -225,7 +225,7 @@ const columns = computed(() => {
|
|||||||
type: "primary",
|
type: "primary",
|
||||||
onClick: () => rejudge(row.id),
|
onClick: () => rejudge(row.id),
|
||||||
},
|
},
|
||||||
() => "重新判题"
|
() => "重新判题",
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ watch(
|
|||||||
() => props.modelValue,
|
() => props.modelValue,
|
||||||
(v) => {
|
(v) => {
|
||||||
code.value = v
|
code.value = v
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
const emit = defineEmits(["update:modelValue"])
|
const emit = defineEmits(["update:modelValue"])
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ interface Props {
|
|||||||
const props = defineProps<Props>()
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
const isPrivate = computed(
|
const isPrivate = computed(
|
||||||
() => props.contest.contest_type === ContestType.private
|
() => props.contest.contest_type === ContestType.private,
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ import { isDark, toggleDark } from "~/shared/composables/dark"
|
|||||||
import { toggleLogin, toggleSignup } from "~/shared/composables/modal"
|
import { toggleLogin, toggleSignup } from "~/shared/composables/modal"
|
||||||
import { RouterLink } from "vue-router"
|
import { RouterLink } from "vue-router"
|
||||||
import { isDesktop, isMobile } from "~/shared/composables/breakpoints"
|
import { isDesktop, isMobile } from "~/shared/composables/breakpoints"
|
||||||
import { screenSwitchLabel, switchScreenMode } from "~/shared/composables/switchScreen"
|
import {
|
||||||
|
screenSwitchLabel,
|
||||||
|
switchScreenMode,
|
||||||
|
} from "~/shared/composables/switchScreen"
|
||||||
import { code } from "~/shared/composables/learn"
|
import { code } from "~/shared/composables/learn"
|
||||||
import { useLearnStore } from "~/learn/store"
|
import { useLearnStore } from "~/learn/store"
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export function loadChart() {
|
|||||||
Colors,
|
Colors,
|
||||||
Title,
|
Title,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Legend
|
Legend,
|
||||||
)
|
)
|
||||||
isLoaded.value = true
|
isLoaded.value = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ const options: MenuOption[] = [
|
|||||||
h(
|
h(
|
||||||
RouterLink,
|
RouterLink,
|
||||||
{ to: "/admin/problem/list" },
|
{ to: "/admin/problem/list" },
|
||||||
{ default: () => "题目列表" }
|
{ default: () => "题目列表" },
|
||||||
),
|
),
|
||||||
key: "admin problem list",
|
key: "admin problem list",
|
||||||
},
|
},
|
||||||
@@ -35,7 +35,7 @@ const options: MenuOption[] = [
|
|||||||
h(
|
h(
|
||||||
RouterLink,
|
RouterLink,
|
||||||
{ to: "/admin/problem/create" },
|
{ to: "/admin/problem/create" },
|
||||||
{ default: () => "新建题目" }
|
{ default: () => "新建题目" },
|
||||||
),
|
),
|
||||||
key: "admin problem create",
|
key: "admin problem create",
|
||||||
},
|
},
|
||||||
@@ -50,7 +50,7 @@ const options: MenuOption[] = [
|
|||||||
h(
|
h(
|
||||||
RouterLink,
|
RouterLink,
|
||||||
{ to: "/admin/user/generate" },
|
{ to: "/admin/user/generate" },
|
||||||
{ default: () => "批量生成" }
|
{ default: () => "批量生成" },
|
||||||
),
|
),
|
||||||
key: "admin user generate",
|
key: "admin user generate",
|
||||||
},
|
},
|
||||||
@@ -60,7 +60,7 @@ const options: MenuOption[] = [
|
|||||||
h(
|
h(
|
||||||
RouterLink,
|
RouterLink,
|
||||||
{ to: "/admin/contest/list" },
|
{ to: "/admin/contest/list" },
|
||||||
{ default: () => "比赛列表" }
|
{ default: () => "比赛列表" },
|
||||||
),
|
),
|
||||||
key: "admin contest list",
|
key: "admin contest list",
|
||||||
},
|
},
|
||||||
@@ -69,7 +69,7 @@ const options: MenuOption[] = [
|
|||||||
h(
|
h(
|
||||||
RouterLink,
|
RouterLink,
|
||||||
{ to: "/admin/contest/create" },
|
{ to: "/admin/contest/create" },
|
||||||
{ default: () => "新建比赛" }
|
{ default: () => "新建比赛" },
|
||||||
),
|
),
|
||||||
key: "admin contest create",
|
key: "admin contest create",
|
||||||
},
|
},
|
||||||
@@ -84,7 +84,7 @@ const options: MenuOption[] = [
|
|||||||
h(
|
h(
|
||||||
RouterLink,
|
RouterLink,
|
||||||
{ to: "/admin/announcement" },
|
{ to: "/admin/announcement" },
|
||||||
{ default: () => "公告配置" }
|
{ default: () => "公告配置" },
|
||||||
),
|
),
|
||||||
key: "admin announcement",
|
key: "admin announcement",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -11,13 +11,13 @@ export const useUserStore = defineStore("user", () => {
|
|||||||
const isAdminRole = computed(
|
const isAdminRole = computed(
|
||||||
() =>
|
() =>
|
||||||
user.value?.admin_type === USER_TYPE.ADMIN ||
|
user.value?.admin_type === USER_TYPE.ADMIN ||
|
||||||
user.value?.admin_type === USER_TYPE.SUPER_ADMIN
|
user.value?.admin_type === USER_TYPE.SUPER_ADMIN,
|
||||||
)
|
)
|
||||||
const isSuperAdmin = computed(
|
const isSuperAdmin = computed(
|
||||||
() => user.value?.admin_type === USER_TYPE.SUPER_ADMIN
|
() => user.value?.admin_type === USER_TYPE.SUPER_ADMIN,
|
||||||
)
|
)
|
||||||
const hasProblemPermission = computed(
|
const hasProblemPermission = computed(
|
||||||
() => user.value?.problem_permission !== PROBLEM_PERMISSION.NONE
|
() => user.value?.problem_permission !== PROBLEM_PERMISSION.NONE,
|
||||||
)
|
)
|
||||||
|
|
||||||
async function getMyProfile() {
|
async function getMyProfile() {
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ export const createTheme = ({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
dark: variant === "dark",
|
dark: variant === "dark",
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
const highlightStyle = HighlightStyle.define(styles)
|
const highlightStyle = HighlightStyle.define(styles)
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ const oneDarkTheme = EditorView.theme(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ dark: true }
|
{ dark: true },
|
||||||
)
|
)
|
||||||
|
|
||||||
/// The highlighting style for code in the One Dark theme.
|
/// The highlighting style for code in the One Dark theme.
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ async function download(url: string) {
|
|||||||
const headers = res.headers
|
const headers = res.headers
|
||||||
const link = document.createElement("a")
|
const link = document.createElement("a")
|
||||||
link.href = window.URL.createObjectURL(
|
link.href = window.URL.createObjectURL(
|
||||||
new window.Blob([res.data], { type: headers["content-type"] })
|
new window.Blob([res.data], { type: headers["content-type"] }),
|
||||||
)
|
)
|
||||||
link.download = (headers["content-disposition"] || "").split("filename=")[1]
|
link.download = (headers["content-disposition"] || "").split("filename=")[1]
|
||||||
document.body.appendChild(link)
|
document.body.appendChild(link)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export function filterEmptyValue(object: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getTagColor(
|
export function getTagColor(
|
||||||
tag: "Low" | "Mid" | "High" | "简单" | "中等" | "困难"
|
tag: "Low" | "Mid" | "High" | "简单" | "中等" | "困难",
|
||||||
) {
|
) {
|
||||||
return <"success" | "info" | "error">{
|
return <"success" | "info" | "error">{
|
||||||
Low: "success",
|
Low: "success",
|
||||||
@@ -39,7 +39,7 @@ export function parseTime(utc: Date | string, format = "YYYY年M月D日") {
|
|||||||
export function duration(
|
export function duration(
|
||||||
start: Date | string,
|
start: Date | string,
|
||||||
end: Date | string,
|
end: Date | string,
|
||||||
showSeconds = false
|
showSeconds = false,
|
||||||
): string {
|
): string {
|
||||||
const duration = intervalToDuration({
|
const duration = intervalToDuration({
|
||||||
start: getTime(parseISO(start.toString())),
|
start: getTime(parseISO(start.toString())),
|
||||||
@@ -141,8 +141,8 @@ export function decode(bytes?: string) {
|
|||||||
const latin = atob(bytes ?? "")
|
const latin = atob(bytes ?? "")
|
||||||
return new TextDecoder("utf-8").decode(
|
return new TextDecoder("utf-8").decode(
|
||||||
Uint8Array.from({ length: latin.length }, (_, index) =>
|
Uint8Array.from({ length: latin.length }, (_, index) =>
|
||||||
latin.charCodeAt(index)
|
latin.charCodeAt(index),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ http.interceptors.response.use(
|
|||||||
},
|
},
|
||||||
(err) => {
|
(err) => {
|
||||||
return Promise.reject(err)
|
return Promise.reject(err)
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
export default http
|
export default http
|
||||||
|
|||||||
Reference in New Issue
Block a user