chore: eslint 配置
All checks were successful
/ depcheck (push) Successful in 2m19s
/ playwright (push) Successful in 1m45s
/ surge (push) Successful in 2m48s
/ build-and-deploy-to-vercel (push) Successful in 3m10s

This commit is contained in:
mini2024
2025-03-05 00:57:51 +08:00
parent feb7659b75
commit 2b7186ef69
35 changed files with 455 additions and 458 deletions

View File

@ -4,7 +4,6 @@ import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescri
import pluginImport from 'eslint-plugin-import-x';
import oxlint from 'eslint-plugin-oxlint';
import perfectionist from 'eslint-plugin-perfectionist';
import perfectionistPlugin from 'eslint-plugin-perfectionist';
import pluginVue from 'eslint-plugin-vue';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
@ -13,8 +12,6 @@ const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const gitignorePath = path.resolve(__dirname, '.gitignore');
import type { Linter } from 'eslint';
// To allow more languages other than `ts` in `.vue` files, uncomment the following lines:
import { configureVueProject } from '@vue/eslint-config-typescript';
@ -24,13 +21,13 @@ configureVueProject({ scriptLangs: ['ts', 'tsx', 'js', 'jsx'] });
export default defineConfigWithVueTs(
includeIgnoreFile(gitignorePath), // oxlint . --fix -D correctness --ignore-path .gitignore
{
name: 'app/files-to-lint',
files: ['**/*.{ts,mts,tsx,vue}'],
name: 'app/files-to-lint',
},
{
name: 'app/files-to-ignore',
ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**'],
name: 'app/files-to-ignore',
},
pluginVue.configs['flat/essential'],
@ -48,43 +45,9 @@ export default defineConfigWithVueTs(
oxlint.configs['flat/recommended'],
skipFormatting,
importPluginConfig(),
{
rules: {
'vue/multi-word-component-names': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'vue/block-order': [
'error',
{
order: ['script', 'template', 'style'],
},
],
'vue/define-macros-order': [
'error',
{
order: ['defineOptions', 'defineProps', 'defineEmits', 'defineSlots'],
},
],
},
},
// https://perfectionist.dev/guide/getting-started
perfectionistPlugin.configs['recommended-natural'],
{
plugins: {
perfectionist,
},
rules: {
'perfectionist/sort-imports': ['error'],
},
},
);
function importPluginConfig(): Linter.Config[] {
return [
[
{
plugins: {
// @ts-expect-error - This is a dynamic import
import: pluginImport,
},
rules: {
@ -98,5 +61,35 @@ function importPluginConfig(): Linter.Config[] {
// 'import/no-webpack-loader-syntax': 'error',
},
},
];
}
],
{
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'vue/block-order': [
'error',
{
order: ['script', 'template', 'style'],
},
],
'vue/define-macros-order': [
'error',
{
order: ['defineOptions', 'defineProps', 'defineEmits', 'defineSlots'],
},
],
'vue/multi-word-component-names': 'off',
},
},
// https://perfectionist.dev/guide/getting-started
[
perfectionist.configs['recommended-natural'],
{
rules: {
'perfectionist/sort-imports': ['error'],
},
},
],
);

View File

