generated from tailored/app-template
	Initial commit
This commit is contained in:
		
							
								
								
									
										11
									
								
								.devcontainer/devcontainer.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								.devcontainer/devcontainer.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "name": "Node.js 22 Development Environment",
 | 
				
			||||||
 | 
					  "image": "mcr.microsoft.com/devcontainers/javascript-node:22",
 | 
				
			||||||
 | 
					  "settings": {
 | 
				
			||||||
 | 
					    "terminal.integrated.defaultProfile.linux": "/bin/bash"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "extensions": [
 | 
				
			||||||
 | 
					    "dbaeumer.vscode-eslint"
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "postCreateCommand": "npm install -g @kevisual/envision-cli@latest && npm install"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					node_modules
 | 
				
			||||||
 | 
					dist
 | 
				
			||||||
							
								
								
									
										4
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					# app-template
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					`/system/lib/app.js` 包函的模块是 `QueryRouterServer` 和 `Page` 和 `useConfigKey`
 | 
				
			||||||
							
								
								
									
										11
									
								
								config/esbuild.config.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								config/esbuild.config.mjs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					import { build } from 'esbuild';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					build({
 | 
				
			||||||
 | 
					  entryPoints: ['src/index.ts'],
 | 
				
			||||||
 | 
					  bundle: true,
 | 
				
			||||||
 | 
					  outfile: 'dist/index.js',
 | 
				
			||||||
 | 
					  platform: 'browser',
 | 
				
			||||||
 | 
					  target: 'esnext',
 | 
				
			||||||
 | 
					  sourcemap: false,
 | 
				
			||||||
 | 
					  format: 'esm',
 | 
				
			||||||
 | 
					}).catch(() => process.exit(1));
 | 
				
			||||||
							
								
								
									
										31
									
								
								index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								index.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
				
			|||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<meta charset="UTF-8">
 | 
				
			||||||
 | 
					<meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
				
			||||||
 | 
					<title>AI Apps</title>
 | 
				
			||||||
 | 
					<link rel="stylesheet" href="./src/assets/index.css">
 | 
				
			||||||
 | 
					<style>
 | 
				
			||||||
 | 
					  html,
 | 
				
			||||||
 | 
					  body {
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    padding: 0;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    height: 100%;
 | 
				
			||||||
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					    font-size: 16px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  #ai-root {
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    height: 100%;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
 | 
					<script src="/system/lib/app.js"></script>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					  <div id="ai-root"></div>
 | 
				
			||||||
 | 
					  <div id="ai-bot-root"></div>
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					<script src="./src/main.ts" type="module"></script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										48
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "name": "app-template",
 | 
				
			||||||
 | 
					  "version": "0.0.1",
 | 
				
			||||||
 | 
					  "description": "",
 | 
				
			||||||
 | 
					  "main": "index.js",
 | 
				
			||||||
 | 
					  "basename": "/me/app/",
 | 
				
			||||||
 | 
					  "scripts": {
 | 
				
			||||||
 | 
					    "dev": "vite",
 | 
				
			||||||
 | 
					    "dev:web": "cross-env WEB_DEV=true vite --mode web",
 | 
				
			||||||
 | 
					    "build": "vite build",
 | 
				
			||||||
 | 
					    "esbuild": "node esbuild.config.mjs",
 | 
				
			||||||
 | 
					    "preview": "vite preview",
 | 
				
			||||||
 | 
					    "prepub": "envision switchOrg user",
 | 
				
			||||||
 | 
					    "pub": "envision deploy ./dist -k app-template -v 0.0.1"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "stackblitz": {
 | 
				
			||||||
 | 
					    "startCommand": "npm dev:web"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "keywords": [],
 | 
				
			||||||
 | 
					  "author": "abearxiong <xiongxiao@xiongxiao.me>",
 | 
				
			||||||
 | 
					  "license": "MIT",
 | 
				
			||||||
 | 
					  "type": "module",
 | 
				
			||||||
 | 
					  "dependencies": {
 | 
				
			||||||
 | 
					    "@floating-ui/dom": "^1.6.13",
 | 
				
			||||||
 | 
					    "@kevisual/query": "0.0.7-alpha.3",
 | 
				
			||||||
 | 
					    "@kevisual/system-lib": "^0.0.10",
 | 
				
			||||||
 | 
					    "@kevisual/system-ui": "^0.0.3",
 | 
				
			||||||
 | 
					    "dayjs": "^1.11.13",
 | 
				
			||||||
 | 
					    "lodash-es": "^4.17.21",
 | 
				
			||||||
 | 
					    "react-dom": "^19.0.0",
 | 
				
			||||||
 | 
					    "zustand": "^5.0.3"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "devDependencies": {
 | 
				
			||||||
 | 
					    "@kevisual/router": "0.0.6-alpha-5",
 | 
				
			||||||
 | 
					    "@kevisual/store": "0.0.1-alpha.10",
 | 
				
			||||||
 | 
					    "@kevisual/types": "^0.0.6",
 | 
				
			||||||
 | 
					    "@tailwindcss/vite": "^4.0.9",
 | 
				
			||||||
 | 
					    "@types/react": "^19.0.8",
 | 
				
			||||||
 | 
					    "@types/react-dom": "^19.0.3",
 | 
				
			||||||
 | 
					    "@vitejs/plugin-basic-ssl": "^1.2.0",
 | 
				
			||||||
 | 
					    "@vitejs/plugin-react": "^4.3.4",
 | 
				
			||||||
 | 
					    "cross-env": "^7.0.3",
 | 
				
			||||||
 | 
					    "esbuild": "^0.25.0",
 | 
				
			||||||
 | 
					    "react": "^19.0.0",
 | 
				
			||||||
 | 
					    "tailwindcss": "^4.0.9",
 | 
				
			||||||
 | 
					    "vite": "^6.1.0"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										1686
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										1686
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										94
									
								
								snippets/no-react/h.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								snippets/no-react/h.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,94 @@
 | 
				
			|||||||
 | 
					// import { nanoid } from 'nanoid';
 | 
				
			||||||
 | 
					import { RefObject, SyntheticEvent } from 'react';
 | 
				
			||||||
 | 
					const randomId = () => {
 | 
				
			||||||
 | 
					  return crypto.getRandomValues(new Uint32Array(1))[0].toString(16);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					const loadChidren = (element: any, children: any[]) => {
 | 
				
			||||||
 | 
					  children.forEach((child) => {
 | 
				
			||||||
 | 
					    if (typeof child === 'boolean') {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (typeof child === 'function') {
 | 
				
			||||||
 | 
					      // console.log('child', child);
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (typeof child === 'undefined') {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    // console.log('child', child);
 | 
				
			||||||
 | 
					    element.appendChild(typeof child === 'string' ? document.createTextNode(child) : child);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					// 在项目中定义 h 函数
 | 
				
			||||||
 | 
					export function h(type: string | Function, props: any, ...children: any[]): HTMLElement {
 | 
				
			||||||
 | 
					  if (typeof type === 'function') {
 | 
				
			||||||
 | 
					    const element = type(props);
 | 
				
			||||||
 | 
					    loadChidren(element, children);
 | 
				
			||||||
 | 
					    return element;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  const element = document.createElement(type);
 | 
				
			||||||
 | 
					  const filterKeys = ['onLoad', 'onUnload', 'key'];
 | 
				
			||||||
 | 
					  const key = props?.key || randomId();
 | 
				
			||||||
 | 
					  Object.entries(props || {}).forEach(([key, value]) => {
 | 
				
			||||||
 | 
					    if (filterKeys.includes(key)) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (key === 'className') {
 | 
				
			||||||
 | 
					      element.setAttribute('class', value as string);
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (key.startsWith('on')) {
 | 
				
			||||||
 | 
					      element.addEventListener(key.slice(2).toLowerCase(), value as EventListener);
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (key === 'ref' && value) {
 | 
				
			||||||
 | 
					      (value as any).current = element;
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (typeof value === 'object') {
 | 
				
			||||||
 | 
					      console.log('error', element, type, value);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      element.setAttribute(key, value as string);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  const onLoad = props?.onLoad;
 | 
				
			||||||
 | 
					  const checkConnect = () => {
 | 
				
			||||||
 | 
					    if (element.isConnected) {
 | 
				
			||||||
 | 
					      onLoad?.({ el: element, key, _props: props });
 | 
				
			||||||
 | 
					      // console.log('onLoad', element, key);
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  setTimeout(() => {
 | 
				
			||||||
 | 
					    const res = checkConnect();
 | 
				
			||||||
 | 
					    if (!res) {
 | 
				
			||||||
 | 
					      setTimeout(() => {}, 1000);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }, 20);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  loadChidren(element, children);
 | 
				
			||||||
 | 
					  return element;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					declare global {
 | 
				
			||||||
 | 
					  namespace JSX {
 | 
				
			||||||
 | 
					    // type Element = HTMLElement; // 将 JSX.Element 设置为 HTMLElement
 | 
				
			||||||
 | 
					    interface Element extends HTMLElement {
 | 
				
			||||||
 | 
					      class?: string;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  namespace React {
 | 
				
			||||||
 | 
					    interface FormEvent<T = Element> extends SyntheticEvent<T> {
 | 
				
			||||||
 | 
					      target: EventTarget & (T extends HTMLInputElement ? HTMLInputElement : T);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useRef = <T = HTMLDivElement>(initialValue: T | null = null): RefObject<T | null> => {
 | 
				
			||||||
 | 
					  return { current: initialValue };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useEffect = (callback: () => void) => {
 | 
				
			||||||
 | 
					  setTimeout(callback, 0);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										6
									
								
								snippets/react/ReactApp.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								snippets/react/ReactApp.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					import { createRoot } from 'react-dom/client';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const ReactApp = () => {
 | 
				
			||||||
 | 
					  const root = createRoot(document.getElementById('app')!);
 | 
				
			||||||
 | 
					  root.render(<div>Hello, World!</div>);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										12
									
								
								snippets/routes-app/app.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								snippets/routes-app/app.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					import type { Page } from '@kevisual/store/page';
 | 
				
			||||||
 | 
					import type { QueryRouterServer } from '@kevisual/router';
 | 
				
			||||||
 | 
					export const page = useContextKey('page', () => {
 | 
				
			||||||
 | 
					  return new window.Page({
 | 
				
			||||||
 | 
					    basename: '',
 | 
				
			||||||
 | 
					  }) as unknown as Page;
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const app = useContextKey('app', () => {
 | 
				
			||||||
 | 
					  console.error('app not found');
 | 
				
			||||||
 | 
					  return null as unknown as QueryRouterServer;
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										39
									
								
								snippets/routes-app/main.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								snippets/routes-app/main.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
				
			|||||||
 | 
					import { page, app } from './app.ts';
 | 
				
			||||||
 | 
					export const render = ({ renderRoot }) => {
 | 
				
			||||||
 | 
					  renderRoot.innerHTML = `
 | 
				
			||||||
 | 
					    <h1>Hello, World!</h1>
 | 
				
			||||||
 | 
					  `;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (page) {
 | 
				
			||||||
 | 
					  page.addPage('/app-template', 'home');
 | 
				
			||||||
 | 
					  page.subscribe('home', () => {
 | 
				
			||||||
 | 
					    render({
 | 
				
			||||||
 | 
					      renderRoot: document.getElementById('ai-root'),
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (app) {
 | 
				
			||||||
 | 
					  app
 | 
				
			||||||
 | 
					    .route({
 | 
				
			||||||
 | 
					      path: 'app-template',
 | 
				
			||||||
 | 
					      key: 'render',
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .define(async (ctx) => {
 | 
				
			||||||
 | 
					      let { renderRoot } = ctx.query;
 | 
				
			||||||
 | 
					      if (!renderRoot) {
 | 
				
			||||||
 | 
					        ctx.throw(404, 'renderRoot is required');
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (typeof renderRoot === 'string') {
 | 
				
			||||||
 | 
					        renderRoot = document.querySelector(renderRoot);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (!renderRoot) {
 | 
				
			||||||
 | 
					        ctx.throw(404, 'renderRoot not found');
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      render({
 | 
				
			||||||
 | 
					        renderRoot,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .addTo(app);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										85
									
								
								snippets/store/app.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								snippets/store/app.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,85 @@
 | 
				
			|||||||
 | 
					import { create } from 'zustand';
 | 
				
			||||||
 | 
					import { query } from '@/modules/query';
 | 
				
			||||||
 | 
					type Store = {
 | 
				
			||||||
 | 
					  list: any[];
 | 
				
			||||||
 | 
					  setList: (list: any[]) => void;
 | 
				
			||||||
 | 
					  data: any;
 | 
				
			||||||
 | 
					  setData: (data: any) => void;
 | 
				
			||||||
 | 
					  loading: boolean;
 | 
				
			||||||
 | 
					  setLoading: (loading: boolean) => void;
 | 
				
			||||||
 | 
					  formData: any;
 | 
				
			||||||
 | 
					  setFormData: (data: any) => void;
 | 
				
			||||||
 | 
					  getList: () => Promise<any>;
 | 
				
			||||||
 | 
					  init: () => Promise<void>;
 | 
				
			||||||
 | 
					  getData: (id: number) => Promise<any>;
 | 
				
			||||||
 | 
					  updateData: (data: any, opts?: { refresh?: boolean }) => Promise<any>;
 | 
				
			||||||
 | 
					  deleteData: (id: number, opts?: { refresh?: boolean }) => Promise<any>;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					export const useStore = create<Store>((set, get) => ({
 | 
				
			||||||
 | 
					  list: [],
 | 
				
			||||||
 | 
					  setList: (list) => set({ list }),
 | 
				
			||||||
 | 
					  data: null,
 | 
				
			||||||
 | 
					  setData: (data) => set({ data }),
 | 
				
			||||||
 | 
					  loading: false,
 | 
				
			||||||
 | 
					  setLoading: (loading) => set({ loading }),
 | 
				
			||||||
 | 
					  formData: null,
 | 
				
			||||||
 | 
					  setFormData: (formData) => set({ formData }),
 | 
				
			||||||
 | 
					  getList: async () => {
 | 
				
			||||||
 | 
					    set({ loading: true });
 | 
				
			||||||
 | 
					    const res = await query.post({ path: 'posts', key: 'list' });
 | 
				
			||||||
 | 
					    set({ loading: false });
 | 
				
			||||||
 | 
					    if (res.code === 200) {
 | 
				
			||||||
 | 
					      set({ list: res.data });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return res;
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  init: async () => {
 | 
				
			||||||
 | 
					    await get().getList();
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  getData: async (id) => {
 | 
				
			||||||
 | 
					    set({ loading: true });
 | 
				
			||||||
 | 
					    const res = await query.post({
 | 
				
			||||||
 | 
					      path: 'posts',
 | 
				
			||||||
 | 
					      key: 'get',
 | 
				
			||||||
 | 
					      id,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    set({ loading: false });
 | 
				
			||||||
 | 
					    if (res.code === 200) {
 | 
				
			||||||
 | 
					      const data = res.data;
 | 
				
			||||||
 | 
					      set({ data });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return res;
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  updateData: async (data, opts = { refresh: true }) => {
 | 
				
			||||||
 | 
					    set({ loading: true });
 | 
				
			||||||
 | 
					    const res = await query.post({
 | 
				
			||||||
 | 
					      path: 'posts',
 | 
				
			||||||
 | 
					      key: 'update',
 | 
				
			||||||
 | 
					      data,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    set({ loading: false });
 | 
				
			||||||
 | 
					    if (res.code === 200) {
 | 
				
			||||||
 | 
					      set({ data: res.data });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (opts.refresh) {
 | 
				
			||||||
 | 
					      await get().getList();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return res;
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  deleteData: async (id, opts = { refresh: true }) => {
 | 
				
			||||||
 | 
					    set({ loading: true });
 | 
				
			||||||
 | 
					    const res = await query.post({
 | 
				
			||||||
 | 
					      path: 'posts',
 | 
				
			||||||
 | 
					      key: 'delete',
 | 
				
			||||||
 | 
					      id,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    set({ loading: false });
 | 
				
			||||||
 | 
					    if (res.code === 200) {
 | 
				
			||||||
 | 
					      set({ data: null });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (opts.refresh) {
 | 
				
			||||||
 | 
					      await get().getList();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return res;
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					}));
 | 
				
			||||||
							
								
								
									
										12
									
								
								src/app.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/app.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					import type { Page } from '@kevisual/store/page';
 | 
				
			||||||
 | 
					import type { QueryRouterServer } from '@kevisual/router';
 | 
				
			||||||
 | 
					import { basename } from './modules/basename';
 | 
				
			||||||
 | 
					export const page = useContextKey('page', () => {
 | 
				
			||||||
 | 
					  return new window.Page({
 | 
				
			||||||
 | 
					    basename,
 | 
				
			||||||
 | 
					  }) as unknown as Page;
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					export const app = useContextKey('app', () => {
 | 
				
			||||||
 | 
					  console.error('app not found');
 | 
				
			||||||
 | 
					  return null as unknown as QueryRouterServer;
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										16
									
								
								src/assets/index.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/assets/index.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					@import "tailwindcss";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@layer components {
 | 
				
			||||||
 | 
					  .test-loading {
 | 
				
			||||||
 | 
					    @apply w-20 h-20 bg-gray-300 rounded-full animate-spin;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ai-bot-root {
 | 
				
			||||||
 | 
					  width: 100%;
 | 
				
			||||||
 | 
					  height: 100%;
 | 
				
			||||||
 | 
					  position: fixed;
 | 
				
			||||||
 | 
					  top: 0;
 | 
				
			||||||
 | 
					  left: -100px;
 | 
				
			||||||
 | 
					  z-index: 9999;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										48
									
								
								src/main.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/main.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
				
			|||||||
 | 
					import { page, app } from './app.ts';
 | 
				
			||||||
 | 
					import { basename } from './modules/basename.ts';
 | 
				
			||||||
 | 
					export const render = ({ renderRoot }) => {
 | 
				
			||||||
 | 
					  renderRoot.innerHTML = `
 | 
				
			||||||
 | 
					    <h1>Hello, World!</h1>
 | 
				
			||||||
 | 
					  `;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					console.log('basename', basename, page, app);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (page) {
 | 
				
			||||||
 | 
					  page.addPage('/app-template', 'home');
 | 
				
			||||||
 | 
					  page.subscribe('home', () => {
 | 
				
			||||||
 | 
					    render({
 | 
				
			||||||
 | 
					      renderRoot: document.getElementById('ai-root'),
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  page.addPage('', 'index');
 | 
				
			||||||
 | 
					  page.subscribe('index', () => {
 | 
				
			||||||
 | 
					    const root = document.getElementById('ai-root') as HTMLElement;
 | 
				
			||||||
 | 
					    root.innerHTML = `
 | 
				
			||||||
 | 
					      <h1>Hello, World!</h1>
 | 
				
			||||||
 | 
					    `;
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (app) {
 | 
				
			||||||
 | 
					  app
 | 
				
			||||||
 | 
					    .route({
 | 
				
			||||||
 | 
					      path: 'app-template',
 | 
				
			||||||
 | 
					      key: 'render',
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .define(async (ctx) => {
 | 
				
			||||||
 | 
					      let { renderRoot } = ctx.query;
 | 
				
			||||||
 | 
					      if (!renderRoot) {
 | 
				
			||||||
 | 
					        ctx.throw(404, 'renderRoot is required');
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (typeof renderRoot === 'string') {
 | 
				
			||||||
 | 
					        renderRoot = document.querySelector(renderRoot);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (!renderRoot) {
 | 
				
			||||||
 | 
					        ctx.throw(404, 'renderRoot not found');
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      render({
 | 
				
			||||||
 | 
					        renderRoot,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .addTo(app);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										2
									
								
								src/modules/basename.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								src/modules/basename.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					// @ts-ignore
 | 
				
			||||||
 | 
					export const basename = DEV_SERVER ? '/' : BASE_NAME;
 | 
				
			||||||
							
								
								
									
										3
									
								
								src/modules/message.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/modules/message.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					import { message } from '@kevisual/system-ui/dist/message';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export { message };
 | 
				
			||||||
							
								
								
									
										3
									
								
								src/modules/query.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/modules/query.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					import { QueryClient } from '@kevisual/query';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const query = new QueryClient();
 | 
				
			||||||
							
								
								
									
										43
									
								
								tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								tsconfig.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "compilerOptions": {
 | 
				
			||||||
 | 
					    "jsx": "react",
 | 
				
			||||||
 | 
					    "target": "ES2020",
 | 
				
			||||||
 | 
					    "useDefineForClassFields": true,
 | 
				
			||||||
 | 
					    "lib": [
 | 
				
			||||||
 | 
					      "ES2020",
 | 
				
			||||||
 | 
					      "DOM",
 | 
				
			||||||
 | 
					      "DOM.Iterable"
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "module": "ESNext",
 | 
				
			||||||
 | 
					    "skipLibCheck": true,
 | 
				
			||||||
 | 
					    /* Bundler mode */
 | 
				
			||||||
 | 
					    "moduleResolution": "bundler",
 | 
				
			||||||
 | 
					    "allowImportingTsExtensions": true,
 | 
				
			||||||
 | 
					    "isolatedModules": true,
 | 
				
			||||||
 | 
					    "moduleDetection": "force",
 | 
				
			||||||
 | 
					    "noEmit": true,
 | 
				
			||||||
 | 
					    // "jsxFragmentFactory": "Fragment",
 | 
				
			||||||
 | 
					    // "jsxFactory": "h",
 | 
				
			||||||
 | 
					    "baseUrl": "./",
 | 
				
			||||||
 | 
					    "typeRoots": [
 | 
				
			||||||
 | 
					      "node_modules/@types",
 | 
				
			||||||
 | 
					      "node_modules/@kevisual/types",
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "paths": {
 | 
				
			||||||
 | 
					      "@/*": [
 | 
				
			||||||
 | 
					        "src/*"
 | 
				
			||||||
 | 
					      ]
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    /* Linting */
 | 
				
			||||||
 | 
					    "strict": true,
 | 
				
			||||||
 | 
					    "noImplicitAny": false,
 | 
				
			||||||
 | 
					    "noUnusedLocals": false,
 | 
				
			||||||
 | 
					    "noUnusedParameters": false,
 | 
				
			||||||
 | 
					    "noFallthroughCasesInSwitch": true
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "include": [
 | 
				
			||||||
 | 
					    "src",
 | 
				
			||||||
 | 
					    "typings.d.ts",
 | 
				
			||||||
 | 
					    "snippets"
 | 
				
			||||||
 | 
					  ]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										55
									
								
								vite.config.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								vite.config.mjs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
				
			|||||||
 | 
					import { defineConfig } from 'vite';
 | 
				
			||||||
 | 
					import basicSsl from '@vitejs/plugin-basic-ssl';
 | 
				
			||||||
 | 
					// import react from '@vitejs/plugin-react';
 | 
				
			||||||
 | 
					import dayjs from 'dayjs';
 | 
				
			||||||
 | 
					import path from 'path';
 | 
				
			||||||
 | 
					import tailwindcss from '@tailwindcss/vite'
 | 
				
			||||||
 | 
					import pkgs from './package.json' with { type: 'json' };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const isDev = process.env.NODE_ENV === 'development';
 | 
				
			||||||
 | 
					const isWebDev = process.env.WEB_DEV === 'true';
 | 
				
			||||||
 | 
					const BUILD_TIME = dayjs().format('YYYY-MM-DD HH:mm:ss');
 | 
				
			||||||
 | 
					const basename = pkgs.basename;
 | 
				
			||||||
 | 
					let plugins = [tailwindcss(),];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (!isWebDev) {
 | 
				
			||||||
 | 
					  // 在bolt的web开发环境下不需要ssl
 | 
				
			||||||
 | 
					  plugins.push(basicSsl());
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default defineConfig({
 | 
				
			||||||
 | 
					  plugins: plugins,
 | 
				
			||||||
 | 
					  resolve: {
 | 
				
			||||||
 | 
					    alias: {
 | 
				
			||||||
 | 
					      '@': path.resolve(__dirname, './src'),
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  base: isDev ? '/' : basename,
 | 
				
			||||||
 | 
					  define: {
 | 
				
			||||||
 | 
					    DEV_SERVER: JSON.stringify(isDev),
 | 
				
			||||||
 | 
					    BUILD_TIME: JSON.stringify(BUILD_TIME),
 | 
				
			||||||
 | 
					    BASE_NAME: JSON.stringify(basename),
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  optimizeDeps: {
 | 
				
			||||||
 | 
					    exclude: ['react'], // 排除 react 和 react-dom 以避免打包
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  // esbuild: {
 | 
				
			||||||
 | 
					  //   jsxFactory: 'h',
 | 
				
			||||||
 | 
					  //   jsxFragment: 'Fragment',
 | 
				
			||||||
 | 
					  // },
 | 
				
			||||||
 | 
					  server: {
 | 
				
			||||||
 | 
					    port: 6025,
 | 
				
			||||||
 | 
					    host: '0.0.0.0',
 | 
				
			||||||
 | 
					    proxy: {
 | 
				
			||||||
 | 
					      '/api': {
 | 
				
			||||||
 | 
					        target: 'https://kevisual.xiongxiao.me',
 | 
				
			||||||
 | 
					        changeOrigin: true,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      '/system/lib': {
 | 
				
			||||||
 | 
					        target: 'https://kevisual.xiongxiao.me',
 | 
				
			||||||
 | 
					        changeOrigin: true,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
		Reference in New Issue
	
	Block a user