generated from template/router-template
update
This commit is contained in:
34
frontend/copy.ts
Normal file
34
frontend/copy.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* 将 dist 目录的内容复制到 backend/public 目录, 使用bun运行
|
||||
*
|
||||
* @tags build, deploy, copy
|
||||
* @description 构建后将前端静态资源复制到后端公共目录,复制前会删除已有内容
|
||||
* @title 前端资源复制脚本
|
||||
* @createdAt 2025-11-24
|
||||
*/
|
||||
import { existsSync, rmSync, cpSync } from 'node:fs';
|
||||
import { resolve, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const distPath = resolve(__dirname, 'dist');
|
||||
const backendPath = resolve(__dirname, '../backend/public');
|
||||
const frontendPath = resolve(backendPath, 'root/daily-question');
|
||||
// 删除已有的 backend/public 目录内容
|
||||
if (existsSync(backendPath)) {
|
||||
console.log('删除已有的 backend/public 目录...');
|
||||
rmSync(backendPath, { recursive: true, force: true });
|
||||
console.log('✓ 已删除旧内容');
|
||||
}
|
||||
|
||||
// 复制 dist 内容到 backend/public
|
||||
if (existsSync(distPath)) {
|
||||
console.log('复制 dist 内容到 backend/public...');
|
||||
cpSync(distPath, frontendPath, { recursive: true });
|
||||
console.log('✓ 复制完成');
|
||||
} else {
|
||||
console.error('错误: dist 目录不存在,请先运行构建命令');
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"build": "astro build",
|
||||
"postbuild": "bun copy.ts",
|
||||
"preview": "astro preview",
|
||||
"pub": "envision deploy ./dist -k daily-question -v 0.0.1 -u",
|
||||
"ui": "pnpm dlx shadcn@latest add "
|
||||
@@ -48,11 +49,13 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kevisual/types": "^0.0.10",
|
||||
"@types/node": "^24.10.1",
|
||||
"@types/react": "^19.2.6",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-basic-ssl": "^2.1.0",
|
||||
"dotenv": "^17.2.3",
|
||||
"tailwindcss": "^4.1.17",
|
||||
"tsx": "^4.20.6",
|
||||
"tw-animate-css": "^1.4.0"
|
||||
},
|
||||
"packageManager": "pnpm@10.23.0",
|
||||
|
||||
@@ -5,6 +5,8 @@ import { useShallow } from 'zustand/shallow';
|
||||
import { useEffect } from 'react';
|
||||
import { AppModal } from './modal.tsx'
|
||||
import dayjs from 'dayjs';
|
||||
import { Footer } from '../footer.tsx';
|
||||
import { exportJson } from '@/lib/json.ts';
|
||||
|
||||
const render = (dateStr: string) => {
|
||||
return dayjs(dateStr).format('YYYY-MM-DD HH:mm:ss');
|
||||
@@ -22,6 +24,10 @@ export const App = () => {
|
||||
store.getList();
|
||||
}, [])
|
||||
const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
},
|
||||
{
|
||||
title: '日期',
|
||||
dataIndex: 'date',
|
||||
@@ -80,7 +86,6 @@ export const App = () => {
|
||||
|
||||
<Button danger onClick={async () => {
|
||||
await store.deleteItem(record.id);
|
||||
message.success('删除成功');
|
||||
store.getList();
|
||||
}}>删除</Button>
|
||||
</Space>
|
||||
@@ -96,6 +101,7 @@ export const AppProvider = () => {
|
||||
<WrapperTable>
|
||||
<App />
|
||||
</WrapperTable>
|
||||
<Footer />
|
||||
</ConfigProvider>
|
||||
}
|
||||
export const Header = () => {
|
||||
@@ -103,6 +109,7 @@ export const Header = () => {
|
||||
open: state.open,
|
||||
setOpen: state.setOpen,
|
||||
setFormData: state.setFormData,
|
||||
list: state.dailyList,
|
||||
})));
|
||||
return <div className='container mx-auto px-4 py-4 flex justify-between items-center'>
|
||||
<h1 className='text-2xl font-bold'>每日问题历史记录管理</h1>
|
||||
@@ -112,6 +119,9 @@ export const Header = () => {
|
||||
store.setFormData({});
|
||||
store.setOpen(true)
|
||||
}}>新增</Button>
|
||||
<Button type="default" onClick={async () => {
|
||||
exportJson(store.list, 'question-daily.json');
|
||||
}}>导出</Button>
|
||||
</Space>
|
||||
</div>
|
||||
}
|
||||
|
||||
50
frontend/src/apps/footer.tsx
Normal file
50
frontend/src/apps/footer.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import { wrapBasename } from "@/modules/basename"
|
||||
|
||||
export const Footer = () => {
|
||||
|
||||
const links = [
|
||||
{
|
||||
href: wrapBasename('/'),
|
||||
label: '每日一题',
|
||||
},
|
||||
{
|
||||
href: wrapBasename('/daily'),
|
||||
label: '历史记录',
|
||||
},
|
||||
{
|
||||
href: wrapBasename('/library'),
|
||||
label: '资料库',
|
||||
},
|
||||
|
||||
{
|
||||
href: wrapBasename('/docs'),
|
||||
label: '文档',
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<footer className="fixed bottom-0 w-full bg-white border-t border-gray-200 shadow-lg">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
||||
{/* 链接区域 */}
|
||||
<nav className="flex flex-wrap justify-center items-center gap-2 sm:gap-4 mb-3">
|
||||
{links.map((link) => (
|
||||
<a
|
||||
key={link.href}
|
||||
href={link.href}
|
||||
className="relative px-4 py-2 text-sm sm:text-base font-medium text-gray-600 hover:text-blue-600 transition-all duration-300 ease-in-out
|
||||
before:absolute before:bottom-0 before:left-0 before:w-0 before:h-0.5 before:bg-blue-600 before:transition-all before:duration-300
|
||||
hover:before:w-full active:scale-95"
|
||||
>
|
||||
{link.label}
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
{/* 版权信息 */}
|
||||
<div className="text-center text-xs sm:text-sm text-gray-500">
|
||||
© 2025 Daily Question
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
@@ -5,6 +5,8 @@ import { useShallow } from 'zustand/shallow';
|
||||
import { useEffect } from 'react';
|
||||
import { AppModal } from './modal'
|
||||
import dayjs from 'dayjs';
|
||||
import { Footer } from '../footer';
|
||||
import { exportJson } from '@/lib/json';
|
||||
|
||||
const render = (dateStr: string) => {
|
||||
return dayjs(dateStr).format('YYYY-MM-DD HH:mm:ss');
|
||||
@@ -87,7 +89,6 @@ export const App = () => {
|
||||
|
||||
<Button danger onClick={async () => {
|
||||
await store.deleteItem(record.id);
|
||||
message.success('删除成功');
|
||||
store.getList();
|
||||
}}>删除</Button>
|
||||
</Space>
|
||||
@@ -101,12 +102,14 @@ export const AppProvider = () => {
|
||||
<WrapperTable>
|
||||
<App />
|
||||
</WrapperTable>
|
||||
<Footer />
|
||||
</ConfigProvider>
|
||||
}
|
||||
export const Header = () => {
|
||||
const store = useStore(useShallow((state) => ({
|
||||
open: state.open,
|
||||
setOpen: state.setOpen,
|
||||
list: state.libraryList,
|
||||
setFormData: state.setFormData,
|
||||
})));
|
||||
return <div className='container mx-auto px-4 py-4 flex justify-between items-center'>
|
||||
@@ -117,6 +120,10 @@ export const Header = () => {
|
||||
store.setFormData({});
|
||||
store.setOpen(true)
|
||||
}}>新增</Button>
|
||||
|
||||
<Button type="default" onClick={async () => {
|
||||
exportJson(store.list, 'question-library.json');
|
||||
}}>导出</Button>
|
||||
</Space>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import { useShallow } from "zustand/shallow";
|
||||
import MDEditor from '@uiw/react-md-editor';
|
||||
import { parseDataTag } from './utils.ts';
|
||||
import { ConfigProvider } from 'antd';
|
||||
import { Footer } from "../footer.tsx";
|
||||
import { wrapBasename } from "@/modules/basename.ts";
|
||||
export const App = () => {
|
||||
const store = useStore(useShallow((state) => ({
|
||||
today: state.today,
|
||||
@@ -35,10 +37,10 @@ export const App = () => {
|
||||
<div className="container mx-auto px-4 py-8 max-w-5xl">
|
||||
{/* 头部标题 */}
|
||||
<div className="mb-8 text-center">
|
||||
<h1 className="text-4xl font-bold mb-2 bg-linear-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
|
||||
今日题目 {store.today?.date}
|
||||
</h1>
|
||||
<p className="text-gray-500 text-sm">Daily Question Challenge</p>
|
||||
<div className="text-4xl font-bold mb-2 bg-linear-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
|
||||
今日题目
|
||||
</div>
|
||||
<p className="text-gray-500 text-sm">{store.today?.date}</p>
|
||||
</div>
|
||||
|
||||
{/* 题目卡片 */}
|
||||
@@ -46,9 +48,15 @@ export const App = () => {
|
||||
<div className="mb-8 bg-white rounded-2xl shadow-lg hover:shadow-xl transition-shadow duration-300 p-6 border border-gray-100">
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<h2 className="text-2xl font-bold text-gray-800 flex-1">{store.today.title}</h2>
|
||||
{store.today.qid && (
|
||||
<span className="ml-4 px-3 py-1 bg-linear-to-r from-blue-500 to-purple-500 text-white rounded-full text-xs font-semibold">
|
||||
#{store.today.qid}
|
||||
{store.today.id && (
|
||||
<span className="ml-4 px-3 py-1 bg-linear-to-r from-blue-500 to-purple-500 text-white rounded-full text-xs font-semibold cursor-pointer"
|
||||
onClick={() => {
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set('id', store.today!.id);
|
||||
url.pathname = wrapBasename('/daily');
|
||||
window.open(url.toString(), '_blank');
|
||||
}}>
|
||||
#{store.today.id}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -114,5 +122,6 @@ export const App = () => {
|
||||
export const AppProvider = () => {
|
||||
return <ConfigProvider>
|
||||
<App />
|
||||
<Footer />
|
||||
</ConfigProvider>
|
||||
}
|
||||
@@ -93,17 +93,24 @@ export const useStore = create<TodayStore>((set, get) => ({
|
||||
}
|
||||
});
|
||||
if (res.code === 200) {
|
||||
console.log('答案提交成功');
|
||||
message.success('答案提交成功');
|
||||
} else {
|
||||
console.error('答案提交失败');
|
||||
message.error('答案提交失败');
|
||||
}
|
||||
},
|
||||
dailyList: [],
|
||||
setDailyList: (data: DailyData[]) => set({ dailyList: data }),
|
||||
getDailyList: async () => {
|
||||
const searchParam = new URLSearchParams(window.location.search);
|
||||
const search = searchParam.get('search') || '';
|
||||
const id = searchParam.get('id') || '';
|
||||
const res = await query.post({
|
||||
path: 'daily',
|
||||
key: 'list'
|
||||
key: 'list',
|
||||
payload: {
|
||||
search,
|
||||
id
|
||||
}
|
||||
})
|
||||
console.log('daily list', res);
|
||||
if (res.code === 200) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: '01-frontend'
|
||||
tags: ['frontend', 'react', 'prompt']
|
||||
description: '第一次对功能进行最简单的解析'
|
||||
description: '第一次对功能进行最简单的解析, 整个前端页面的功能的大概概览'
|
||||
---
|
||||
|
||||
## 前端页面
|
||||
|
||||
16
frontend/src/data/docs/02-downlocal.md
Normal file
16
frontend/src/data/docs/02-downlocal.md
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
title: '02-下载应用到本地运行'
|
||||
tags: ['frontend', 'prompt']
|
||||
description: '本地使用sqlite为后端, 访问 http://localhost:9000 访问对应的页面的内容'
|
||||
---
|
||||
|
||||
## 下载应用
|
||||
|
||||
https://kevisual.lanzoub.com/s/daily-question
|
||||
|
||||
## 使用方法
|
||||
|
||||
1. 双击执行,访问 http://localhost:9000
|
||||
|
||||
首页是`http://localhost:9000/root/daily-question/`
|
||||
|
||||
12
frontend/src/lib/json.ts
Normal file
12
frontend/src/lib/json.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export const exportJson = (data: any, filename = 'data.json') => {
|
||||
const jsonStr = JSON.stringify(data, null, 2);
|
||||
const blob = new Blob([jsonStr], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
a.click();
|
||||
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
@@ -1,4 +1,12 @@
|
||||
// @ts-ignore
|
||||
export const basename = BASE_NAME;
|
||||
|
||||
console.log(basename);
|
||||
console.log(basename);
|
||||
|
||||
export const wrapBasename = (path: string) => {
|
||||
if (basename) {
|
||||
return `${basename}${path}/`;
|
||||
} else {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,14 @@
|
||||
import { Query } from '@kevisual/query'
|
||||
|
||||
const getUrl = () => {
|
||||
const host = window.location.host
|
||||
const isKevisual = host.includes('kevisual');
|
||||
if (isKevisual) {
|
||||
return '/api/router'
|
||||
}
|
||||
|
||||
return '/client/router'
|
||||
}
|
||||
export const query = new Query({
|
||||
url: '/client/router'
|
||||
url: getUrl()
|
||||
});
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
import { getCollection } from 'astro:content';
|
||||
const posts = await getCollection('docs');
|
||||
console.log('post', posts);
|
||||
import { basename } from '@/modules/basename';
|
||||
import Blank from '@/layouts/blank.astro';
|
||||
---
|
||||
@@ -28,7 +27,7 @@ import Blank from '@/layouts/blank.astro';
|
||||
<article class='group bg-white dark:bg-slate-800 rounded-xl shadow-sm hover:shadow-xl transition-all duration-300 overflow-hidden border border-slate-200 dark:border-slate-700 hover:border-blue-500 dark:hover:border-blue-400'>
|
||||
<div class='p-6'>
|
||||
{/* 文档标题 */}
|
||||
<a href={`${basename}/docs/${post.id}`} class='block'>
|
||||
<a href={`${basename}/docs/${post.id}/`} class='block'>
|
||||
<h2 class='text-xl sm:text-2xl font-semibold text-slate-900 dark:text-white group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors duration-200 mb-3'>
|
||||
{post.data.title}
|
||||
</h2>
|
||||
@@ -51,7 +50,7 @@ import Blank from '@/layouts/blank.astro';
|
||||
|
||||
{/* 阅读更多指示器 */}
|
||||
<a
|
||||
href={`${basename}/docs/${post.id}`}
|
||||
href={`${basename}/docs/${post.id}/`}
|
||||
class='mt-4 flex items-center text-blue-600 dark:text-blue-400 text-sm font-medium opacity-0 group-hover:opacity-100 transition-opacity duration-200'>
|
||||
<span>阅读更多</span>
|
||||
<svg
|
||||
|
||||
Reference in New Issue
Block a user