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
+129 -119
View File
@@ -70,15 +70,26 @@
{{ formatDate(record.updated_at) }}
</template>
<template v-else-if="column.key === 'actions'">
<div class="table-actions-row" style="justify-content: flex-end;">
<div class="table-actions-row" style="justify-content: flex-end">
<a-tooltip title="编辑">
<a-button type="text" shape="circle" size="small" class="action-btn action-edit" @click="openEdit(record)">
<a-button
type="text"
shape="circle"
size="small"
class="action-btn action-edit"
@click="openEdit(record)"
>
<EditOutlined />
</a-button>
</a-tooltip>
<a-tooltip title="删除">
<a-popconfirm title="确认删除该映射?" @confirm="deleteMapping(record)">
<a-button type="text" shape="circle" size="small" class="action-btn action-delete">
<a-button
type="text"
shape="circle"
size="small"
class="action-btn action-delete"
>
<DeleteOutlined />
</a-button>
</a-popconfirm>
@@ -107,7 +118,11 @@
<a-input v-model:value="form.site_name" placeholder="知乎" />
</a-form-item>
<a-form-item label="状态">
<a-switch v-model:checked="form.is_active" checked-children="启用" un-checked-children="停用" />
<a-switch
v-model:checked="form.is_active"
checked-children="启用"
un-checked-children="停用"
/>
</a-form-item>
</a-form>
@@ -122,224 +137,219 @@
</template>
<script setup lang="ts">
import {
DeleteOutlined,
EditOutlined,
PlusOutlined,
ReloadOutlined,
} from "@ant-design/icons-vue";
import type { TablePaginationConfig } from "ant-design-vue";
import { message } from "ant-design-vue";
import dayjs from "dayjs";
import { computed, onMounted, reactive, ref } from "vue";
import { DeleteOutlined, EditOutlined, PlusOutlined, ReloadOutlined } from '@ant-design/icons-vue'
import type { TablePaginationConfig } from 'ant-design-vue'
import { message } from 'ant-design-vue'
import dayjs from 'dayjs'
import { computed, onMounted, reactive, ref } from 'vue'
import { OpsApiError, http } from "@/lib/http";
import { OpsApiError, http } from '@/lib/http'
interface SiteDomainMappingRow {
id: number;
registrable_domain: string;
site_key: string;
site_name: string;
is_active: boolean;
created_at: string;
updated_at: string;
id: number
registrable_domain: string
site_key: string
site_name: string
is_active: boolean
created_at: string
updated_at: string
}
interface ListResult {
items: SiteDomainMappingRow[];
total: number;
page: number;
size: number;
items: SiteDomainMappingRow[]
total: number
page: number
size: number
}
const filter = reactive({
keyword: "",
keyword: '',
status: undefined as string | undefined,
});
})
const statusOptions = [
{ label: "启用", value: "true" },
{ label: "停用", value: "false" },
];
{ label: '启用', value: 'true' },
{ label: '停用', value: 'false' },
]
const columns = [
{ title: "域名 / Key", key: "registrable_domain", dataIndex: "registrable_domain", width: "34%" },
{ title: "中文名", key: "site_name", dataIndex: "site_name", width: "22%" },
{ title: "状态", key: "is_active", width: 120 },
{ title: "更新时间", key: "updated_at", width: 180 },
{ title: "操作", key: "actions", width: 120, align: "right" as const },
];
{ title: '域名 / Key', key: 'registrable_domain', dataIndex: 'registrable_domain', width: '34%' },
{ title: '中文名', key: 'site_name', dataIndex: 'site_name', width: '22%' },
{ title: '状态', key: 'is_active', width: 120 },
{ title: '更新时间', key: 'updated_at', width: 180 },
{ title: '操作', key: 'actions', width: 120, align: 'right' as const },
]
const rows = ref<SiteDomainMappingRow[]>([]);
const loading = ref(false);
const page = ref(1);
const size = ref(20);
const total = ref(0);
const statusLoadingId = ref<number | null>(null);
const rows = ref<SiteDomainMappingRow[]>([])
const loading = ref(false)
const page = ref(1)
const size = ref(20)
const total = ref(0)
const statusLoadingId = ref<number | null>(null)
const activeRows = computed(() => rows.value.filter((row) => row.is_active).length);
const inactiveRows = computed(() => rows.value.length - activeRows.value);
const activeRows = computed(() => rows.value.filter((row) => row.is_active).length)
const inactiveRows = computed(() => rows.value.length - activeRows.value)
const pagination = computed<TablePaginationConfig>(() => ({
current: page.value,
pageSize: size.value,
total: total.value,
showSizeChanger: true,
pageSizeOptions: ["10", "20", "50", "100"],
}));
pageSizeOptions: ['10', '20', '50', '100'],
}))
let keywordTimer: number | null = null;
let keywordTimer: number | null = null
function onKeywordChange() {
if (keywordTimer) window.clearTimeout(keywordTimer);
if (keywordTimer) window.clearTimeout(keywordTimer)
keywordTimer = window.setTimeout(() => {
page.value = 1;
void reload();
}, 300);
page.value = 1
void reload()
}, 300)
}
function onStatusChange() {
page.value = 1;
void reload();
page.value = 1
void reload()
}
async function reload() {
loading.value = true;
loading.value = true
try {
const result = await http.get<ListResult>("/site-domain-mappings", {
const result = await http.get<ListResult>('/site-domain-mappings', {
keyword: filter.keyword || undefined,
is_active: filter.status || undefined,
page: page.value,
size: size.value,
});
rows.value = result.items;
total.value = result.total;
})
rows.value = result.items
total.value = result.total
} catch (error) {
handleError(error);
handleError(error)
} finally {
loading.value = false;
loading.value = false
}
}
function onTableChange(pag: TablePaginationConfig) {
page.value = pag.current ?? 1;
size.value = pag.pageSize ?? 20;
void reload();
page.value = pag.current ?? 1
size.value = pag.pageSize ?? 20
void reload()
}
const drawerOpen = ref(false);
const saving = ref(false);
const editingRow = ref<SiteDomainMappingRow | null>(null);
const drawerOpen = ref(false)
const saving = ref(false)
const editingRow = ref<SiteDomainMappingRow | null>(null)
const form = reactive({
registrable_domain: "",
site_key: "",
site_name: "",
registrable_domain: '',
site_key: '',
site_name: '',
is_active: true,
});
})
function resetForm() {
form.registrable_domain = "";
form.site_key = "";
form.site_name = "";
form.is_active = true;
editingRow.value = null;
form.registrable_domain = ''
form.site_key = ''
form.site_name = ''
form.is_active = true
editingRow.value = null
}
function openCreate() {
resetForm();
drawerOpen.value = true;
resetForm()
drawerOpen.value = true
}
function openEdit(row: SiteDomainMappingRow) {
editingRow.value = row;
form.registrable_domain = row.registrable_domain;
form.site_key = row.site_key;
form.site_name = row.site_name;
form.is_active = row.is_active;
drawerOpen.value = true;
editingRow.value = row
form.registrable_domain = row.registrable_domain
form.site_key = row.site_key
form.site_name = row.site_name
form.is_active = row.is_active
drawerOpen.value = true
}
function closeDrawer() {
drawerOpen.value = false;
drawerOpen.value = false
}
async function submitForm() {
const domain = form.registrable_domain.trim();
const siteName = form.site_name.trim();
const domain = form.registrable_domain.trim()
const siteName = form.site_name.trim()
if (!domain) {
message.warning("请输入匹配域名");
return;
message.warning('请输入匹配域名')
return
}
if (!siteName) {
message.warning("请输入中文名");
return;
message.warning('请输入中文名')
return
}
saving.value = true;
saving.value = true
try {
const payload = {
registrable_domain: domain,
site_key: form.site_key.trim(),
site_name: siteName,
is_active: form.is_active,
};
if (editingRow.value) {
await http.patch(`/site-domain-mappings/${editingRow.value.id}`, payload);
message.success("已保存");
} else {
await http.post("/site-domain-mappings", payload);
message.success("已新增");
}
drawerOpen.value = false;
resetForm();
void reload();
if (editingRow.value) {
await http.patch(`/site-domain-mappings/${editingRow.value.id}`, payload)
message.success('已保存')
} else {
await http.post('/site-domain-mappings', payload)
message.success('已新增')
}
drawerOpen.value = false
resetForm()
void reload()
} catch (error) {
handleError(error);
handleError(error)
} finally {
saving.value = false;
saving.value = false
}
}
async function setActive(row: SiteDomainMappingRow, active: boolean) {
statusLoadingId.value = row.id;
statusLoadingId.value = row.id
try {
await http.post(`/site-domain-mappings/${row.id}/active`, { is_active: active });
message.success(active ? "已启用" : "已停用");
row.is_active = active;
await http.post(`/site-domain-mappings/${row.id}/active`, { is_active: active })
message.success(active ? '已启用' : '已停用')
row.is_active = active
} catch (error) {
handleError(error);
handleError(error)
} finally {
statusLoadingId.value = null;
statusLoadingId.value = null
}
}
function onActiveSwitchChange(row: SiteDomainMappingRow, checked: unknown) {
void setActive(row, Boolean(checked));
void setActive(row, Boolean(checked))
}
async function deleteMapping(row: SiteDomainMappingRow) {
try {
await http.delete(`/site-domain-mappings/${row.id}`);
message.success("已删除");
void reload();
await http.delete(`/site-domain-mappings/${row.id}`)
message.success('已删除')
void reload()
} catch (error) {
handleError(error);
handleError(error)
}
}
function formatDate(value: string | null | undefined): string {
return value ? dayjs(value).format("YYYY-MM-DD HH:mm:ss") : "—";
return value ? dayjs(value).format('YYYY-MM-DD HH:mm:ss') : '—'
}
function handleError(error: unknown) {
if (error instanceof OpsApiError) {
message.error(error.detail || error.message);
message.error(error.detail || error.message)
}
}
onMounted(() => {
void reload();
});
void reload()
})
</script>
<style scoped>