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,81 +1,89 @@
<script setup lang="ts">
import { SearchOutlined } from "@ant-design/icons-vue";
import { useQuery } from "@tanstack/vue-query";
import { computed, ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { imagesApi } from "@/lib/api";
import { formatBytes, formatPercentage } from "@/lib/display";
import type { ImageAssetItem } from "@geo/shared-types";
import { imagesApi } from '@/lib/api'
import { formatBytes, formatPercentage } from '@/lib/display'
import type { ImageAssetItem } from '@geo/shared-types'
import { useQuery } from '@tanstack/vue-query'
import { computed, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
const props = withDefaults(defineProps<{
singleSelect?: boolean;
}>(), {
singleSelect: true,
});
withDefaults(
defineProps<{
singleSelect?: boolean
}>(),
{
singleSelect: true,
},
)
const emit = defineEmits<{
select: [image: ImageAssetItem];
}>();
select: [image: ImageAssetItem]
}>()
const { t } = useI18n();
const { t } = useI18n()
// State
const selectedFolderId = ref<number | null>(null);
const searchKeyword = ref("");
const debouncedSearch = ref("");
const currentPage = ref(1);
const pageSize = ref(12);
const selectedId = ref<number | null>(null);
const selectedFolderId = ref<number | null>(null)
const searchKeyword = ref('')
const debouncedSearch = ref('')
const currentPage = ref(1)
const pageSize = ref(12)
const selectedId = ref<number | null>(null)
// Queries
const foldersQuery = useQuery({
queryKey: ["images", "folders"],
queryKey: ['images', 'folders'],
queryFn: () => imagesApi.listFolders(),
});
})
const imagesQuery = useQuery({
queryKey: computed(() => ["images", "list", "picker", {
folder_id: selectedFolderId.value,
q: debouncedSearch.value,
page: currentPage.value,
page_size: pageSize.value,
}]),
queryFn: () => imagesApi.list({
folder_id: selectedFolderId.value ?? undefined,
q: debouncedSearch.value,
page: currentPage.value,
page_size: pageSize.value,
}),
});
queryKey: computed(() => [
'images',
'list',
'picker',
{
folder_id: selectedFolderId.value,
q: debouncedSearch.value,
page: currentPage.value,
page_size: pageSize.value,
},
]),
queryFn: () =>
imagesApi.list({
folder_id: selectedFolderId.value ?? undefined,
q: debouncedSearch.value,
page: currentPage.value,
page_size: pageSize.value,
}),
})
const storageQuery = useQuery({
queryKey: ["images", "storage-usage", "picker"],
queryKey: ['images', 'storage-usage', 'picker'],
queryFn: () => imagesApi.storageUsage(),
});
})
// Computed
const folders = computed(() => foldersQuery.data.value ?? []);
const images = computed(() => imagesQuery.data.value?.items ?? []);
const totalImages = computed(() => imagesQuery.data.value?.total ?? 0);
const storageUsage = computed(() => storageQuery.data.value);
const folders = computed(() => foldersQuery.data.value ?? [])
const images = computed(() => imagesQuery.data.value?.items ?? [])
const totalImages = computed(() => imagesQuery.data.value?.total ?? 0)
const storageUsage = computed(() => storageQuery.data.value)
// Actions
let searchTimer: ReturnType<typeof setTimeout> | null = null;
let searchTimer: ReturnType<typeof setTimeout> | null = null
watch(searchKeyword, () => {
if (searchTimer) clearTimeout(searchTimer);
if (searchTimer) clearTimeout(searchTimer)
searchTimer = setTimeout(() => {
debouncedSearch.value = searchKeyword.value;
currentPage.value = 1;
}, 300);
});
debouncedSearch.value = searchKeyword.value
currentPage.value = 1
}, 300)
})
function handleSelect(image: ImageAssetItem) {
selectedId.value = image.id;
emit("select", image);
selectedId.value = image.id
emit('select', image)
}
function getSelectPopupContainer(triggerNode: HTMLElement) {
return triggerNode.parentElement ?? triggerNode;
return triggerNode.parentElement ?? triggerNode
}
</script>
@@ -90,7 +98,7 @@ function getSelectPopupContainer(triggerNode: HTMLElement) {
:get-popup-container="getSelectPopupContainer"
@change="currentPage = 1"
>
<a-select-option :value="null">{{ t("images.allFolders") }}</a-select-option>
<a-select-option :value="null">{{ t('images.allFolders') }}</a-select-option>
<a-select-option v-for="f in folders" :key="f.id" :value="f.id">
{{ f.name }}
</a-select-option>
@@ -136,15 +144,20 @@ function getSelectPopupContainer(triggerNode: HTMLElement) {
<div v-if="storageUsage" class="storage-usage">
<div class="storage-usage__info">
<span>{{ t("images.storageUsage") }}: {{ t("images.storageUsageDetail", {
used: formatBytes(storageUsage.used_bytes),
total: formatBytes(storageUsage.quota_bytes),
percent: formatPercentage(storageUsage.used_pct)
}) }}</span>
<span>
{{ t('images.storageUsage') }}:
{{
t('images.storageUsageDetail', {
used: formatBytes(storageUsage.used_bytes),
total: formatBytes(storageUsage.quota_bytes),
percent: formatPercentage(storageUsage.used_pct),
})
}}
</span>
</div>
<a-progress
:percent="storageUsage.used_pct"
:show-info="false"
<a-progress
:percent="storageUsage.used_pct"
:show-info="false"
stroke-color="#1677ff"
size="small"
/>