chore(frontend): introduce prettier + eslint and prune unused code
Backend CI / Backend (push) Has been cancelled
Frontend CI / Frontend (push) Failing after 1m39s

- Add Prettier 3 with prettier-plugin-organize-imports (sorts/removes unused imports)
- Add ESLint 10 flat config with typescript-eslint + eslint-plugin-vue + eslint-config-prettier
- Add root scripts: format, format:check, lint, lint:fix
- Reformat 257 files across admin-web, ops-web, desktop-client, packages
- Remove unused locals/exports flagged by --noUnusedLocals/--noUnusedParameters
- Fix duplicate localTabLabel key, surrogate-pair regex u-flag, NBSP literal in regex
- Skip server/ (Go) and apps/browser-extension/ (deprecated per ADR)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-01 20:39:09 +08:00
parent 21c7892ee4
commit 162abdc97c
258 changed files with 42261 additions and 37556 deletions
@@ -1,63 +1,66 @@
<script setup lang="ts">
import { QuestionCircleOutlined, UserOutlined } from "@ant-design/icons-vue";
import { useQuery } from "@tanstack/vue-query";
import type { MediaPlatform } from "@geo/shared-types";
import { computed, ref } from "vue";
import { useI18n } from "vue-i18n";
import { QuestionCircleOutlined, UserOutlined } from '@ant-design/icons-vue'
import type { MediaPlatform } from '@geo/shared-types'
import { useQuery } from '@tanstack/vue-query'
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { articlesApi, mediaApi } from "@/lib/api";
import { getPublishStatusMeta } from "@/lib/display";
import { getPublishPlatformMeta, normalizePublishPlatformId } from "@/lib/publish-platforms";
import { articlesApi, mediaApi } from '@/lib/api'
import { getPublishStatusMeta } from '@/lib/display'
import { getPublishPlatformMeta, normalizePublishPlatformId } from '@/lib/publish-platforms'
interface PublishPlatformEntry {
key: string;
name: string;
shortName: string;
accent: string;
accountName: string;
avatarUrl: string;
status: PublishRecordStatus;
key: string
name: string
shortName: string
accent: string
accountName: string
avatarUrl: string
status: PublishRecordStatus
}
type PublishRecordStatus = "success" | "failed" | "publishing";
type PublishRecordStatus = 'success' | 'failed' | 'publishing'
interface PublishPlatformSection {
key: PublishRecordStatus;
label: string;
tone: "success" | "failed" | "pending";
entries: PublishPlatformEntry[];
key: PublishRecordStatus
label: string
tone: 'success' | 'failed' | 'pending'
entries: PublishPlatformEntry[]
}
const props = withDefaults(defineProps<{
status?: string | null;
articleId?: number | null;
showPlatforms?: boolean;
}>(), {
status: null,
articleId: null,
showPlatforms: true,
});
const props = withDefaults(
defineProps<{
status?: string | null
articleId?: number | null
showPlatforms?: boolean
}>(),
{
status: null,
articleId: null,
showPlatforms: true,
},
)
const { t } = useI18n();
const popoverOpen = ref(false);
const shouldLoadRecords = computed(() => Boolean(props.articleId) && props.status !== "unpublished");
const shouldLoadPlatforms = computed(() => props.showPlatforms && shouldLoadRecords.value);
const { t } = useI18n()
const popoverOpen = ref(false)
const shouldLoadRecords = computed(() => Boolean(props.articleId) && props.status !== 'unpublished')
const shouldLoadPlatforms = computed(() => props.showPlatforms && shouldLoadRecords.value)
const platformsQuery = useQuery({
queryKey: ["media", "platforms", "publish-status"],
queryKey: ['media', 'platforms', 'publish-status'],
enabled: computed(() => popoverOpen.value && shouldLoadPlatforms.value),
queryFn: () => mediaApi.platforms(),
staleTime: 5 * 60 * 1000,
});
})
const publishRecordsQuery = useQuery({
queryKey: computed(() => ["articles", "publish-records", props.articleId]),
queryKey: computed(() => ['articles', 'publish-records', props.articleId]),
enabled: shouldLoadRecords,
queryFn: () => articlesApi.publishRecords(props.articleId as number),
staleTime: 30 * 1000,
});
})
const hasPopover = computed(() => props.showPlatforms && shouldLoadRecords.value);
const hasPopover = computed(() => props.showPlatforms && shouldLoadRecords.value)
const platformMap = computed(() => {
return new Map(
@@ -65,166 +68,166 @@ const platformMap = computed(() => {
normalizePublishPlatformId(platform.platform_id),
platform,
]),
);
});
)
})
const platformEntries = computed<PublishPlatformEntry[]>(() => {
const seen = new Set<string>();
const entries: PublishPlatformEntry[] = [];
const seen = new Set<string>()
const entries: PublishPlatformEntry[] = []
for (const record of publishRecordsQuery.data.value ?? []) {
const key = `${record.platform_id}:${record.platform_account_id}`;
const key = `${record.platform_id}:${record.platform_account_id}`
if (seen.has(key)) {
continue;
continue
}
seen.add(key);
seen.add(key)
const normalizedId = normalizePublishPlatformId(record.platform_id);
const platform = platformMap.value.get(normalizedId);
const fallback = getPublishPlatformMeta(normalizedId);
const normalizedId = normalizePublishPlatformId(record.platform_id)
const platform = platformMap.value.get(normalizedId)
const fallback = getPublishPlatformMeta(normalizedId)
entries.push({
key,
name: record.platform_name || platform?.name || fallback.name,
shortName: platform?.short_name || fallback.shortName,
accent: platform?.accent_color || fallback.accent,
accountName: record.platform_nickname || "--",
avatarUrl: "",
accountName: record.platform_nickname || '--',
avatarUrl: '',
status: normalizePublishRecordStatus(record.status),
});
})
}
return entries;
});
return entries
})
const successEntries = computed(() =>
platformEntries.value.filter((entry) => entry.status === "success"),
);
platformEntries.value.filter((entry) => entry.status === 'success'),
)
const failedEntries = computed(() =>
platformEntries.value.filter((entry) => entry.status === "failed"),
);
platformEntries.value.filter((entry) => entry.status === 'failed'),
)
const pendingEntries = computed(() =>
platformEntries.value.filter((entry) => entry.status === "publishing"),
);
platformEntries.value.filter((entry) => entry.status === 'publishing'),
)
const displayStatus = computed(() => {
if (successEntries.value.length > 0 && failedEntries.value.length > 0) {
return "partial_success";
return 'partial_success'
}
if (failedEntries.value.length > 0) {
return "failed";
return 'failed'
}
if (pendingEntries.value.length > 0) {
return "publishing";
return 'publishing'
}
if (successEntries.value.length > 0) {
return "success";
return 'success'
}
if (props.status === "partial_success") {
return "partial_success";
if (props.status === 'partial_success') {
return 'partial_success'
}
if (props.status === "publish_success" || props.status === "published") {
return "success";
if (props.status === 'publish_success' || props.status === 'published') {
return 'success'
}
if (props.status === "publish_failed") {
return "failed";
if (props.status === 'publish_failed') {
return 'failed'
}
if (props.status === "pending_review") {
return "failed";
if (props.status === 'pending_review') {
return 'failed'
}
return props.status ?? null;
});
return props.status ?? null
})
const statusMeta = computed(() => getPublishStatusMeta(displayStatus.value));
const statusMeta = computed(() => getPublishStatusMeta(displayStatus.value))
const statusTone = computed(() => {
switch (displayStatus.value) {
case "success":
return "success";
case "failed":
return "failed";
case "publishing":
return "processing";
case "partial_success":
return "warning";
case 'success':
return 'success'
case 'failed':
return 'failed'
case 'publishing':
return 'processing'
case 'partial_success':
return 'warning'
default:
return "default";
return 'default'
}
});
})
const popoverSections = computed<PublishPlatformSection[]>(() => {
const sections: PublishPlatformSection[] = [];
const sections: PublishPlatformSection[] = []
if (failedEntries.value.length > 0) {
sections.push({
key: "failed",
label: `${getPublishStatusMeta("failed").label}`,
tone: "failed",
key: 'failed',
label: `${getPublishStatusMeta('failed').label}`,
tone: 'failed',
entries: failedEntries.value,
});
})
}
if (pendingEntries.value.length > 0) {
sections.push({
key: "publishing",
label: `${getPublishStatusMeta("publishing").label}`,
tone: "pending",
key: 'publishing',
label: `${getPublishStatusMeta('publishing').label}`,
tone: 'pending',
entries: pendingEntries.value,
});
})
}
if (successEntries.value.length > 0) {
sections.push({
key: "success",
label: `${getPublishStatusMeta("success").label}`,
tone: "success",
key: 'success',
label: `${getPublishStatusMeta('success').label}`,
tone: 'success',
entries: successEntries.value,
});
})
}
return sections;
});
return sections
})
function handlePopoverChange(nextOpen: boolean): void {
popoverOpen.value = nextOpen;
popoverOpen.value = nextOpen
}
function normalizePublishRecordStatus(status?: string | null): PublishRecordStatus {
switch (String(status ?? "").trim()) {
case "success":
case "published":
case "publish_success":
return "success";
case "publishing":
case "running":
case "queued":
return "publishing";
case "pending_review":
return "failed";
case "failed":
case "publish_failed":
switch (String(status ?? '').trim()) {
case 'success':
case 'published':
case 'publish_success':
return 'success'
case 'publishing':
case 'running':
case 'queued':
return 'publishing'
case 'pending_review':
return 'failed'
case 'failed':
case 'publish_failed':
default:
return "failed";
return 'failed'
}
}
function getAccountInitial(accountName: string): string {
const trimmed = String(accountName ?? "").trim();
if (!trimmed || trimmed === "--") {
return "";
const trimmed = String(accountName ?? '').trim()
if (!trimmed || trimmed === '--') {
return ''
}
return trimmed.slice(0, 1).toUpperCase();
return trimmed.slice(0, 1).toUpperCase()
}
</script>
@@ -243,7 +246,7 @@ function getAccountInitial(accountName: string): string {
class="article-publish-status__loading"
>
<a-spin size="small" />
<span>{{ t("common.loading") }}</span>
<span>{{ t('common.loading') }}</span>
</div>
<div v-else-if="popoverSections.length" class="article-publish-status__sections">
@@ -282,7 +285,9 @@ function getAccountInitial(accountName: string): string {
</template>
<UserOutlined v-else />
</span>
<span class="article-publish-status__account-name">{{ entry.accountName }}</span>
<span class="article-publish-status__account-name">
{{ entry.accountName }}
</span>
</div>
</template>
@@ -304,7 +309,7 @@ function getAccountInitial(accountName: string): string {
</div>
<span v-else class="article-publish-status__empty">
{{ t("media.records.empty") }}
{{ t('media.records.empty') }}
</span>
</div>
</template>
@@ -347,7 +352,10 @@ function getAccountInitial(accountName: string): string {
font-weight: 600;
line-height: 22px;
white-space: nowrap;
transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
transition:
border-color 0.2s ease,
box-shadow 0.2s ease,
transform 0.2s ease;
}
.article-publish-status__badge--success {
@@ -382,7 +390,7 @@ function getAccountInitial(accountName: string): string {
.article-publish-status__badge-dot {
width: 6px;
height:6px;
height: 6px;
border-radius: 999px;
flex: 0 0 auto;
}