@ -4,14 +4,7 @@ import { defineFakeRoute } from 'vite-plugin-fake-server/client';
let fail = !false;
export default defineFakeRoute([
{
timeout: 2000,
method: 'POST',
url: '/fake/upload',
response: () => {
return {
url: 'https://picsum.photos/200/300',
};
},
rawResponse(req, res) {
fail = !fail;
if (fail) {
@ -22,5 +15,12 @@ export default defineFakeRoute([
res.end(JSON.stringify({ url: 'https://picsum.photos/200/300' }));
}
},
response: () => {
return {
url: 'https://picsum.photos/200/300',
};
},
timeout: 2000,
url: '/fake/upload',
},
]);

View File

@ -5,30 +5,30 @@ import { defineFakeRoute } from 'vite-plugin-fake-server/client';
export default defineFakeRoute([
{
url: '/mock/get-user-info',
response: () => {
return Mock.mock({
id: '@guid',
username: '@first',
email: '@email',
avatar: '@image("200x200")',
email: '@email',
id: '@guid',
role: 'admin',
username: '@first',
});
},
url: '/mock/get-user-info',
},
{
url: '/fake/get-user-info',
response: () => {
return {
id: faker.string.uuid(),
avatar: faker.image.avatar(),
birthday: faker.date.birthdate(),
email: faker.internet.email(),
firstName: faker.person.firstName(),
id: faker.string.uuid(),
lastName: faker.person.lastName(),
sex: faker.person.sexType(),
role: 'admin',
sex: faker.person.sexType(),
};
},
url: '/fake/get-user-info',
},
]);

View File

@ -12,29 +12,13 @@ import { defineConfig, devices } from '@playwright/test';
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './tests/e2e',
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: process.env.BASE_URL || 'https://vue-ts-example.oo1.dev',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
expect: {
timeout: 30 * 1000,
},
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Run tests in files in parallel */
fullyParallel: true,
/* Configure projects for major browsers */
projects: [
{
@ -72,6 +56,22 @@ export default defineConfig({
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
testDir: './tests/e2e',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: process.env.BASE_URL || 'https://vue-ts-example.oo1.dev',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Run your local dev server before starting the tests */
// webServer: {

View File

@ -6,7 +6,7 @@ function checkIsVisible(el: Element, root: Element | null = null) {
const elRect = el.getBoundingClientRect();
const rootRect = root
? root.getBoundingClientRect()
: { top: 0, left: 0, bottom: window.innerHeight, right: window.innerWidth };
: { bottom: window.innerHeight, left: 0, right: window.innerWidth, top: 0 };
return (
elRect.bottom >= rootRect.top &&
@ -32,26 +32,26 @@ function checkIsVisible(el: Element, root: Element | null = null) {
*/
const props = defineProps<{
loading: boolean;
complete: boolean;
error: boolean;
errorText: string;
loading: boolean;
}>();
const emit = defineEmits<{
load: [];
clickError: [];
load: [];
}>();
defineSlots<{
// 加载中
loading(): unknown;
// 加载完成(还有更多)
loaded(): unknown;
// 加载完成(没有更多了)
complete(): unknown;
// 加载失败
error(): unknown;
// 加载完成(还有更多)
loaded(): unknown;
// 加载中
loading(): unknown;
}>();
const check = (reason?: string) => {

View File

@ -6,15 +6,15 @@ interface FileExt extends File {
}
interface FileUploadInst extends FileUploadState {
chooseDisabled?: boolean;
files: FileExt[];
uploadedFiles: {
rawFile: FileExt;
name: string;
url: string;
status: 'uploading' | 'uploaded' | 'failed';
progress: number;
rawFile: FileExt;
status: 'failed' | 'uploaded' | 'uploading';
url: string;
}[];
chooseDisabled?: boolean;
}
</script>
@ -30,11 +30,11 @@ const onUploader = (event: FileUploadUploaderEvent) => {
const files = event.files as FileExt[];
for (const file of files) {
fileUploadRef.value!.uploadedFiles.push({
rawFile: file,
name: file.name,
url: '',
status: 'uploading',
progress: 0,
rawFile: file,
status: 'uploading',
url: '',
});
const formData = new FormData();
formData.append('file', file);

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
const { remaining: countdownTime, start: startCountdown, isActive: isCounting } = useCountdown($__DEV__ ? 3 : 60);
const { isActive: isCounting, remaining: countdownTime, start: startCountdown } = useCountdown($__DEV__ ? 3 : 60);
const isSending = ref(false);
const sendSms = async () => {
@ -8,7 +8,7 @@ const sendSms = async () => {
try {
await new Promise((resolve) => setTimeout(resolve, 1000));
startCountdown();
ToastService.add({ severity: 'info', summary: '提示', life: 3000, detail: '验证码发送成功' });
ToastService.add({ detail: '验证码发送成功', life: 3000, severity: 'info', summary: '提示' });
} finally {
isSending.value = false;
}

View File

@ -6,7 +6,7 @@ import { ref } from 'vue';
import { useLayout } from './composables/layout';
const { layoutConfig, isDarkTheme } = useLayout();
const { isDarkTheme, layoutConfig } = useLayout();
const presets = {
Aura,
Lara,
@ -419,12 +419,50 @@ const surfaces = ref([
},
]);
function applyTheme(type: string, color: any) {
if (type === 'primary') {
updatePreset(getPresetExt());
} else if (type === 'surface') {
updateSurfacePalette(color.palette);
}
}
function getPresetExt() {
const color = primaryColors.value.find((c) => c.name === layoutConfig.primary)!;
if (color.name === 'noir') {
return {
semantic: {
colorScheme: {
dark: {
highlight: {
background: '{primary.50}',
color: '{primary.950}',
focusBackground: '{primary.300}',
focusColor: '{primary.950}',
},
primary: {
activeColor: '{primary.300}',
color: '{primary.50}',
contrastColor: '{primary.950}',
hoverColor: '{primary.200}',
},
},
light: {
highlight: {
background: '{primary.950}',
color: '#ffffff',
focusBackground: '{primary.700}',
focusColor: '#ffffff',
},
primary: {
activeColor: '{primary.700}',
color: '{primary.950}',
contrastColor: '#ffffff',
hoverColor: '{primary.800}',
},
},
},
primary: {
50: '{surface.50}',
100: '{surface.100}',
@ -438,93 +476,49 @@ function getPresetExt() {
900: '{surface.900}',
950: '{surface.950}',
},
colorScheme: {
light: {
primary: {
color: '{primary.950}',
contrastColor: '#ffffff',
hoverColor: '{primary.800}',
activeColor: '{primary.700}',
},
highlight: {
background: '{primary.950}',
focusBackground: '{primary.700}',
color: '#ffffff',
focusColor: '#ffffff',
},
},
dark: {
primary: {
color: '{primary.50}',
contrastColor: '{primary.950}',
hoverColor: '{primary.200}',
activeColor: '{primary.300}',
},
highlight: {
background: '{primary.50}',
focusBackground: '{primary.300}',
color: '{primary.950}',
focusColor: '{primary.950}',
},
},
},
},
};
} else {
return {
semantic: {
primary: color.palette,
colorScheme: {
light: {
primary: {
color: '{primary.500}',
contrastColor: '#ffffff',
hoverColor: '{primary.600}',
activeColor: '{primary.700}',
},
highlight: {
background: '{primary.50}',
focusBackground: '{primary.100}',
color: '{primary.700}',
focusColor: '{primary.800}',
},
},
dark: {
highlight: {
background: 'color-mix(in srgb, {primary.400}, transparent 84%)',
color: 'rgba(255,255,255,.87)',
focusBackground: 'color-mix(in srgb, {primary.400}, transparent 76%)',
focusColor: 'rgba(255,255,255,.87)',
},
primary: {
activeColor: '{primary.200}',
color: '{primary.400}',
contrastColor: '{surface.900}',
hoverColor: '{primary.300}',
activeColor: '{primary.200}',
},
},
light: {
highlight: {
background: 'color-mix(in srgb, {primary.400}, transparent 84%)',
focusBackground: 'color-mix(in srgb, {primary.400}, transparent 76%)',
color: 'rgba(255,255,255,.87)',
focusColor: 'rgba(255,255,255,.87)',
background: '{primary.50}',
color: '{primary.700}',
focusBackground: '{primary.100}',
focusColor: '{primary.800}',
},
primary: {
activeColor: '{primary.700}',
color: '{primary.500}',
contrastColor: '#ffffff',
hoverColor: '{primary.600}',
},
},
},
primary: color.palette,
},
};
}
}
function updateColors(type: string, color: any) {
if (type === 'primary') {
layoutConfig.primary = color.name;
} else if (type === 'surface') {
layoutConfig.surface = color.name;
}
applyTheme(type, color);
}
function applyTheme(type: string, color: any) {
if (type === 'primary') {
updatePreset(getPresetExt());
} else if (type === 'surface') {
updateSurfacePalette(color.palette);
}
function onMenuModeChange() {
layoutConfig.menuMode = menuMode.value;
}
function onPresetChange() {
@ -535,8 +529,14 @@ function onPresetChange() {
$t().preset(presetValue).preset(getPresetExt()).surfacePalette(surfacePalette).use({ useDefaultOptions: true });
}
function onMenuModeChange() {
layoutConfig.menuMode = menuMode.value;
function updateColors(type: string, color: any) {
if (type === 'primary') {
layoutConfig.primary = color.name;
} else if (type === 'surface') {
layoutConfig.surface = color.name;
}
applyTheme(type, color);
}
</script>

View File

@ -8,9 +8,9 @@ import AppSidebar from './AppSidebar.vue';
import AppTopbar from './AppTopbar.vue';
import { useLayout } from './composables/layout';
const { layoutConfig, layoutState, isSidebarActive } = useLayout();
const { isSidebarActive, layoutConfig, layoutState } = useLayout();
const outsideClickListener = ref(null as Parameters<typeof document.addEventListener>[1] | null);
const outsideClickListener = ref(null as null | Parameters<typeof document.addEventListener>[1]);
watch(isSidebarActive, (newVal) => {
if (newVal) {
@ -22,11 +22,11 @@ watch(isSidebarActive, (newVal) => {
const containerClass = computed(() => {
return {
'layout-mobile-active': layoutState.staticMenuMobileActive,
'layout-overlay': layoutConfig.menuMode === 'overlay',
'layout-overlay-active': layoutState.overlayMenuActive,
'layout-static': layoutConfig.menuMode === 'static',
'layout-static-inactive': layoutState.staticMenuDesktopInactive && layoutConfig.menuMode === 'static',
'layout-overlay-active': layoutState.overlayMenuActive,
'layout-mobile-active': layoutState.staticMenuMobileActive,
};
});
@ -43,13 +43,6 @@ function bindOutsideClickListener() {
}
}
function unbindOutsideClickListener() {
if (outsideClickListener.value) {
document.removeEventListener('click', outsideClickListener.value);
outsideClickListener.value = null;
}
}
function isOutsideClicked(event: Event) {
const sidebarEl = document.querySelector('.layout-sidebar')!;
const topbarEl = document.querySelector('.layout-menu-button')!;
@ -61,6 +54,13 @@ function isOutsideClicked(event: Event) {
topbarEl.contains(event.target as never)
);
}
function unbindOutsideClickListener() {
if (outsideClickListener.value) {
document.removeEventListener('click', outsideClickListener.value);
outsideClickListener.value = null;
}
}
</script>
<template>

View File

@ -2,7 +2,7 @@
import AppConfigurator from './AppConfigurator.vue';
import { useLayout } from './composables/layout';
const { toggleMenu, toggleDarkMode, isDarkTheme } = useLayout();
const { isDarkTheme, toggleDarkMode, toggleMenu } = useLayout();
</script>
<template>

View File

@ -1,21 +1,21 @@
import { computed, reactive } from 'vue';
const layoutConfig = reactive({
darkTheme: false,
menuMode: 'static',
preset: 'Aura',
primary: 'emerald',
surface: null,
darkTheme: false,
menuMode: 'static',
});
const layoutState = reactive({
staticMenuDesktopInactive: false,
activeMenuItem: null,
configSidebarVisible: false,
menuHoverActive: false,
overlayMenuActive: false,
profileSidebarVisible: false,
configSidebarVisible: false,
staticMenuDesktopInactive: false,
staticMenuMobileActive: false,
menuHoverActive: false,
activeMenuItem: null,
});
export function useLayout() {
@ -59,14 +59,14 @@ export function useLayout() {
const getSurface = computed(() => layoutConfig.surface);
return {
layoutConfig,
layoutState,
toggleMenu,
isSidebarActive,
isDarkTheme,
getPrimary,
getSurface,
isDarkTheme,
isSidebarActive,
layoutConfig,
layoutState,
setActiveMenuItem,
toggleDarkMode,
toggleMenu,
};
}

View File

@ -1,9 +1,9 @@
import type { PopconfirmProps } from 'ant-design-vue';
type NotUndefined<T> = T extends undefined ? never : T;
type PopconfirmOnConfirmParameters = Parameters<NotUndefined<PopconfirmProps['onConfirm']>>;
export type HPopconfirmProps = {
description: PopconfirmProps['description'];
onConfirm?: (...args: PopconfirmOnConfirmParameters) => Promise<void>;
title: PopconfirmProps['title'];
};
type NotUndefined<T> = T extends undefined ? never : T;
type PopconfirmOnConfirmParameters = Parameters<NotUndefined<PopconfirmProps['onConfirm']>>;

View File

@ -1,18 +1,18 @@
<script setup lang="ts">
import 'orbpro/style/widgets.css';
import {
Viewer,
GoogleMaps,
Math as CesiumMath,
Cartographic,
Cartesian3,
viewerReferenceFrameMixin,
TileMapServiceImageryProvider,
ImageryLayer,
Cartographic,
Math as CesiumMath,
DynamicTimeline,
// VERSION,
EmbeddedTileServiceImageryProvider,
DynamicTimeline,
GoogleMaps,
ImageryLayer,
SpaceEntity,
TileMapServiceImageryProvider,
Viewer,
viewerReferenceFrameMixin,
} from 'orbpro';
import { demoOrbitGeneration } from './fns';
@ -21,25 +21,25 @@ let viewer: Viewer;
onMounted(() => {
viewer = new Viewer('cesiumContainer', {
// globe: false, // 地球
baseLayerPicker: true,
homeButton: true, // Home按钮
fullscreenButton: !true, // 全屏按钮
geocoder: true, // = IonGeocodeProviderType.DEFAULT] - 在使用Geocoder小部件进行搜索时使用的地理编码服务或服务。如果设置为false则不会创建Geocoder小部件。
infoBox: true, // InfoBox小部件。
navigationHelpButton: !true, // 是否显示导航帮助按钮
projectionPicker: !true, // 投影选择器
sceneModePicker: true, // 是否显示场景模式选择器(2D/3D切换)
animation: true, // 是否创建动画小部件
animationContainer: !true,
// globe: false, // 地球
baseLayerPicker: true,
bottomContainer: document.createElement('p'), // The DOM element or ID that will contain the bottomContainer. If not specified, the bottomContainer is added to the widget itself.
fullscreenButton: !true, // 全屏按钮
geocoder: true, // = IonGeocodeProviderType.DEFAULT] - 在使用Geocoder小部件进行搜索时使用的地理编码服务或服务。如果设置为false则不会创建Geocoder小部件。
homeButton: true, // Home按钮
infoBox: true, // InfoBox小部件。
navigationHelpButton: !true, // 是否显示导航帮助按钮
orderIndependentTranslucency: false, // 顺序无关透明度
projectionPicker: !true, // 投影选择器
requestRenderMode: !true, // 如果为真渲染帧将仅在场景内部发生变化时需要时发生。启用此功能可以减少应用程序的CPU/GPU使用率并在移动设备上节省更多电量但在此模式下需要使用{@link Scene#requestRender}显式渲染新帧。在API的其他部分对场景进行更改后在许多情况下都需要这样做。请参阅{@link https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/|使用显式渲染提高性能}。
sceneModePicker: true, // 是否显示场景模式选择器(2D/3D切换)
selectionIndicator: true,
shadows: true, // Determines if shadows are cast by light sources.
showRenderLoopErrors: true, // 如果为真当发生渲染循环错误时此小部件将自动向用户显示包含错误的HTML面板。
timeline: !true, // If set to false, the Timeline widget will not be created.
timelineContainer: true,
selectionIndicator: true,
requestRenderMode: !true, // 如果为真渲染帧将仅在场景内部发生变化时需要时发生。启用此功能可以减少应用程序的CPU/GPU使用率并在移动设备上节省更多电量但在此模式下需要使用{@link Scene#requestRender}显式渲染新帧。在API的其他部分对场景进行更改后在许多情况下都需要这样做。请参阅{@link https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/|使用显式渲染提高性能}。
showRenderLoopErrors: true, // 如果为真当发生渲染循环错误时此小部件将自动向用户显示包含错误的HTML面板。
bottomContainer: document.createElement('p'), // The DOM element or ID that will contain the bottomContainer. If not specified, the bottomContainer is added to the widget itself.
orderIndependentTranslucency: false, // 顺序无关透明度
shadows: true, // Determines if shadows are cast by light sources.
});
viewer.scene.debugShowFramesPerSecond = true;

View File

@ -8,8 +8,8 @@ import {
SpaceCatalogDataSource,
SpaceEntity,
VerticalOrigin,
viewerReferenceFrameMixin,
type Viewer,
viewerReferenceFrameMixin,
} from 'orbpro';
declare global {
@ -26,85 +26,85 @@ export async function demoOrbitGeneration(viewer: Viewer) {
// https://spacedatastandards.org/#/standards?search=OMM
const ISS = new SpaceEntity({
id: '25544',
label: {
backgroundColor: new Color(0.1, 0.1, 0.1, 0.9),
font: `1rem Helvetica`,
pixelOffset: new Cartesian2(10, 0),
pixelOffsetScaleByDistance: new NearFarScalar(1.5e2, 3.0, 1.5e7, 0.5),
scaleByDistance: new NearFarScalar(1.5e2, 1.5, 13.0e7, 0.0),
show: false,
showBackground: true,
},
name: 'ISS',
point: {
pixelSize: 1,
},
label: {
show: false,
font: `1rem Helvetica`,
showBackground: true,
backgroundColor: new Color(0.1, 0.1, 0.1, 0.9),
pixelOffset: new Cartesian2(10, 0),
scaleByDistance: new NearFarScalar(1.5e2, 1.5, 13.0e7, 0.0),
pixelOffsetScaleByDistance: new NearFarScalar(1.5e2, 3.0, 1.5e7, 0.5),
},
viewFrom: new Cartesian3(-1678500.7493507154, -17680994.63403464, 24667690.486357275),
});
ISS.position.loadOMM({
/* CCSDS OMM版本 */ CCSDS_OMM_VERS: 0.0,
/* 创建日期(ISO 8601 UTC格式) */ CREATION_DATE: null,
/* 创建者 */ ORIGINATOR: null,
/* 卫星名称 */ OBJECT_NAME: 'ISS (ZARYA)',
/* 国际标识符(YYYY-NNNAAA) */ OBJECT_ID: '1998-067A',
/* 中心名称(例如:EARTH, MARS) */ CENTER_NAME: null,
/* 参考坐标系 */ REFERENCE_FRAME: 2,
/* 参考坐标系历元(ISO 8601 UTC格式) */ REFERENCE_FRAME_EPOCH: null,
/* 时间系统[M, UTC] */ TIME_SYSTEM: 11,
/* 平均元素理论 */ MEAN_ELEMENT_THEORY: 0,
/* 注释 */ COMMENT: null,
/* 平均开普勒根数的历元(ISO 8601 UTC格式) */ EPOCH: '2024-05-08T19:52:52.426848',
/* 半长轴(km) */ SEMI_MAJOR_AXIS: 0,
/* 平均运动(圈/天) */ MEAN_MOTION: 15.51025615,
/* 偏心率(无量纲) */ ECCENTRICITY: 0.0003349,
/* 轨道倾角(度) */ INCLINATION: 51.6355,
/* 升交点赤经(度) */ RA_OF_ASC_NODE: 150.5366,
/* 近地点幅角(度) */ ARG_OF_PERICENTER: 149.2285,
/* 平近点角(度) */ MEAN_ANOMALY: 210.8902,
/* 引力常数(km³/s²) */ GM: 0,
/* 质量(kg) */ MASS: 0,
/* 太阳辐射面积(m²) */ SOLAR_RAD_AREA: 0,
/* 太阳辐射压系数(无量纲) */ SOLAR_RAD_COEFF: 0,
/* 大气阻力面积(m²) */ DRAG_AREA: 0,
/* 大气阻力系数(无量纲) */ DRAG_COEFF: 0,
/* 星历类型(SGP,SGP4等) */ EPHEMERIS_TYPE: 0,
/* 分类类型,默认'U' */ CLASSIFICATION_TYPE: 'U',
/* NORAD编号 */ NORAD_CAT_ID: 25544,
/* 根数组编号 */ ELEMENT_SET_NO: 999,
/* 历元时的圈次 */ REV_AT_EPOCH: 45244,
/* 气阻系数(1/地球半径) */ BSTAR: 0.00028217,
/* 平均运动一阶导数(圈/天²) */ MEAN_MOTION_DOT: 0.00016275,
/* 平均运动二阶导数(圈/天³) */ MEAN_MOTION_DDOT: 0,
/* 协方差矩阵参考坐标系 */ COV_REFERENCE_FRAME: 23,
/* CCSDS OMM版本 */ ARG_OF_PERICENTER: 149.2285,
/* 创建日期(ISO 8601 UTC格式) */ BSTAR: 0.00028217,
/* 创建者 */ CCSDS_OMM_VERS: 0.0,
/* 卫星名称 */ CENTER_NAME: null,
/* 国际标识符(YYYY-NNNAAA) */ CLASSIFICATION_TYPE: 'U',
/* 中心名称(例如:EARTH, MARS) */ COMMENT: null,
/* 参考坐标系 */ COV_REFERENCE_FRAME: 23,
/* 参考坐标系历元(ISO 8601 UTC格式) */ CREATION_DATE: null,
/* 时间系统[M, UTC] */ CX_DOT_X: 0,
/* 平均元素理论 */ CX_DOT_X_DOT: 0,
/* 注释 */ CX_DOT_Y: 0,
/* 平均开普勒根数的历元(ISO 8601 UTC格式) */ CX_DOT_Z: 0,
/* 半长轴(km) */ CX_X: 0,
/* 平均运动(圈/天) */ CY_DOT_X: 0,
/* 偏心率(无量纲) */ CY_DOT_X_DOT: 0,
/* 轨道倾角(度) */ CY_DOT_Y: 0,
/* 升交点赤经(度) */ CY_DOT_Y_DOT: 0,
/* 近地点幅角(度) */ CY_DOT_Z: 0,
/* 平近点角(度) */ CY_X: 0,
/* 引力常数(km³/s²) */ CY_Y: 0,
/* 质量(kg) */ CZ_DOT_X: 0,
/* 太阳辐射面积(m²) */ CZ_DOT_X_DOT: 0,
/* 太阳辐射压系数(无量纲) */ CZ_DOT_Y: 0,
/* 大气阻力面积(m²) */ CZ_DOT_Y_DOT: 0,
/* 大气阻力系数(无量纲) */ CZ_DOT_Z: 0,
/* 星历类型(SGP,SGP4等) */ CZ_DOT_Z_DOT: 0,
/* 分类类型,默认'U' */ CZ_X: 0,
/* NORAD编号 */ CZ_Y: 0,
/* 根数组编号 */ CZ_Z: 0,
/* 历元时的圈次 */ DRAG_AREA: 0,
/* 气阻系数(1/地球半径) */ DRAG_COEFF: 0,
/* 平均运动一阶导数(圈/天²) */ ECCENTRICITY: 0.0003349,
/* 平均运动二阶导数(圈/天³) */ ELEMENT_SET_NO: 999,
/* 协方差矩阵参考坐标系 */ EPHEMERIS_TYPE: 0,
/* --- 位置/速度协方差矩阵(6x6下三角) --- */
/* 位置协方差矩阵元素(km²) */ CX_X: 0,
/* 位置协方差矩阵元素(km²) */ CY_X: 0,
/* 位置协方差矩阵元素(km²) */ CY_Y: 0,
/* 位置协方差矩阵元素(km²) */ CZ_X: 0,
/* 位置协方差矩阵元素(km²) */ CZ_Y: 0,
/* 位置协方差矩阵元素(km²) */ CZ_Z: 0,
/* 速度-位置协方差矩阵元素(km²/s) */ CX_DOT_X: 0,
/* 速度-位置协方差矩阵元素(km²/s) */ CX_DOT_Y: 0,
/* 速度-位置协方差矩阵元素(km²/s) */ CX_DOT_Z: 0,
/* 速度协方差矩阵元素(km²/s²) */ CX_DOT_X_DOT: 0,
/* 速度-位置协方差矩阵元素(km²/s) */ CY_DOT_X: 0,
/* 速度-位置协方差矩阵元素(km²/s) */ CY_DOT_Y: 0,
/* 速度-位置协方差矩阵元素(km²/s) */ CY_DOT_Z: 0,
/* 速度协方差矩阵元素(km²/s²) */ CY_DOT_X_DOT: 0,
/* 速度协方差矩阵元素(km²/s²) */ CY_DOT_Y_DOT: 0,
/* 速度-位置协方差矩阵元素(km²/s) */ CZ_DOT_X: 0,
/* 速度-位置协方差矩阵元素(km²/s) */ CZ_DOT_Y: 0,
/* 速度-位置协方差矩阵元素(km²/s) */ CZ_DOT_Z: 0,
/* 速度协方差矩阵元素(km²/s²) */ CZ_DOT_X_DOT: 0,
/* 速度协方差矩阵元素(km²/s²) */ CZ_DOT_Y_DOT: 0,
/* 速度协方差矩阵元素(km²/s²) */ CZ_DOT_Z_DOT: 0,
/* 位置协方差矩阵元素(km²) */ EPOCH: '2024-05-08T19:52:52.426848',
/* 位置协方差矩阵元素(km²) */ GM: 0,
/* 位置协方差矩阵元素(km²) */ INCLINATION: 51.6355,
/* 位置协方差矩阵元素(km²) */ MASS: 0,
/* 位置协方差矩阵元素(km²) */ MEAN_ANOMALY: 210.8902,
/* 位置协方差矩阵元素(km²) */ MEAN_ELEMENT_THEORY: 0,
/* 速度-位置协方差矩阵元素(km²/s) */ MEAN_MOTION: 15.51025615,
/* 速度-位置协方差矩阵元素(km²/s) */ MEAN_MOTION_DDOT: 0,
/* 速度-位置协方差矩阵元素(km²/s) */ MEAN_MOTION_DOT: 0.00016275,
/* 速度协方差矩阵元素(km²/s²) */ NORAD_CAT_ID: 25544,
/* 速度-位置协方差矩阵元素(km²/s) */ OBJECT_ID: '1998-067A',
/* 速度-位置协方差矩阵元素(km²/s) */ OBJECT_NAME: 'ISS (ZARYA)',
/* 速度-位置协方差矩阵元素(km²/s) */ ORIGINATOR: null,
/* 速度协方差矩阵元素(km²/s²) */ RA_OF_ASC_NODE: 150.5366,
/* 速度协方差矩阵元素(km²/s²) */ REFERENCE_FRAME: 2,
/* 速度-位置协方差矩阵元素(km²/s) */ REFERENCE_FRAME_EPOCH: null,
/* 速度-位置协方差矩阵元素(km²/s) */ REV_AT_EPOCH: 45244,
/* 速度-位置协方差矩阵元素(km²/s) */ SEMI_MAJOR_AXIS: 0,
/* 速度协方差矩阵元素(km²/s²) */ SOLAR_RAD_AREA: 0,
/* 速度协方差矩阵元素(km²/s²) */ SOLAR_RAD_COEFF: 0,
/* 速度协方差矩阵元素(km²/s²) */ TIME_SYSTEM: 11,
/* --- 用户自定义字段 --- */
/* 用户自定义BIP-0044类型 */ USER_DEFINED_BIP_0044_TYPE: 0,
/* 用户自定义对象标识符 */ USER_DEFINED_OBJECT_DESIGNATOR: null,
/* 用户自定义地球模型 */ USER_DEFINED_EARTH_MODEL: null,
/* 用户自定义历元时间戳 */ USER_DEFINED_EPOCH_TIMESTAMP: 0,
/* 用户自定义微秒数 */ USER_DEFINED_MICROSECONDS: 0,
/* 用户自定义对象标识符 */ USER_DEFINED_EARTH_MODEL: null,
/* 用户自定义地球模型 */ USER_DEFINED_EPOCH_TIMESTAMP: 0,
/* 用户自定义历元时间戳 */ USER_DEFINED_MICROSECONDS: 0,
/* 用户自定义微秒数 */ USER_DEFINED_OBJECT_DESIGNATOR: null,
});
ISS.point!.pixelSize = 10 as any;
@ -112,8 +112,8 @@ export async function demoOrbitGeneration(viewer: Viewer) {
MakeBillboardLabel({
entity: ISS,
text: 'ISS-text',
fontSize: 36,
text: 'ISS-text',
verticalOrigin: VerticalOrigin.CENTER,
});

View File

@ -4,10 +4,10 @@ import { createReusableTemplate } from '@vueuse/core';
const TemplateFoo = createReusableTemplate<
{ msg: string },
{
default: object;
slotNameee: {
slotPropName: string;
};
default: object;
}
>();
</script>

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
const baseURL = '/fake-api';
let fakeApiResult = $ref<Record<string, unknown> | null>(null);
let fakeApiResult = $ref<null | Record<string, unknown>>(null);
fetch(`${baseURL}/mock/get-user-info`)
.then((response) => response.json())

View File

@ -1,10 +1,12 @@
## 配置项目
- https://github.dev/CesiumGS/cesium-vite-example
- https://cesium.com/blog/2024/02/13/configuring-vite-or-webpack-for-cesiumjs/
- https://cesium.com/learn/cesiumjs-learn/cesiumjs-quickstart/
- vite-plugin-cesium
## 参考
- https://www.npmjs.com/package/vue-cesium
- https://zouyaoji.top/vue-cesium/#/zh-CN/component/controls/vc-navigation
- https://cesium.pages.dev/

View File

@ -8,64 +8,64 @@ export function cesium_init() {
// 复写原型方法 用于timeline组件日期格式化
// @ts-expect-error node_modules/@cesium/widgets/Source/Timeline/Timeline.js
Cesium.Timeline.prototype.makeLabel = function (time) {
let minutes = 0 - new Date().getTimezoneOffset();
let dataZone8 = Cesium.JulianDate.addMinutes(time, minutes, new Cesium.JulianDate());
const minutes = 0 - new Date().getTimezoneOffset();
const dataZone8 = Cesium.JulianDate.addMinutes(time, minutes, new Cesium.JulianDate());
return Cesium.JulianDate.toIso8601(dataZone8).slice(0, 19);
};
// cesium-viewer-bottom
const viewer = new Cesium.Viewer('cesiumContainer', {
animation: true, // 是否创建动画小部件
// globe: false, // 地球
baseLayerPicker: true,
fullscreenButton: !true, // 全屏按钮
geocoder: true, // = IonGeocodeProviderType.DEFAULT] - 在使用Geocoder小部件进行搜索时使用的地理编码服务或服务。如果设置为false则不会创建Geocoder小部件。
homeButton: true, // Home按钮
infoBox: true, // InfoBox小部件。
navigationHelpButton: !true, // 是否显示导航帮助按钮
orderIndependentTranslucency: false, // 顺序无关透明度
projectionPicker: !true, // 投影选择器
requestRenderMode: !true, // 如果为真渲染帧将仅在场景内部发生变化时需要时发生。启用此功能可以减少应用程序的CPU/GPU使用率并在移动设备上节省更多电量但在此模式下需要使用{@link Scene#requestRender}显式渲染新帧。在API的其他部分对场景进行更改后在许多情况下都需要这样做。请参阅{@link https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/|使用显式渲染提高性能}。
sceneModePicker: true, // 是否显示场景模式选择器(2D/3D切换)
selectionIndicator: true,
shadows: true, // Determines if shadows are cast by light sources.
/* animationContainer: !true, */
/* timelineContainer: true, */
/* bottomContainer: document.createElement('p'), // The DOM element or ID that will contain the bottomContainer. If not specified, the bottomContainer is added to the widget itself. */
shouldAnimate: true,
// globe: false, // 地球
baseLayerPicker: true,
homeButton: true, // Home按钮
fullscreenButton: !true, // 全屏按钮
geocoder: true, // = IonGeocodeProviderType.DEFAULT] - 在使用Geocoder小部件进行搜索时使用的地理编码服务或服务。如果设置为false则不会创建Geocoder小部件。
infoBox: true, // InfoBox小部件。
navigationHelpButton: !true, // 是否显示导航帮助按钮
projectionPicker: !true, // 投影选择器
sceneModePicker: true, // 是否显示场景模式选择器(2D/3D切换)
animation: true, // 是否创建动画小部件
timeline: true, // If set to false, the Timeline widget will not be created.
selectionIndicator: true,
requestRenderMode: !true, // 如果为真渲染帧将仅在场景内部发生变化时需要时发生。启用此功能可以减少应用程序的CPU/GPU使用率并在移动设备上节省更多电量但在此模式下需要使用{@link Scene#requestRender}显式渲染新帧。在API的其他部分对场景进行更改后在许多情况下都需要这样做。请参阅{@link https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/|使用显式渲染提高性能}。
showRenderLoopErrors: true, // 如果为真当发生渲染循环错误时此小部件将自动向用户显示包含错误的HTML面板。
orderIndependentTranslucency: false, // 顺序无关透明度
shadows: true, // Determines if shadows are cast by light sources.
timeline: true, // If set to false, the Timeline widget will not be created.
});
/* if ($__DEV__) */ viewer.scene.debugShowFramesPerSecond = true;
// 时间格式化
let minutes = 0 - new Date().getTimezoneOffset(); // 0 - (-480);
const minutes = 0 - new Date().getTimezoneOffset(); // 0 - (-480);
viewer.animation.viewModel.timeFormatter = function (date, _viewModel) {
let dataZone8 = Cesium.JulianDate.addMinutes(date, minutes, new Cesium.JulianDate());
const dataZone8 = Cesium.JulianDate.addMinutes(date, minutes, new Cesium.JulianDate());
return Cesium.JulianDate.toIso8601(dataZone8).slice(11, 19);
};
viewer.animation.viewModel.dateFormatter = function (date, _viewModel) {
let dataZone8 = Cesium.JulianDate.addMinutes(date, minutes, new Cesium.JulianDate());
const dataZone8 = Cesium.JulianDate.addMinutes(date, minutes, new Cesium.JulianDate());
return Cesium.JulianDate.toIso8601(dataZone8).slice(0, 10);
};
//高德卫星地图
let gaoDeSatelliteImgLayer = new Cesium.UrlTemplateImageryProvider({
url: 'https://webst02.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}',
minimumLevel: 3,
const gaoDeSatelliteImgLayer = new Cesium.UrlTemplateImageryProvider({
maximumLevel: 18,
minimumLevel: 3,
tilingScheme: new Cesium.WebMercatorTilingScheme(),
url: 'https://webst02.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}',
});
const customLayerViewModel = new Cesium.ProviderViewModel({
name: '高德地图',
iconUrl: 'gaodeImage.png',
tooltip: '高德地图',
category: 'Cesium ion', // 或 'Other 、Cesium ion'、'Bing Maps' 等
creationFunction: function () {
return gaoDeSatelliteImgLayer;
},
iconUrl: 'gaodeImage.png',
name: '高德地图',
tooltip: '高德地图',
});
// 设置高德地图为默认图层
viewer.baseLayerPicker.viewModel.imageryProviderViewModels.unshift(customLayerViewModel);

View File

@ -1,6 +1,7 @@
import type { Viewer } from 'cesium';
import * as Cesium from 'cesium';
import { twoline2satrec, propagate, gstime, eciToEcf } from 'satellite.js';
import { eciToEcf, gstime, propagate, twoline2satrec } from 'satellite.js';
export async function demoOrbitGeneration(viewer: Viewer) {
const tle = `STARLINK-11371 [DTC]
@ -74,36 +75,36 @@ export async function demoOrbitGeneration(viewer: Viewer) {
viewer.entities.add({
name: `${satelliteName} Orbit`,
polyline: {
material: new Cesium.PolylineGlowMaterialProperty({
color: Cesium.Color.BLUE,
glowPower: 0.2,
}),
positions: pointsArray,
width: 2,
material: new Cesium.PolylineGlowMaterialProperty({
glowPower: 0.2,
color: Cesium.Color.BLUE,
}),
},
});
await PromiseConfirmationService({ message: '添加卫星实体' });
// 添加卫星实体
const satellite = viewer.entities.add({
label: {
fillColor: Cesium.Color.WHITE,
font: '14pt sans-serif',
outlineColor: Cesium.Color.BLACK,
outlineWidth: 2,
pixelOffset: new Cesium.Cartesian2(0, -10),
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
text: satelliteName,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
},
name: satelliteName,
position: currentPosition,
point: {
pixelSize: 10,
color: Cesium.Color.YELLOW,
outlineColor: Cesium.Color.WHITE,
outlineWidth: 2,
pixelSize: 10,
},
label: {
text: satelliteName,
font: '14pt sans-serif',
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
fillColor: Cesium.Color.WHITE,
outlineColor: Cesium.Color.BLACK,
outlineWidth: 2,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
pixelOffset: new Cesium.Cartesian2(0, -10),
},
position: currentPosition,
// billboard: {
// image: '/assets/satellite.png', // 您需要添加一个卫星图标
// scale: 0.5,

View File

@ -3,6 +3,7 @@ import type { Viewer } from 'cesium';
import { cesium_init } from './cesium-helper/00.cesium-init';
import { demoOrbitGeneration } from './cesium-helper/01.x';
let viewer: Viewer;
onMounted(async () => {
viewer = cesium_init();

View File

@ -10,8 +10,8 @@ export const usePageData = defineBasicLoader(
return { idFromPreviousPage: route.params.id, someOtherData: 'someOtherData' };
},
{
lazy: false,
commit: 'immediate',
lazy: false,
// - `immediate`: the data is committed as soon as it is loaded.
// - `after-load`: the data is committed after all non-lazy loaders have finished loading.
// - `immediate`:数据在加载后立即提交。
@ -28,8 +28,8 @@ definePage({
});
const {
data: pageData, // the data returned by the loader
isLoading, // a boolean indicating if the loader is fetching data
error, // an error object if the loader failed
isLoading, // a boolean indicating if the loader is fetching data
reload, // a function to refetch the data without navigating
} = usePageData();

View File

@ -13,11 +13,11 @@ async function getUserById(userId: string, { signal }: { signal?: AbortSignal })
}
export const useUserData = defineColadaLoader('DataLoadersIdSub1UserId', {
key: (to) => ['users', to.params.userId],
async query(to, { signal }) {
console.debug('[defineColadaLoader] query');
return getUserById(to.params.userId, { signal });
},
key: (to) => ['users', to.params.userId],
// Keep the data "fresh" 10 seconds to avoid fetching the same data too often
// 保持数据“新鲜”10秒以避免过于频繁地获取相同的数据
staleTime: 1000 * 10,
@ -31,7 +31,7 @@ export const usePageData = defineBasicLoader('DataLoadersIdSub1UserId', async (r
</script>
<script setup lang="ts">
const { data: user, status, error, isLoading, reload, refresh } = useUserData();
const { data: user, error, isLoading, refresh, reload, status } = useUserData();
const route = useRoute('DataLoadersIdSub1UserId');
</script>

View File

@ -4,13 +4,13 @@ import { routes } from 'vue-router/auto-routes';
definePage({ meta: { title: '首页' } });
useHead({
// Classes
bodyAttrs: { class: { overflow: true } },
// Template params
templateParams: { separator: '|', siteName: 'My App' },
// Titles
title: 'Hello World',
titleTemplate: '%s %separator %siteName',
// Template params
templateParams: { separator: '|', siteName: 'My App' },
// Classes
bodyAttrs: { class: { overflow: true } },
// Deduping
// script: [{ key: '123', src: '/script.js' }],
});
@ -21,9 +21,9 @@ const FComponent: import('vue').FunctionalComponent<{ prop: string }> = (props,
<>
<a
class="green"
target="_blank"
rel="noopener noreferrer"
href="https://cn.vuejs.org/guide/extras/render-function#typing-functional-components"
rel="noopener noreferrer"
target="_blank"
>
函数式组件: https://cn.vuejs.org/guide/extras/render-function#typing-functional-components
</a>

View File

@ -2,11 +2,11 @@
const structuredClone = window.structuredClone || ((obj) => JSON.parse(JSON.stringify(obj)));
const K_INITIAL_STATE = deepFreeze({
list: [] as Record<string, never>[],
page: 0,
loading: false,
complete: false,
error: null as unknown,
list: [] as Record<string, never>[],
loading: false,
page: 0,
});
let page3ShouldError = true;
@ -33,6 +33,8 @@ const refresh = () => {
loadMore();
};
type CurrentUse = 'use-intersection-observer-infinite-loading' | 'van-list';
async function loadMore() {
if (state.loading) return;
@ -59,8 +61,6 @@ async function loadMore() {
state.loading = false;
}
}
type CurrentUse = 'van-list' | 'use-intersection-observer-infinite-loading';
const currentUse = ref<CurrentUse>((sessionStorage.getItem('currentUse') as CurrentUse) || 'van-list');
watchEffect(() => {
sessionStorage.setItem('currentUse', currentUse.value);

View File

@ -3,8 +3,8 @@ import dialogContent from './__dialog-content.vue';
const dynamicComponent = defineComponent({
props: {
'defining-props': {
type: Object as PropType<Record<string, unknown>>,
default: () => ({}),
type: Object as PropType<Record<string, unknown>>,
},
},
setup(props, ctx) {
@ -24,12 +24,6 @@ const dynamicComponent = defineComponent({
// https://primevue.org/dynamicdialog/
export const openDialog = async () => {
const dialog1 = DialogService.open(dynamicComponent, {
props: {
header: '对话框1 可以拖动',
position: 'bottomleft',
draggable: true,
pt: { mask: { class: 'backdrop-blur-sm' } }, // 相当于: pt:mask:class="backdrop-blur-sm"
},
data: {
inject接收: '通过 DynamicDialogOptions.data 传递的数据',
},
@ -39,8 +33,14 @@ export const openDialog = async () => {
'defining-props': {
'用-props-接收': '定义在-DynamicDialogOptions.emits-的数据',
},
attrs: '定义在-DynamicDialogOptions.emits-的数据,',
onClose: () => dialog1.close(),
attrs: '定义在-DynamicDialogOptions.emits-的数据,',
},
props: {
draggable: true,
header: '对话框1 可以拖动',
position: 'bottomleft',
pt: { mask: { class: 'backdrop-blur-sm' } }, // 相当于: pt:mask:class="backdrop-blur-sm"
},
});
@ -58,17 +58,17 @@ export const openDialog = async () => {
export const openConfirm = async () => {
ConfirmationService.require({
message: '确定要继续吗?',
header: '确认',
icon: 'pi pi-exclamation-triangle',
rejectProps: { label: '取消', severity: 'secondary', outlined: true },
accept: () => {
ToastService.add({ detail: '您已同意操作', life: 3000, severity: 'info', summary: '已确认' });
},
// rejectProps: { style: { display: 'none' } },
acceptProps: { label: '确定' },
accept: () => {
ToastService.add({ severity: 'info', summary: '已确认', life: 3000, detail: '您已同意操作' });
},
header: '确认',
icon: 'pi pi-exclamation-triangle',
message: '确定要继续吗?',
reject: () => {
ToastService.add({ severity: 'error', summary: '已取消', life: 3000, detail: '您已取消操作' });
ToastService.add({ detail: '您已取消操作', life: 3000, severity: 'error', summary: '已取消' });
},
rejectProps: { label: '取消', outlined: true, severity: 'secondary' },
});
};

View File

@ -2,10 +2,10 @@
import { $enum } from 'ts-enum-util';
enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT',
Up = 'UP',
}
enum TestEnum {
@ -15,25 +15,25 @@ enum TestEnum {
const visitValue = (value: Direction | null | undefined) => {
$enum.visitValue(value).with({
[Direction.Up]: () => {},
[Direction.Down]: () => {},
[Direction.Left]: () => {},
[Direction.Right]: () => {},
[$enum.handleNull]: () => {},
[$enum.handleUndefined]: () => {},
[$enum.handleUnexpected]: () => {},
[Direction.Down]: () => {},
[Direction.Left]: () => {},
[Direction.Right]: () => {},
[Direction.Up]: () => {},
});
};
const mapValue = (value: Direction | null | undefined) => {
return $enum.mapValue(value).with({
[Direction.Up]: 'Up',
[Direction.Down]: 'Down',
[Direction.Left]: 'Left',
[Direction.Right]: 'Right',
[$enum.handleNull]: 'Null',
[$enum.handleUndefined]: 'Undefined',
[$enum.handleUnexpected]: 'Unexpected',
[Direction.Down]: 'Down',
[Direction.Left]: 'Left',
[Direction.Right]: 'Right',
[Direction.Up]: 'Up',
});
};
</script>

View File

@ -1,7 +1,7 @@
type AutoInstallModule = { [K: string]: unknown; install?: UserPlugin };
type UserPlugin = (ctx: UserPluginContext) => void;
// https://github.com/antfu-collective/vitesse/blob/47618e72dfba76c77b9b85b94784d739e35c492b/src/modules/README.md
type UserPluginContext = { app: import('vue').App<Element> };
type UserPlugin = (ctx: UserPluginContext) => void;
type AutoInstallModule = { install?: UserPlugin; [K: string]: unknown };
export function setupPlugins(app: import('vue').App, modules: AutoInstallModule | Record<string, unknown>) {
console.group('🔌 Plugins');

View File

@ -12,17 +12,17 @@ export function install({ app }: { app: import('vue').App<Element> }) {
app.use(PrimeVue, {
locale: {
...zhCN['zh-CN'],
completed: '已上传',
noFileChosenMessage: '未选择文件',
pending: '待上传',
completed: '已上传',
}, // usePrimeVue().config.locale
theme: {
preset: Aura,
options: {
prefix: 'p',
darkModeSelector: '.app-dark' /* 'system' */,
cssLayer: false,
darkModeSelector: '.app-dark' /* 'system' */,
prefix: 'p',
},
preset: Aura,
},
});
}

View File

@ -8,7 +8,6 @@ const setupLayoutsResult = setupLayouts(routes);
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: /* routes ?? */ setupLayoutsResult,
strict: true,
scrollBehavior: (_to, _from, savedPosition) => {
if (savedPosition) {
return savedPosition;
@ -16,6 +15,7 @@ const router = createRouter({
return { left: 0, top: 0 };
}
},
strict: true,
});
if (import.meta.hot) handleHotUpdate(router);
if ($__DEV__) Object.assign(window, { router });
@ -23,7 +23,7 @@ router.onError((error) => {
console.debug('🚨 [router error]: ', error);
});
export { router, setupLayoutsResult, createGetRoutes };
export { createGetRoutes, router, setupLayoutsResult };
export function install({ app }: { app: import('vue').App<Element> }) {
app
// Register the plugin before the router

View File

@ -14,7 +14,7 @@
declare module 'vue' {
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
interface HTMLAttributes
extends Partial<Record<import('@unocss/preset-attributify').AttributifyNames, string | boolean>> {}
extends Partial<Record<import('@unocss/preset-attributify').AttributifyNames, boolean | string>> {}
}
export {};

View File

@ -1,11 +1,11 @@
export async function PromiseConfirmationService({ message }: { message: string }) {
return await new Promise<boolean>((reslove) => {
ConfirmationService.require({
position: 'bottomright',
modal: false,
accept: () => reslove(true),
header: '提示',
message: message,
accept: () => reslove(true),
modal: false,
position: 'bottomright',
reject: () => reslove(false),
});
});

View File

@ -1,4 +1,4 @@
import { test, expect } from '@playwright/test';
import { expect, test } from '@playwright/test';
test('接口请求页面', async ({ page }) => {
await page.goto('/api');

View File

@ -3,52 +3,6 @@ import { defineConfig, presetAttributify, presetWind3, transformerDirectives, tr
// import presetChinese, { chineseTypography } from 'unocss-preset-chinese';
export default defineConfig({
theme: {
// https://unocss.dev/presets/wind#differences-from-tailwind-css
// https://www.npmjs.com/package/unocss-preset-animations
// https://github.com/whatnickcodes/unocss-preset-tailwindcss-motion
animation: {
keyframes: {
scalein: '{0% { opacity: 0; transform: scaleY(0.8); } 100% { opacity: 1; transform: scaleY(1); }}',
fadeout: '{0% { opacity: 1; } 100% { opacity: 0; }}',
},
durations: {
scalein: '0.15s',
fadeout: '0.15s',
},
timingFns: {
scalein: 'linear',
fadeout: 'linear',
},
properties: {
scalein: {
transition: 'transform 0.12s cubic-bezier(0, 0, 0.2, 1), opacity 0.12s cubic-bezier(0, 0, 0.2, 1)',
},
fadeout: {},
},
counts: {
scalein: '1',
fadeout: '1',
},
},
// https://unocss.dev/config/theme#usage
colors: {
'surface-0': 'var(--p-surface-0)', // text-surface-0
'surface-900': 'var(--p-surface-900)', // text-surface-900
},
},
shortcuts: [
{
'logo-transform': 'i-icon:pacman w-6em h-6em transform transition-800',
pacman: 'i-icon:pacman text-(pink 36)',
},
{
// https://github.com/primefaces/tailwindcss-primeui/blob/d5e903377e015b7c63cb5edf42490b9d6954ef04/src/utils/preset.js
'text-muted-color': 'text-[var(--p-text-muted-color)]',
},
],
presets: [
presetWind3({
/* prefix: "u-", */
@ -64,6 +18,52 @@ export default defineConfig({
// https://unocss.dev/presets/attributify
presetAttributify(),
],
shortcuts: [
{
'logo-transform': 'i-icon:pacman w-6em h-6em transform transition-800',
pacman: 'i-icon:pacman text-(pink 36)',
},
{
// https://github.com/primefaces/tailwindcss-primeui/blob/d5e903377e015b7c63cb5edf42490b9d6954ef04/src/utils/preset.js
'text-muted-color': 'text-[var(--p-text-muted-color)]',
},
],
theme: {
// https://unocss.dev/presets/wind#differences-from-tailwind-css
// https://www.npmjs.com/package/unocss-preset-animations
// https://github.com/whatnickcodes/unocss-preset-tailwindcss-motion
animation: {
counts: {
fadeout: '1',
scalein: '1',
},
durations: {
fadeout: '0.15s',
scalein: '0.15s',
},
keyframes: {
fadeout: '{0% { opacity: 1; } 100% { opacity: 0; }}',
scalein: '{0% { opacity: 0; transform: scaleY(0.8); } 100% { opacity: 1; transform: scaleY(1); }}',
},
properties: {
fadeout: {},
scalein: {
transition: 'transform 0.12s cubic-bezier(0, 0, 0.2, 1), opacity 0.12s cubic-bezier(0, 0, 0.2, 1)',
},
},
timingFns: {
fadeout: 'linear',
scalein: 'linear',
},
},
// https://unocss.dev/config/theme#usage
colors: {
'surface-0': 'var(--p-surface-0)', // text-surface-0
'surface-900': 'var(--p-surface-900)', // text-surface-900
},
},
transformers: [
//https://unocss.dev/transformers/variant-group
transformerVariantGroup(),

View File

@ -3,7 +3,6 @@
* https://github.com/vue-macros/vue-macros/blob/main/playground/vue3/vite.config.ts
*/
import { viteStaticCopy } from 'vite-plugin-static-copy';
import VueI18n from '@intlify/unplugin-vue-i18n/vite';
import { PrimeVueResolver } from '@primevue/auto-import-resolver';
import { unheadVueComposablesImports } from '@unhead/vue';
@ -26,6 +25,7 @@ import { createUtils4uAutoImports } from 'utils4u/auto-imports';
import { PluginOption } from 'vite';
import cdnImport from 'vite-plugin-cdn-import';
import { vitePluginFakeServer } from 'vite-plugin-fake-server';
import { viteStaticCopy } from 'vite-plugin-static-copy';
import VueDevTools from 'vite-plugin-vue-devtools';
import Layouts from 'vite-plugin-vue-layouts';
import MetaLayouts from 'vite-plugin-vue-meta-layouts';
@ -42,22 +42,22 @@ export function Plugins() {
plugins.push(
VueMacros({
plugins: {
vue: Vue({ include: [/\.vue$/, /\.md$/] }),
vueJsx: VueJsx(), // 如有需要
// https://uvr.esm.is/guide/configuration.html
// https://github.com/posva/unplugin-vue-router
vueRouter: VueRouter({
routesFolder: 'src/pages',
exclude: ['**/__*', '**/__*/**/*'],
extensions: ['.page.vue', '.page.md'],
getRouteName: (routeNode) => getPascalCaseRouteName(routeNode),
logs: false,
extensions: ['.page.vue', '.page.md'],
routesFolder: 'src/pages',
}),
vue: Vue({ include: [/\.vue$/, /\.md$/] }),
vueJsx: VueJsx(), // 如有需要
},
}), // https://vue-macros.dev/zh-CN/guide/bundler-integration.html
// https://github.com/JohnCampionJr/vite-plugin-vue-layouts?tab=readme-ov-file#configuration
Layouts({ pagesDirs: [], defaultLayout: 'sakai-vue/AppLayout' }),
Layouts({ defaultLayout: 'sakai-vue/AppLayout', pagesDirs: [] }),
// https://github.com/dishait/vite-plugin-vue-meta-layouts
MetaLayouts({
@ -76,7 +76,11 @@ export function Plugins() {
// https://github.com/antfu/unplugin-auto-import
AutoImport({
resolvers: [TDesignResolver({ library: 'mobile-vue', esm: true }), VantResolver({ importStyle: true })],
dirs: [
// 'src/composables',
'src/stores',
'src/utils',
],
imports: [
'vue',
'vue-i18n',
@ -95,37 +99,33 @@ export function Plugins() {
},
),
{
'consola/browser': ['consola'],
// add any other imports you were relying on
'vue-router/auto': ['useLink'],
'consola/browser': ['consola'],
},
],
dirs: [
// 'src/composables',
'src/stores',
'src/utils',
],
resolvers: [TDesignResolver({ esm: true, library: 'mobile-vue' }), VantResolver({ importStyle: true })],
vueTemplate: true,
}),
// https://github.com/antfu/unplugin-vue-components
Components({
// __开头的
excludeNames: [/^__/],
// allow auto load markdown components under `./src/components/`
extensions: ['vue', 'md'],
// allow auto import and register components used in markdown
include: [/\.vue$/, /\.vue\?vue/, /\.md$/],
// __开头的
excludeNames: [/^__/],
resolvers: [
AntDesignVueResolver({
importStyle: false, // css in js
resolveIcons: true,
}),
IconsResolver({
prefix: 'icon',
customCollections: ['svg'],
prefix: 'icon',
}), // https://github.com/unplugin/unplugin-icons?tab=readme-ov-file#auto-importing
TDesignResolver({ library: 'mobile-vue', esm: true }),
TDesignResolver({ esm: true, library: 'mobile-vue' }),
VantResolver({ importStyle: true }),
PrimeVueResolver(/* { components: { prefix: 'P' } } */),
],
@ -166,9 +166,9 @@ export function Plugins() {
// https://github.com/condorheroblog/vite-plugin-fake-server?tab=readme-ov-file#usage
vitePluginFakeServer({
include: 'fake',
basename: 'fake-api',
enableProd: true,
include: 'fake',
}),
viteStaticCopy({
@ -195,9 +195,9 @@ export function Plugins() {
// https://github.com/mmf-fe/vite-plugin-cdn-import/blob/HEAD/README.zh-CN.md
// 会对 Components 插件的自动导入产生影响。
cdnImport({
enableInDevMode: true,
modules: ['vue'],
prodUrl: '//fastly.jsdelivr.net/npm/{name}@{version}/{path}',
enableInDevMode: true,
}),
);
};

View File

@ -7,29 +7,14 @@ import { defineConfig, loadEnv } from 'vite';
import { cesiumBaseUrl, Plugins } from './vite.config.plugins';
// https://vitejs.dev/config/
export default defineConfig(({ mode, command }) => {
export default defineConfig(({ command, mode }) => {
const isBuild = command === 'build';
const env = loadEnv(mode, process.cwd());
return {
base: env.VITE_BASE,
plugins: Plugins(),
define: {
$__DEV__: JSON.stringify(!isBuild),
CESIUM_BASE_URL: JSON.stringify(`/${cesiumBaseUrl}`),
},
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
server: {
proxy: createViteProxy(),
allowedHosts: ['.nwct.dev'],
},
build: {
minify: 'terser',
sourcemap: mode !== 'production' || env.VITE_SOURCE_MAP === 'true',
rollupOptions: {
onwarn: (warning, warn) => {
if (warning.code === 'EMPTY_BUNDLE') return;
@ -54,6 +39,21 @@ export default defineConfig(({ mode, command }) => {
// },
},
},
sourcemap: mode !== 'production' || env.VITE_SOURCE_MAP === 'true',
},
define: {
$__DEV__: JSON.stringify(!isBuild),
CESIUM_BASE_URL: JSON.stringify(`/${cesiumBaseUrl}`),
},
plugins: Plugins(),
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
server: {
allowedHosts: ['.nwct.dev'],
proxy: createViteProxy(),
},
};
});