فهرست منبع

feat(dependencies): 更新 'agentkeepalive' 'aproba' 'are-we-there-yet' 等 23 个依赖包版本

zyh 1 سال پیش
والد
کامیت
ea85c193f2
9فایلهای تغییر یافته به همراه809 افزوده شده و 165 حذف شده
  1. 62 13
      README.md
  2. 6 0
      app/db.server.ts
  3. 41 127
      app/routes/_index.tsx
  4. 0 0
      database.sqlite
  5. 11 0
      knexfile.ts
  6. 15 0
      migrations/20240101000000_create_admin_user.ts
  7. 648 22
      package-lock.json
  8. 13 3
      package.json
  9. 13 0
      seeds/initial_data.ts

+ 62 - 13
README.md

@@ -1,40 +1,89 @@
-# Welcome to Remix!
+# 欢迎使用 Remix!
 
-- 📖 [Remix docs](https://remix.run/docs)
+- 📖 [Remix 文档](https://remix.run/docs)
 
-## Development
+## 新增库
 
-Run the dev server:
+- [Ant Design](https://ant.design/) - 用于构建用户界面的 React UI 库
+- [Knex](http://knexjs.org/) - SQL 查询构建器和数据库迁移工具
+
+## 开发
+
+运行开发服务器:
 
 ```shellscript
 npm run dev
 ```
 
-## Deployment
+## 数据库操作
+
+运行数据库迁移:
+
+```shellscript
+npm run migrate
+```
+
+创建新的迁移文件:
+
+```shellscript
+npm run migrate:make <migration_name>
+```
+
+回滚最近的迁移:
+
+```shellscript
+npm run migrate:rollback
+```
 
-First, build your app for production:
+列出所有迁移及其状态:
+
+```shellscript
+npm run migrate:list
+```
+
+运行种子文件:
+
+```shellscript
+npm run seed
+```
+
+创建新的种子文件:
+
+```shellscript
+npm run seed:make <seed_name>
+```
+
+## 部署
+
+首先,为生产环境构建您的应用:
 
 ```sh
 npm run build
 ```
 
-Then run the app in production mode:
+然后运行数据库迁移:
+
+```sh
+npm run migrate
+```
+
+最后在生产模式下运行应用:
 
 ```sh
 npm start
 ```
 
-Now you'll need to pick a host to deploy it to.
+现在您需要选择一个主机来部署它。
 
-### DIY
+### 自行部署
 
-If you're familiar with deploying Node applications, the built-in Remix app server is production-ready.
+如果您熟悉部署 Node 应用程序,内置的 Remix 应用服务器已经可以用于生产环境。
 
-Make sure to deploy the output of `npm run build`
+确保部署 `npm run build` 的输出:
 
 - `build/server`
 - `build/client`
 
-## Styling
+## 样式
 
-This template comes with [Tailwind CSS](https://tailwindcss.com/) already configured for a simple default starting experience. You can use whatever css framework you prefer. See the [Vite docs on css](https://vitejs.dev/guide/features.html#css) for more information.
+这个模板已经配置了 [Tailwind CSS](https://tailwindcss.com/),为您提供一个简单的默认起点。您可以使用任何您喜欢的 CSS 框架。有关更多信息,请参阅 [Vite 关于 CSS 的文档](https://vitejs.dev/guide/features.html#css)。

+ 6 - 0
app/db.server.ts

@@ -0,0 +1,6 @@
+import knex from "knex";
+import config from "../knexfile";
+
+const db = knex(config);
+
+export default db;

+ 41 - 127
app/routes/_index.tsx

@@ -1,138 +1,52 @@
-import type { MetaFunction } from "@remix-run/node";
+import { useState, useEffect } from "react";
+import type { MetaFunction, LoaderFunction } from "@remix-run/node";
+import { json } from "@remix-run/node";
+import { useLoaderData } from "@remix-run/react";
+import { Button, Card, Typography, Space } from "antd";
+import db from "~/db.server";
+
+const { Title, Paragraph } = Typography;
 
 export const meta: MetaFunction = () => {
   return [
-    { title: "New Remix App" },
-    { name: "description", content: "Welcome to Remix!" },
+    { title: "Remix 管理系统" },
+    { name: "description", content: "欢迎来到 Remix 管理系统!" },
   ];
 };
 
+export const loader: LoaderFunction = async () => {
+  const userCount = await db("users").count("* as count").first();
+  return json({ userCount: userCount?.count || 0 });
+};
+
 export default function Index() {
+  const { userCount } = useLoaderData<typeof loader>();
+  const [currentTime, setCurrentTime] = useState(new Date());
+
+  useEffect(() => {
+    const timer = setInterval(() => setCurrentTime(new Date()), 1000);
+    return () => clearInterval(timer);
+  }, []);
+
   return (
-    <div className="flex h-screen items-center justify-center">
-      <div className="flex flex-col items-center gap-16">
-        <header className="flex flex-col items-center gap-9">
-          <h1 className="leading text-2xl font-bold text-gray-800 dark:text-gray-100">
-            Welcome to <span className="sr-only">Remix</span>
-          </h1>
-          <div className="h-[144px] w-[434px]">
-            <img
-              src="/logo-light.png"
-              alt="Remix"
-              className="block w-full dark:hidden"
-            />
-            <img
-              src="/logo-dark.png"
-              alt="Remix"
-              className="hidden w-full dark:block"
-            />
-          </div>
-        </header>
-        <nav className="flex flex-col items-center justify-center gap-4 rounded-3xl border border-gray-200 p-6 dark:border-gray-700">
-          <p className="leading-6 text-gray-700 dark:text-gray-200">
-            What&apos;s next?
-          </p>
-          <ul>
-            {resources.map(({ href, text, icon }) => (
-              <li key={href}>
-                <a
-                  className="group flex items-center gap-3 self-stretch p-3 leading-normal text-blue-700 hover:underline dark:text-blue-500"
-                  href={href}
-                  target="_blank"
-                  rel="noreferrer"
-                >
-                  {icon}
-                  {text}
-                </a>
-              </li>
-            ))}
-          </ul>
-        </nav>
-      </div>
+    <div className="min-h-screen bg-gray-100 flex items-center justify-center">
+      <Card className="w-full max-w-md shadow-lg">
+        <Title level={2} className="text-center mb-6">欢迎来到 Remix 管理系统</Title>
+        <Paragraph className="text-center mb-4">
+          当前时间:{currentTime.toLocaleString()}
+        </Paragraph>
+        <Paragraph className="text-center mb-6">
+          系统中共有 {userCount} 个用户
+        </Paragraph>
+        <Space direction="vertical" size="middle" className="w-full">
+          <Button type="primary" block>
+            登录系统
+          </Button>
+          <Button block>
+            查看用户列表
+          </Button>
+        </Space>
+      </Card>
     </div>
   );
 }
-
-const resources = [
-  {
-    href: "https://remix.run/start/quickstart",
-    text: "Quick Start (5 min)",
-    icon: (
-      <svg
-        xmlns="http://www.w3.org/2000/svg"
-        width="24"
-        height="20"
-        viewBox="0 0 20 20"
-        fill="none"
-        className="stroke-gray-600 group-hover:stroke-current dark:stroke-gray-300"
-      >
-        <path
-          d="M8.51851 12.0741L7.92592 18L15.6296 9.7037L11.4815 7.33333L12.0741 2L4.37036 10.2963L8.51851 12.0741Z"
-          strokeWidth="1.5"
-          strokeLinecap="round"
-          strokeLinejoin="round"
-        />
-      </svg>
-    ),
-  },
-  {
-    href: "https://remix.run/start/tutorial",
-    text: "Tutorial (30 min)",
-    icon: (
-      <svg
-        xmlns="http://www.w3.org/2000/svg"
-        width="24"
-        height="20"
-        viewBox="0 0 20 20"
-        fill="none"
-        className="stroke-gray-600 group-hover:stroke-current dark:stroke-gray-300"
-      >
-        <path
-          d="M4.561 12.749L3.15503 14.1549M3.00811 8.99944H1.01978M3.15503 3.84489L4.561 5.2508M8.3107 1.70923L8.3107 3.69749M13.4655 3.84489L12.0595 5.2508M18.1868 17.0974L16.635 18.6491C16.4636 18.8205 16.1858 18.8205 16.0144 18.6491L13.568 16.2028C13.383 16.0178 13.0784 16.0347 12.915 16.239L11.2697 18.2956C11.047 18.5739 10.6029 18.4847 10.505 18.142L7.85215 8.85711C7.75756 8.52603 8.06365 8.21994 8.39472 8.31453L17.6796 10.9673C18.0223 11.0653 18.1115 11.5094 17.8332 11.7321L15.7766 13.3773C15.5723 13.5408 15.5554 13.8454 15.7404 14.0304L18.1868 16.4767C18.3582 16.6481 18.3582 16.926 18.1868 17.0974Z"
-          strokeWidth="1.5"
-          strokeLinecap="round"
-          strokeLinejoin="round"
-        />
-      </svg>
-    ),
-  },
-  {
-    href: "https://remix.run/docs",
-    text: "Remix Docs",
-    icon: (
-      <svg
-        xmlns="http://www.w3.org/2000/svg"
-        width="24"
-        height="20"
-        viewBox="0 0 20 20"
-        fill="none"
-        className="stroke-gray-600 group-hover:stroke-current dark:stroke-gray-300"
-      >
-        <path
-          d="M9.99981 10.0751V9.99992M17.4688 17.4688C15.889 19.0485 11.2645 16.9853 7.13958 12.8604C3.01467 8.73546 0.951405 4.11091 2.53116 2.53116C4.11091 0.951405 8.73546 3.01467 12.8604 7.13958C16.9853 11.2645 19.0485 15.889 17.4688 17.4688ZM2.53132 17.4688C0.951566 15.8891 3.01483 11.2645 7.13974 7.13963C11.2647 3.01471 15.8892 0.951453 17.469 2.53121C19.0487 4.11096 16.9854 8.73551 12.8605 12.8604C8.73562 16.9853 4.11107 19.0486 2.53132 17.4688Z"
-          strokeWidth="1.5"
-          strokeLinecap="round"
-        />
-      </svg>
-    ),
-  },
-  {
-    href: "https://rmx.as/discord",
-    text: "Join Discord",
-    icon: (
-      <svg
-        xmlns="http://www.w3.org/2000/svg"
-        width="24"
-        height="20"
-        viewBox="0 0 24 20"
-        fill="none"
-        className="stroke-gray-600 group-hover:stroke-current dark:stroke-gray-300"
-      >
-        <path
-          d="M15.0686 1.25995L14.5477 1.17423L14.2913 1.63578C14.1754 1.84439 14.0545 2.08275 13.9422 2.31963C12.6461 2.16488 11.3406 2.16505 10.0445 2.32014C9.92822 2.08178 9.80478 1.84975 9.67412 1.62413L9.41449 1.17584L8.90333 1.25995C7.33547 1.51794 5.80717 1.99419 4.37748 2.66939L4.19 2.75793L4.07461 2.93019C1.23864 7.16437 0.46302 11.3053 0.838165 15.3924L0.868838 15.7266L1.13844 15.9264C2.81818 17.1714 4.68053 18.1233 6.68582 18.719L7.18892 18.8684L7.50166 18.4469C7.96179 17.8268 8.36504 17.1824 8.709 16.4944L8.71099 16.4904C10.8645 17.0471 13.128 17.0485 15.2821 16.4947C15.6261 17.1826 16.0293 17.8269 16.4892 18.4469L16.805 18.8725L17.3116 18.717C19.3056 18.105 21.1876 17.1751 22.8559 15.9238L23.1224 15.724L23.1528 15.3923C23.5873 10.6524 22.3579 6.53306 19.8947 2.90714L19.7759 2.73227L19.5833 2.64518C18.1437 1.99439 16.6386 1.51826 15.0686 1.25995ZM16.6074 10.7755L16.6074 10.7756C16.5934 11.6409 16.0212 12.1444 15.4783 12.1444C14.9297 12.1444 14.3493 11.6173 14.3493 10.7877C14.3493 9.94885 14.9378 9.41192 15.4783 9.41192C16.0471 9.41192 16.6209 9.93851 16.6074 10.7755ZM8.49373 12.1444C7.94513 12.1444 7.36471 11.6173 7.36471 10.7877C7.36471 9.94885 7.95323 9.41192 8.49373 9.41192C9.06038 9.41192 9.63892 9.93712 9.6417 10.7815C9.62517 11.6239 9.05462 12.1444 8.49373 12.1444Z"
-          strokeWidth="1.5"
-        />
-      </svg>
-    ),
-  },
-];

+ 0 - 0
database.sqlite


+ 11 - 0
knexfile.ts

@@ -0,0 +1,11 @@
+import type { Knex } from "knex";
+
+const config: Knex.Config = {
+  client: "sqlite3",
+  connection: {
+    filename: "./database.sqlite",
+  },
+  useNullAsDefault: true,
+};
+
+export default config;

+ 15 - 0
migrations/20240101000000_create_admin_user.ts

@@ -0,0 +1,15 @@
+import { Knex } from "knex";
+
+export async function up(knex: Knex): Promise<void> {
+  await knex.schema.createTable("users", (table) => {
+    table.increments("id").primary();
+    table.string("username").notNullable().unique();
+    table.string("password").notNullable();
+    table.boolean("is_admin").notNullable().defaultTo(false);
+    table.timestamps(true, true);
+  });
+}
+
+export async function down(knex: Knex): Promise<void> {
+  await knex.schema.dropTable("users");
+}

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 648 - 22
package-lock.json


+ 13 - 3
package.json

@@ -4,18 +4,28 @@
   "type": "module",
   "scripts": {
     "build": "remix vite:build",
-    "dev": "remix vite:dev",
+    "dev": "remix vite:dev --port 23904",
     "lint": "eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .",
     "start": "remix-serve ./build/server/index.js",
-    "typecheck": "tsc"
+    "typecheck": "tsc",
+    "migrate": "knex migrate:latest",
+    "migrate:make": "knex migrate:make",
+    "migrate:rollback": "knex migrate:rollback",
+    "migrate:list": "knex migrate:list",
+    "seed": "knex seed:run",
+    "seed:make": "knex seed:make"
   },
   "dependencies": {
+    "@ant-design/icons": "^5.5.1",
     "@remix-run/node": "*",
     "@remix-run/react": "*",
     "@remix-run/serve": "*",
+    "antd": "^5.21.5",
     "isbot": "^4.1.0",
+    "knex": "^3.1.0",
     "react": "^18.2.0",
-    "react-dom": "^18.2.0"
+    "react-dom": "^18.2.0",
+    "sqlite3": "^5.1.7"
   },
   "devDependencies": {
     "@remix-run/dev": "*",

+ 13 - 0
seeds/initial_data.ts

@@ -0,0 +1,13 @@
+import { Knex } from "knex";
+
+export async function seed(knex: Knex): Promise<void> {
+  // 清空现有数据
+  await knex("users").del();
+
+  // 插入新数据
+  await knex("users").insert([
+    { username: "admin", password: "admin123", is_admin: true },
+    { username: "user1", password: "password1", is_admin: false },
+    { username: "user2", password: "password2", is_admin: false },
+  ]);
+}

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است