first commit

This commit is contained in:
userName 2024-08-23 11:01:27 +00:00
commit 35ca477a2d
117 changed files with 4796 additions and 0 deletions

31
README.md Executable file
View File

@ -0,0 +1,31 @@
<!-- npm init vue@latest
cd vue -->
npm install
axios
my-vue-app/
├── node_modules/ # 通过 npm 或 yarn 安装的项目依赖包
├── public/ # 存放静态资源和公共资源
│ ├── favicon.ico # 网站图标
│ ├── index.html # HTML 模板文件
│ └── ... # 其他静态资源如图片、字体等
├── src/ # 源代码目录
│ ├── App.vue # 根组件
│ ├── main.js # 应用入口文件
│ ├── assets/ # 静态资源,如图片、样式文件等
│ ├── components/ # 全局可复用的组件
│ ├── views/ # 视图组件
│ ├── router/ # 路由配置
│ ├── store/ # Vuex 状态管理
│ ├── utils/ # 工具函数
│ └── ...
├── .gitignore # Git 忽略文件配置
├── package.json # 项目元数据和依赖关系
├── vue.config.js # Vue CLI 配置文件
├── README.md # 项目文档
└── ...
<template>
</template>
<script></script>
<style></style>

17
backend/1.py Executable file
View File

@ -0,0 +1,17 @@
# start_service.py
import subprocess
def run_service():
# 激活虚拟环境
activate_cmd = '/home/lqs1/app/venv/bin/activate'
# 运行服务
service_cmd = ['uvicorn', 'backend.main:app', '--reload']
# 构建完整的命令
full_cmd = f'source {activate_cmd} && cd /home/lqs1/app/ && uvicorn backend.main:app --reload'
# 使用 Popen 执行命令
subprocess.Popen(full_cmd, shell=True, executable='/bin/bash')
if __name__ == '__main__':
run_service()

16
backend/2.py Executable file
View File

@ -0,0 +1,16 @@
# stop_service.py
import subprocess
def stop_service():
# 查找 uvicorn 进程 ID
find_cmd = "ps aux | grep 'uvicorn backend.main:app --reload' | grep -v grep | awk '{print $2}'"
process = subprocess.run(find_cmd, shell=True, capture_output=True, text=True)
pid = process.stdout.strip()
if pid:
# 终止进程
kill_cmd = f"kill {pid}"
subprocess.run(kill_cmd, shell=True)
if __name__ == '__main__':
stop_service()

22
backend/README.md Executable file
View File

@ -0,0 +1,22 @@
uvicorn backend.main:app --reload
import configparser
uvicorn main:app --reload
# 创建ConfigParser对象
config = configparser.ConfigParser()
# 读取配置文件
config.read('app.conf')
# 获取配置项
value = config['SectionName']['KeyName']
VUE3
npm install socket.io-client
#数据库迁移
alembic init alembic
alembic revision autogenrate -m "Add is_superuser column to admin table2"
alembic upgrade head
# 删除所有表
# Base.metadata.drop_all(bind=engine)
# print("数据库表已重置")

1
backend/__init__.py Executable file
View File

@ -0,0 +1 @@
from . import models

Binary file not shown.

Binary file not shown.

Binary file not shown.

13
backend/database/1脚本.py Executable file
View File

@ -0,0 +1,13 @@
import subprocess
def run_service():
bash_cmd = f"""
source /home/lqs1/app/venv/bin/activate && \
cd /home/lqs1/app/backend/database && \
alembic revision -m "Initial migration" --autogenerate
"""
subprocess.Popen(bash_cmd, shell=True, executable='/bin/bash')
if __name__ == '__main__':
run_service()
#alembic revision -m "initial migration"

19
backend/database/2迁移.py Executable file
View File

@ -0,0 +1,19 @@
import subprocess
def run_service():
# 激活虚拟环境并运行服务
# 注意这里的命令被组合成了一个bash脚本字符串
# 首先激活虚拟环境然后改变目录最后执行alembic命令
bash_cmd = f"""
source /home/lqs1/app/venv/bin/activate && \
cd /home/lqs1/app/backend/database && \
alembic upgrade head
"""
# 使用 Popen 执行bash命令
# 注意这里使用shell=True因为我们正在执行一个bash脚本
# executable='/bin/bash' 是可选的因为默认就是bash但明确指出也无妨
subprocess.Popen(bash_cmd, shell=True, executable='/bin/bash')
if __name__ == '__main__':
run_service()

23
backend/database/3.py Executable file
View File

@ -0,0 +1,23 @@
import subprocess
def run_service():
# 激活虚拟环境并运行服务
# 注意这里的命令被组合成了一个bash脚本字符串
# 首先激活虚拟环境然后改变目录最后执行alembic命令
bash_cmd = f"""
source /home/lqs1/app/venv/bin/activate && \
cd /home/lqs1/app/backend/database && \
alembic revision -m "Add shuai column"
"""
# 使用 Popen 执行bash命令
# 注意这里使用shell=True因为我们正在执行一个bash脚本
# executable='/bin/bash' 是可选的因为默认就是bash但明确指出也无妨
subprocess.Popen(bash_cmd, shell=True, executable='/bin/bash')
if __name__ == '__main__':
run_service()
# alembic revision --autogenerate -m "Add is_superuser column to admin table2" && \
# alembic revision -m "Initial migration" --autogenerate && \
# alembic upgrade head

Binary file not shown.

116
backend/database/alembic.ini Executable file
View File

@ -0,0 +1,116 @@
# A generic, single database configuration.
[alembic]
# path to migration scripts
# Use forward slashes (/) also on windows to provide an os agnostic path
script_location = alembic
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
# Uncomment the line below if you want the files to be prepended with date and time
# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
# for all available tokens
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
# sys.path path, will be prepended to sys.path if present.
# defaults to the current working directory.
prepend_sys_path = .
# timezone to use when rendering the date within the migration file
# as well as the filename.
# If specified, requires the python>=3.9 or backports.zoneinfo library.
# Any required deps can installed by adding `alembic[tz]` to the pip requirements
# string value is passed to ZoneInfo()
# leave blank for localtime
# timezone =
# max length of characters to apply to the "slug" field
# truncate_slug_length = 40
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
# set to 'true' to allow .pyc and .pyo files without
# a source .py file to be detected as revisions in the
# versions/ directory
# sourceless = false
# version location specification; This defaults
# to alembic/versions. When using multiple version
# directories, initial revisions must be specified with --version-path.
# The path separator used here should be the separator specified by "version_path_separator" below.
# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions
# version path separator; As mentioned above, this is the character used to split
# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
# Valid values for version_path_separator are:
#
# version_path_separator = :
# version_path_separator = ;
# version_path_separator = space
version_path_separator = os # Use os.pathsep. Default configuration used for new projects.
# set to 'true' to search source files recursively
# in each "version_locations" directory
# new in Alembic version 1.10
# recursive_version_locations = false
# the output encoding used when revision files
# are written from script.py.mako
# output_encoding = utf-8
sqlalchemy.url = postgresql://fastapi:Sj89061189@localhost/fastapi
[post_write_hooks]
# post_write_hooks defines scripts or Python functions that are run
# on newly generated revision scripts. See the documentation for further
# detail and examples
# format using "black" - use the console_scripts runner, against the "black" entrypoint
# hooks = black
# black.type = console_scripts
# black.entrypoint = black
# black.options = -l 79 REVISION_SCRIPT_FILENAME
# lint with attempts to fix using "ruff" - use the exec runner, execute a binary
# hooks = ruff
# ruff.type = exec
# ruff.executable = %(here)s/.venv/bin/ruff
# ruff.options = --fix REVISION_SCRIPT_FILENAME
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

View File

@ -0,0 +1 @@
Generic single-database configuration.

Binary file not shown.

89
backend/database/alembic/env.py Executable file
View File

@ -0,0 +1,89 @@
from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from alembic import context
import sys
import os.path
sys.path.append(os.path.realpath('../models'))
import admin
target_metadata = admin.Base.metadata
# sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
# # 导入所有模型
# from models import Base
# target_metadata = Base.metadata
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
if config.config_file_name is not None:
fileConfig(config.config_file_name)
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def run_migrations_offline() -> None:
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online() -> None:
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
connectable = engine_from_config(
config.get_section(config.config_ini_section, {}),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection, target_metadata=target_metadata
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

View File

@ -0,0 +1,26 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision: str = ${repr(up_revision)}
down_revision: Union[str, None] = ${repr(down_revision)}
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
def upgrade() -> None:
${upgrades if upgrades else "pass"}
def downgrade() -> None:
${downgrades if downgrades else "pass"}

View File

@ -0,0 +1,26 @@
"""initial migration
Revision ID: 48d1670daeba
Revises:
Create Date: 2024-08-17 09:56:35.223567
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '48d1670daeba'
down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
pass
def downgrade() -> None:
pass

21
backend/database/database.py Executable file
View File

@ -0,0 +1,21 @@
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
DATABASE_URL = "postgresql://fastapi:Sj89061189@localhost/fastapi"
engine = create_engine(DATABASE_URL)
#不会提交事务 不会自动刷新 不会预加载
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
#ORM 模型都将继承自 Base
Base = declarative_base()
def get_db():
#创建一个数据库会话
db = SessionLocal()
#发送异常正确关闭
try:
yield db
finally:
db.close()

35
backend/main.py Executable file
View File

@ -0,0 +1,35 @@
from fastapi import FastAPI
app = FastAPI()
from fastapi.middleware.cors import CORSMiddleware
origins = [
"*",
# "http://localhost:7999",
# "http://www.gdsfs.top:2023",
# "http://localhost:8000",
# "http://localhost:2023",
# "https://www.gdsfs.top", # 修正了末尾的换行符和空格
# "http://www.gdsfs.top:7999", # 修正了末尾的换行符和空格
# "https://www.gdsfs.top:7999", # 修正了中间的空格
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"], # 允许所有 HTTP 方法
allow_headers=["*"], # 允许所有头部
)
from .database.database import engine
from .models.admin import Base
from .models.products import Base
# from .models import Base # 从 models 包导入所有子模块
# 在应用启动时创建表
Base.metadata.create_all(bind=engine)
from .routers import admin,products
app.include_router(admin.router, prefix="/admin", tags=["员工"])
app.include_router(products.router, prefix="/products", tags=["产品"])

5
backend/models/__init__.py Executable file
View File

@ -0,0 +1,5 @@
from . import admin, products # 导入所有包含模型的子模块
from .admin import Base # 导入 admin 模块中的 Base
# models/products/__init__.py
from .products import Base # 导入 products 模块中的 Base

Binary file not shown.

Binary file not shown.

Binary file not shown.

23
backend/models/admin.py Executable file
View File

@ -0,0 +1,23 @@
from sqlalchemy import Column, Integer, String, Boolean
#ORM基类
from sqlalchemy.ext.declarative import declarative_base
#创建ORM
Base = declarative_base()
class Admin(Base):
__tablename__ = "admin"
#字段 主键 索引
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
#唯一约束
username = Column(String, unique=True, index=True)
password = Column(String)
is_superuser = Column(Boolean, default=False, nullable=False)

20
backend/models/products.py Executable file
View File

@ -0,0 +1,20 @@
from sqlalchemy import Column, Integer, String
#ORM基类
from sqlalchemy.ext.declarative import declarative_base
#创建ORM
Base = declarative_base()
class Products(Base):
__tablename__ = "products"
#字段 主键 索引
id = Column(Integer, primary_key=True, index=True)
#唯一约束
name = Column(String, unique=True, index=True)
described = Column(String)

18
backend/requirements.txt Executable file
View File

@ -0,0 +1,18 @@
# requirements.txt
# pip install -r requirements.txt
fastapi # 检查最新版本以使用最新的功能和修复
pydantic # 数据验证和设置管理
asyncpg # 一个快速、纯Python编写的异步PostgreSQL数据库客户端
sqlalchemy # SQLAlchemy支持ORM可选如果需要使用ORM
python-multipart
uvicorn[standard]
psycopg2-binary
passlib
pyJWT
alembic#数据库迁移
#fastapi-utils==0.7.1
# 用于测试
#pytest==7.1.1
#pytest-asyncio==0.18.1

0
backend/routers/__init__.py Executable file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

133
backend/routers/admin.py Executable file
View File

@ -0,0 +1,133 @@
from fastapi import APIRouter, Depends, HTTPException, status
router = APIRouter()
from sqlalchemy.orm import Session
from datetime import datetime, timedelta
from passlib.context import CryptContext
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from typing import Optional
import jwt
from ..database.database import get_db
from ..models.admin import Admin as AdminModel
from ..schemas.admin import AdminCreate ,AdminSearchAll,AdminUpdate
from ..main import app
import re
#token加索锁#注意要完整路径
SECRET_KEY = "87d8876300bd2591a738e8ec88fc721bf57ed44944531708c4bdae88d5e9beb5"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
def verify_password(plain_password:str, hashed_password:str):
#校验明文密码
return pwd_context.verify(plain_password, hashed_password)
def authenticate_admin(username: str, password: str, db: Session):
employee = db.query(AdminModel).filter(AdminModel.username == username).first()
if not employee or not verify_password(password, employee.password):
return None
return employee
#创建超级管理员
@app.post("/admin", response_model=dict)
async def create_admin(admin: AdminCreate, db: Session = Depends(get_db)):
superuser_exists = db.query(AdminModel).filter(AdminModel.is_superuser == True).first()
if superuser_exists:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="username already registered")
new_admin = AdminModel(
name="admin",
username=admin.username,
password=get_password_hash(admin.password),
is_superuser=True,
)
db.add(new_admin)
db.commit()
db.refresh(new_admin)
return {"message": "hr_admin created successfully"}
@app.post("/", response_model=dict)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)):
authenticated_employee = authenticate_admin(form_data.username, form_data.password, db)
if not authenticated_employee:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect usernamel or password")
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(data={"sub": authenticated_employee.username}, expires_delta=access_token_expires)
return {"access_token": access_token, "token_type": "bearer"}
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/")
#哈希功能
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def get_password_hash(password):
return pwd_context.hash(password)
# 定义手机号码和密码强度的正则表达式
PHONE_REGEX = r'^1[3-9]\d{9}$' # 中国手机号码的正则表达式
PASSWORD_REGEX = r'^(?=.*\d)(?=.*[a-zA-Z]).{8,}$' # 至少包含一个数字和一个字母长度至少8位
#增加用户
@router.post("/create", response_model=dict)
async def create(admin: AdminCreate, db: Session = Depends(get_db)):
# 验证手机号码
if not re.match(PHONE_REGEX, admin.username):
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid phone number")
# 验证密码强度
if not re.match(PASSWORD_REGEX, admin.password):
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Password must contain at least one digit and one letter, and be at least 8 characters long")
existing_username = db.query(AdminModel).filter(AdminModel.username == admin.username).first()
#已经存在用户
if existing_username:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="username already registered")
#密码哈希
new_admin = AdminModel(
name=admin.name,
username=admin.username,
password=get_password_hash(admin.password)
)
db.add(new_admin)
db.commit()
db.refresh(new_admin)
return {"message": "hr_admin created successfully"}
#删除用户
@router.delete("/{admin_id}", response_model=dict)
async def delete(admin_id: int, db: Session = Depends(get_db), token: str = Depends(oauth2_scheme)):
# ID查找员工
admin = db.query(AdminModel).filter(AdminModel.id == admin_id).first()
if not admin:
raise HTTPException(status_code=404, detail="admin not found")
# 删除员工
db.delete(admin)
db.commit()
return {"message": f"Deleted admin {admin_id}"}
#更新密码用户名
@router.put("/{user_id}", response_model=AdminSearchAll) # 注意这里假设AdminSearchAll是你想要返回的模型如果不是请替换
async def update(user_id: int, updated_employee: AdminUpdate, db: Session = Depends(get_db), token: str = Depends(oauth2_scheme)):
employee = db.query(AdminModel).filter(AdminModel.id == user_id).first()
if not employee:
raise HTTPException(status_code=404, detail="Employee not found")
# 检查是否有提供新的密码,并且密码不为空
if updated_employee.password:
# 对密码进行哈希处理
hashed_password = get_password_hash(updated_employee.password)
# 更新员工的密码字段
employee.password = hashed_password
# 更新其他员工信息(排除未设置的字段)
for key, value in updated_employee.dict(exclude_unset=True, exclude={'password'}).items(): # 如果password已处理这里排除它
setattr(employee, key, value)
db.commit()
db.refresh(employee)
return employee
#查寻整张表
@router.get("/search", response_model=list[AdminSearchAll])
async def search_all(db: Session = Depends(get_db) ,token: str = Depends(oauth2_scheme)):
admin = db.query(AdminModel).all()
return admin
#, token: str = Depends(oauth2_scheme)

48
backend/routers/products.py Executable file
View File

@ -0,0 +1,48 @@
from fastapi import APIRouter, Depends, HTTPException
router = APIRouter()
from sqlalchemy.orm import Session
from fastapi.security import OAuth2PasswordBearer
from ..database.database import get_db
from ..models.products import Products as ProductsModel
from ..schemas.products import ProductsCreate ,ProductsSearchAll,ProductsUpdate
#加token
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/")
#增加
@router.post("/create", response_model=dict)
async def create(Products: ProductsCreate, db: Session = Depends(get_db),token: str = Depends(oauth2_scheme)):
new_self = ProductsModel(
name=Products.name,
described=Products.described,
)
db.add(new_self)
db.commit()
db.refresh(new_self)
return {"message": "hr_Products created successfully"}
#删除
@router.delete("/{router_id}", response_model=dict)
async def delete(router_id: int, db: Session = Depends(get_db), token: str = Depends(oauth2_scheme)):
# ID查找员工
Products = db.query(ProductsModel).filter(ProductsModel.id == router_id).first()
if not Products:
raise HTTPException(status_code=404, detail="Products not found")
# 删除员工
db.delete(Products)
db.commit()
return {"message": f"Deleted Products {router_id}"}
#更新
@router.put("/{router_id}", response_model=ProductsSearchAll) # 注意这里假设ProductsSearchAll是你想要返回的模型如果不是请替换
async def update(router_id: int, ProductsUpdatesql: ProductsUpdate, db: Session = Depends(get_db), token: str = Depends(oauth2_scheme)):
sql_id = db.query(ProductsModel).filter(ProductsModel.id == router_id).first()
if not sql_id:
raise HTTPException(status_code=404, detail="Employee not found")
for key, value in ProductsUpdatesql.dict(exclude_unset=True).items():
setattr(sql_id, key, value)
db.commit()
db.refresh(sql_id)
return sql_id
#查寻整张表
@router.get("/search", response_model=list[ProductsSearchAll])
async def search_all(db: Session = Depends(get_db), token: str = Depends(oauth2_scheme)):
Products = db.query(ProductsModel).all()
return Products

0
backend/schemas/__init__.py Executable file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

21
backend/schemas/admin.py Executable file
View File

@ -0,0 +1,21 @@
from pydantic import BaseModel
# 用于定义数据验证模型,通常用于 API 层。
# 不直接与数据库交互,而是用于验证请求数据是否符合预期的结构。
# 支持数据的序列化和反序列化,方便处理 JSON 数据。
#创建基础模型
class Admin(BaseModel):
name: str | None = None
username:str
password:str
is_superuser:bool | None = None
#创建
class AdminCreate(Admin):
pass
#更新
class AdminUpdate(Admin):
pass
#查询
class AdminSearchAll(Admin):
id: int

22
backend/schemas/products.py Executable file
View File

@ -0,0 +1,22 @@
from pydantic import BaseModel
from typing import Optional
# 用于定义数据验证模型,通常用于 API 层。
# 不直接与数据库交互,而是用于验证请求数据是否符合预期的结构。
# 支持数据的序列化和反序列化,方便处理 JSON 数据。
#创建基础模型
class Products(BaseModel):
name: str
described:str
#创建
class ProductsCreate(Products):
pass
#更新
class ProductsUpdate(Products):
name: Optional[str] = None # 设置 name 为可选字段
#查询
class ProductsSearchAll(Products):
id: int

25
backend/vue3README.md Executable file
View File

@ -0,0 +1,25 @@
<!-- npm init vue@latest
cd vue -->
npm install
axios
my-vue-app/
├── node_modules/ # 通过 npm 或 yarn 安装的项目依赖包
├── public/ # 存放静态资源和公共资源
│ ├── favicon.ico # 网站图标
│ ├── index.html # HTML 模板文件
│ └── ... # 其他静态资源如图片、字体等
├── src/ # 源代码目录
│ ├── App.vue # 根组件
│ ├── main.js # 应用入口文件
│ ├── assets/ # 静态资源,如图片、样式文件等
│ ├── components/ # 全局可复用的组件
│ ├── views/ # 视图组件
│ ├── router/ # 路由配置
│ ├── store/ # Vuex 状态管理
│ ├── utils/ # 工具函数
│ └── ...
├── .gitignore # Git 忽略文件配置
├── package.json # 项目元数据和依赖关系
├── vue.config.js # Vue CLI 配置文件
├── README.md # 项目文档
└── ...

0
vue/.env Normal file
View File

30
vue/.gitignore vendored Executable file
View File

@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo

5
vue/.npmignore Executable file
View File

@ -0,0 +1,5 @@
node_modules/
test/
README.md
CHANGELOG.md
LICENSE

3
vue/.vscode/extensions.json vendored Executable file
View File

@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar"]
}

37
vue/README.md Executable file
View File

@ -0,0 +1,37 @@
# vue
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
## Type Support for `.vue` Imports in TS
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
## Customize configuration
See [Vite Configuration Reference](https://vitejs.dev/config/).
## Project Setup
```sh
npm install
```
### Compile and Hot-Reload for Development
```sh
npm run dev
```
### Type-Check, Compile and Minify for Production
```sh
npm run build
```
https://www.gzsjfz.top/web#cids=1&action=menu
https://www.gzsjfz.top/web#menu_id=339&cids=1&action=372&model=product.template&view_type=kanban
https://www.gzsjfz.top/web#id=2509&menu_id=339&cids=1&action=372&model=product.template&view_type=form
https://www.gzsjfz.top/web#action=372&model=product.template&view_type=list&menu_id=339&cids=1

1
vue/env.d.ts vendored Executable file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

23
vue/index.html Executable file
View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>时时6叔</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
<style>
body{
width: 100%;
height: 100%;
background-color: rgb(240, 230, 140) ;
/* 渐变背景色 */
background: radial-gradient(circle,rgb(240, 230, 140),rgb(230, 220, 125),
rgb(225, 205, 60),rgb(200, 180, 15));
}
</style>

1901
vue/package-lock.json generated Executable file

File diff suppressed because it is too large Load Diff

30
vue/package.json Executable file
View File

@ -0,0 +1,30 @@
{
"name": "vue",
"version": "0.0.0",
"private": false,
"type": "module",
"scripts": {
"dev": "vite",
"build": "run-p type-check \"build-only {@}\" --",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --build --force"
},
"dependencies": {
"axios": "^1.7.3",
"element-plus": "^2.8.0",
"pinia": "^2.1.7",
"vue": "^3.4.29",
"vue-router": "^4.3.3"
},
"devDependencies": {
"@tsconfig/node20": "^20.1.4",
"@types/node": "^20.14.5",
"@vitejs/plugin-vue": "^5.0.5",
"@vue/tsconfig": "^0.5.1",
"npm-run-all2": "^6.2.0",
"typescript": "~5.4.0",
"vite": "^5.3.1",
"vue-tsc": "^2.0.21"
}
}

BIN
vue/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

0
vue/src/.env Normal file
View File

49
vue/src/App.vue Executable file
View File

@ -0,0 +1,49 @@
<template>
<header>
<!-- 显示导航组件的条件 -->
<HomeView v-if="isLoggedIn" />
<!-- 登录链接仅当用户未登录时显示 -->
<RouterLink :to="{ name: '登录' }" v-if="!isLoggedIn"></RouterLink>
<!-- <img alt="Vue logo" class="logo" src="@/assets/login.png" width="125" height="125" /> -->
</header>
<div class="wrapper">
<RouterView></RouterView>
</div>
</template>
<script setup lang="ts">
import { RouterLink, RouterView } from 'vue-router'
import HomeView from '@/components/Home.vue'
import { useRouter, useRoute } from 'vue-router';
import { onMounted, ref } from 'vue';
const router = useRouter();
//
const isLoggedIn = ref(false);
onMounted(() => {
//
checkLoginStatus();
});
//
const checkLoginStatus = () => {
isLoggedIn.value = localStorage.getItem('isLoggedIn') === 'true';
};
//
router.afterEach(() => {
checkLoginStatus(); //
});
</script>
<style scoped>
/* rgb(240, 230, 140); */
header {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 5vh;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20px;
z-index: 1000;
}
</style>

BIN
vue/src/assets/6shu.mp4 Normal file

Binary file not shown.

86
vue/src/assets/base.css Executable file
View File

@ -0,0 +1,86 @@
/* color palette from <https://github.com/vuejs/theme> */
:root {
--vt-c-white: #ffffff;
--vt-c-white-soft: #f8f8f8;
--vt-c-white-mute: #f2f2f2;
--vt-c-black: #181818;
--vt-c-black-soft: #222222;
--vt-c-black-mute: #282828;
--vt-c-indigo: #2c3e50;
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
--vt-c-text-light-1: var(--vt-c-indigo);
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
--vt-c-text-dark-1: var(--vt-c-white);
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
}
/* semantic color variables for this project */
:root {
--color-background: var(--vt-c-white);
--color-background-soft: var(--vt-c-white-soft);
--color-background-mute: var(--vt-c-white-mute);
--color-border: var(--vt-c-divider-light-2);
--color-border-hover: var(--vt-c-divider-light-1);
--color-heading: var(--vt-c-text-light-1);
--color-text: var(--vt-c-text-light-1);
--section-gap: 160px;
}
@media (prefers-color-scheme: dark) {
:root {
--color-background: var(--vt-c-black);
--color-background-soft: var(--vt-c-black-soft);
--color-background-mute: var(--vt-c-black-mute);
--color-border: var(--vt-c-divider-dark-2);
--color-border-hover: var(--vt-c-divider-dark-1);
--color-heading: var(--vt-c-text-dark-1);
--color-text: var(--vt-c-text-dark-2);
}
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
font-weight: normal;
}
body {
min-height: 100vh;
color: var(--color-text);
background: var(--color-background);
transition:
color 0.5s,
background-color 0.5s;
line-height: 1.6;
font-family:
Inter,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
Oxygen,
Ubuntu,
Cantarell,
'Fira Sans',
'Droid Sans',
'Helvetica Neue',
sans-serif;
font-size: 15px;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

BIN
vue/src/assets/login.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

1
vue/src/assets/logo.svg Executable file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>

After

Width:  |  Height:  |  Size: 276 B

35
vue/src/assets/main.css Executable file
View File

@ -0,0 +1,35 @@
@import './base.css';
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
font-weight: normal;
}
a,
.green {
text-decoration: none;
color: hsla(160, 100%, 37%, 1);
transition: 0.4s;
padding: 3px;
}
@media (hover: hover) {
a:hover {
background-color: hsla(160, 100%, 37%, 0.2);
}
}
@media (min-width: 1024px) {
body {
display: flex;
place-items: center;
}
#app {
display: grid;
grid-template-columns: 1fr 1fr;
padding: 0 2rem;
}
}

409
vue/src/chishi/login灯.vue Normal file
View File

@ -0,0 +1,409 @@
<template>
<div class="cube">
<div class="top">
<h2></h2>
</div>
<div class="box">
<span style="--i:0">
<h2></h2>
<h2></h2>
</span>
<span style="--i:1">
<h2></h2>
<h2></h2>
</span>
<span style="--i:2">
<h2></h2>
<h2></h2>
</span>
<span style="--i:3">
<h2></h2>
<h2></h2>
</span>
</div>
</div>
</template>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
/* 解决手机浏览器点击有选框的问题 */
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
body {
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
min-height: 100vh;
background-color: #050505;
}
.cube {
width: 300px;
height: 300px;
position: relative;
transform-style: preserve-3d;
animation: animate 10s linear infinite;
}
@keyframes animate {
0% {
transform: rotateX(-30deg) rotateY(0deg);
}
100% {
transform: rotateX(-30deg) rotateY(-360deg);
}
}
.cube div {
width: 100%;
height: 100%;
position: absolute;
transform-style: preserve-3d;
}
.cube .box span {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
background: linear-gradient(#151515, rgb(255, 40, 40));
position: absolute;
transform: rotateY(calc(90deg * var(--i))) translateZ(150px);
transform-style: preserve-3d;
}
.cube .box span h2 {
font-size: 10em;
color: #ffffff;
position: absolute;
transform: translateZ(50px);
}
.cube .box span h2:nth-child(2) {
color: rgba(0, 0, 0, 0.1);
filter: blur(2px);
transform: translateZ(0) translateY(20px);
}
.cube .top {
display: flex;
justify-content: center;
align-items: center;
background-color: #151515;
position: absolute;
transform: rotateX(90deg) translateZ(150px);
}
.cube .top::before {
content: "";
width: 100%;
height: 100%;
background-color: rgb(255, 50, 50);
filter: blur(20px);
box-shadow: 0 0 120px rgba(255, 50, 50, 0.2), 0 0 200px rgba(255, 50, 50, 0.4),
0 0 300px rgba(255, 50, 50, 0.6), 0 0 400px rgba(255, 50, 50, 0.8),
0 0 500px rgba(255, 50, 50, 1);
position: absolute;
transform: translateZ(-400px);
}
.cube .top h2 {
font-size: 10em;
color: #fff;
position: absolute;
transform: translateZ(70px) rotateX(-90deg) rotateY(45deg);
}
* {
/* 常规初始化 */
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: "阿里巴巴普惠体";
/* 解决手机浏览器点击有选框的问题 */
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
body {
/* 常规居中显示 */
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
min-height: 100vh;
background-color: #3f3f3f;
}
.box {
/* flex 让盒子横着排列 */
display: flex;
/* 元素之间的距离 */
gap: 10px;
/* 3D模式 */
transform-style: preserve-3d;
/* 旋转一下,看着立体一点 */
transform: rotateY(30deg) rotateX(10deg);
}
.box .text {
/* 单个元素的宽高 */
width: 100px;
height: 100px;
/* 给个定位 */
position: relative;
/* 3D模式 */
transform-style: preserve-3d;
/* 过度动画时间、动画曲线 */
transition: 2.5s ease-in-out;
/* 动画延时,通过 calc 和 --i 计算出每个 text 元素对应的延时时间 */
transition-delay: calc(0.25s * var(--i));
}
/* 方块左边空的一面 */
.box .text::before {
content: "";
width: 100%;
height: 100%;
background-color: #373737;
position: absolute;
/* 下面的旋转位移都按左边为基础 */
transform-origin: left;
/* 调整位置盖住左边面 */
transform: rotateY(90deg) translateX(-50%);
}
.box .text:first-child:before {
/* 第一块给个不一样的颜色 */
background-color: #e95e87;
}
.box:hover .text {
/* 鼠标放上去旋转一定角度 */
transform: rotateX(-540deg);
}
.box:hover .text:first-child {
/* 第一块来个不同方向 */
transform: rotateX(540deg);
}
.box .text span {
/* 内部元素居中对齐 */
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
/* 背景渐变颜色 */
background: linear-gradient(#434343, #535353);
/* 字体大小、粗细、颜色 */
font-size: 4em;
font-weight: 500;
color: #fff;
position: absolute;
/* 3D模式 */
transform-style: preserve-3d;
/* 旋转 90deg 的倍数,组成正方体四个面,调整到合适位置 */
transform: rotateX(calc(90deg * var(--j))) translateZ(50px);
}
.box .text:first-child span {
/* 第一个颜色不一样 */
background: linear-gradient(#e97089, #ed82ad);
}
/* 立体文字*/
* {
/* 常规初始化 */
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
/* 常规居中显示 */
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
min-height: 100vh;
/* 渐变背景色 */
background: radial-gradient(circle, #0077ee, #1f4f99, #1b2949, #000);
}
.box {
/* 父盒子确定整体宽高 */
width: 50vmin;
height: 50vmin;
/* 3D模式给一个透视距离 */
transform-style: preserve-3d;
perspective: 100vmin;
position: relative;
}
.layer {
/* 内部元素居中对齐,不要也行,就是有些时候不在中间 */
display: flex;
justify-content: center;
align-items: center;
/* 文字区域的宽高 */
width: 100%;
height: 100%;
/* 3D模式 */
transform-style: preserve-3d;
/* 先旋转一下方便查看样式,也是配合动画的初始化 */
transform: rotateX(40deg) rotateY(40deg) translateZ(0);
/* 旋转动画,只是为了更好地展示文字立体效果 */
animation: animate 10s infinite alternate ease-in-out;
/* 定位叠起来 */
position: absolute;
}
@keyframes animate {
100% {
/* 动画就是简单左右旋转 */
transform: rotateX(-40deg) rotateY(-40deg);
}
}
.layer::after {
/* 给每层的 after 填充文字 */
content: "立体文字";
/* 文字颜色、大小、粗细、行高、居中 */
color: #f5f5f5;
font-size: 20vmin;
font-weight: bold;
line-height: 22vmin;
text-align: center;
/* 文字阴影 */
text-shadow: 0.4vmin 0 1vmin rgba(0, 0, 0, 0.2);
position: absolute;
}
.layer:nth-child(1)::after {
/* 第一层就是正常显示的一层 */
transform: translateZ(0);
}
.layer:nth-child(2)::after {
/* 后面的每一层都逐层 -0.15vmin */
transform: translateZ(-0.15vmin);
}
.layer:nth-child(3)::after {
transform: translateZ(-0.3vmin);
}
.layer:nth-child(4)::after {
transform: translateZ(-0.45vmin);
}
.layer:nth-child(5)::after {
transform: translateZ(-0.6vmin);
}
.layer:nth-child(6)::after {
transform: translateZ(-0.75vmin);
}
.layer:nth-child(7)::after {
transform: translateZ(-0.9vmin);
}
.layer:nth-child(8)::after {
transform: translateZ(-1.05vmin);
}
.layer:nth-child(9)::after {
transform: translateZ(-1.2vmin);
}
.layer:nth-child(10)::after {
transform: translateZ(-1.35vmin);
}
.layer:nth-child(11)::after {
transform: translateZ(-1.5vmin);
}
.layer:nth-child(12)::after {
transform: translateZ(-1.65vmin);
}
.layer:nth-child(13)::after {
transform: translateZ(-1.8vmin);
}
.layer:nth-child(14)::after {
transform: translateZ(-1.95vmin);
}
.layer:nth-child(15)::after {
transform: translateZ(-2.1vmin);
}
.layer:nth-child(16)::after {
transform: translateZ(-2.25vmin);
}
.layer:nth-child(17)::after {
transform: translateZ(-2.4vmin);
}
.layer:nth-child(18)::after {
transform: translateZ(-2.55vmin);
}
.layer:nth-child(19)::after {
transform: translateZ(-2.7vmin);
}
.layer:nth-child(20)::after {
transform: translateZ(-2.85vmin);
}
/* 第 10 层往后 */
.layer:nth-child(n + 10)::after {
/* 文本宽度和颜色,后面会覆盖掉,其实也就中间有一个阴影层次感 */
-webkit-text-stroke: 0.3vmin rgba(0, 0, 0, 0.2);
}
/* 第 11 层往后 */
.layer:nth-child(n + 11)::after {
/* 文字要大一圈,换个颜色 */
-webkit-text-stroke: 1.5vmin #1f90ff;
/* 字体阴影,还是加强层次感 */
text-shadow: 0.6vmin 0 0.6vmin #00366b, 0.5vmin 0.5vmin 0.5vmin #002951,
0 0.6vmin 0.6vmin #00366b;
}
/* 第 12 层往后 */
.layer:nth-child(n + 12)::after {
/* 换个字体颜色 */
-webkit-text-stroke: 1.5vmin #0077ee;
}
.layer:last-child:after {
/* 最后一层来个黑色阴影垫底 */
-webkit-text-stroke: 1.5vmin rgba(0, 0, 0, 0.1);
}
.layer:first-child:after {
/* 把第一层的颜色改成最亮,字体阴影也不要 */
color: #fff;
text-shadow: none;
}
</style>

View File

View File

View File

@ -0,0 +1,433 @@
<template>
<div class="cube">
<div class="top">
<h2></h2>
</div>
<div class="box">
<span style="--i:0">
<h2></h2>
<h2></h2>
</span>
<span style="--i:1">
<h2></h2>
<h2></h2>
</span>
<span style="--i:2">
<h2></h2>
<h2></h2>
</span>
<span style="--i:3">
<h2></h2>
<h2></h2>
</span>
</div>
</div>
<div class="box">
<div class="text" style="--i:0">
<span style="--j:0">3D</span>
<span style="--j:1">3</span>
<span style="--j:2">2</span>
<span style="--j:3">6</span>
</div>
<div class="text" style="--i:1">
<span style="--j:0"></span>
<span style="--j:1">1</span>
<span style="--j:2">0</span>
<span style="--j:3">8</span>
</div>
<div class="text" style="--i:2">
<span style="--j:0"></span>
<span style="--j:1">3</span>
<span style="--j:2">2</span>
<span style="--j:3">5</span>
</div>
<div class="text" style="--i:3">
<span style="--j:0"></span>
<span style="--j:1">1</span>
<span style="--j:2">4</span>
<span style="--j:3">7</span>
</div>
</div>
<div class="box">
<div class="layer"></div>
<div class="layer"></div>
<div class="layer"></div>
<div class="layer"></div>
<div class="layer"></div>
<div class="layer"></div>
<div class="layer"></div>
<div class="layer"></div>
<div class="layer"></div>
<div class="layer"></div>
<div class="layer"></div>
<div class="layer"></div>
<div class="layer"></div>
<div class="layer"></div>
<div class="layer"></div>
<div class="layer"></div>
<div class="layer"></div>
<div class="layer"></div>
<div class="layer"></div>
<div class="layer"></div>
</div>
</template>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
/* 解决手机浏览器点击有选框的问题 */
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
.cube {
width: 300px;
height: 300px;
position: relative;
transform-style: preserve-3d;
animation: animate 10s linear infinite;
}
@keyframes animate {
0% {
transform: rotateX(-30deg) rotateY(0deg);
}
100% {
transform: rotateX(-30deg) rotateY(-360deg);
}
}
.cube div {
width: 100%;
height: 100%;
position: absolute;
transform-style: preserve-3d;
}
.cube .box span {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
background: linear-gradient(#151515, rgb(255, 40, 40));
position: absolute;
transform: rotateY(calc(90deg * var(--i))) translateZ(150px);
transform-style: preserve-3d;
}
.cube .box span h2 {
font-size: 10em;
color: #ffffff;
position: absolute;
transform: translateZ(50px);
}
.cube .box span h2:nth-child(2) {
color: rgba(0, 0, 0, 0.1);
filter: blur(2px);
transform: translateZ(0) translateY(20px);
}
.cube .top {
display: flex;
justify-content: center;
align-items: center;
background-color: #151515;
position: absolute;
transform: rotateX(90deg) translateZ(150px);
}
.cube .top::before {
content: "";
width: 100%;
height: 100%;
background-color: rgb(255, 50, 50);
filter: blur(20px);
box-shadow: 0 0 120px rgba(255, 50, 50, 0.2), 0 0 200px rgba(255, 50, 50, 0.4),
0 0 300px rgba(255, 50, 50, 0.6), 0 0 400px rgba(255, 50, 50, 0.8),
0 0 500px rgba(255, 50, 50, 1);
position: absolute;
transform: translateZ(-400px);
}
.cube .top h2 {
font-size: 10em;
color: #fff;
position: absolute;
transform: translateZ(70px) rotateX(-90deg) rotateY(45deg);
}
* {
/* 常规初始化 */
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: "阿里巴巴普惠体";
/* 解决手机浏览器点击有选框的问题 */
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
.box {
/* flex 让盒子横着排列 */
display: flex;
/* 元素之间的距离 */
gap: 10px;
/* 3D模式 */
transform-style: preserve-3d;
/* 旋转一下,看着立体一点 */
transform: rotateY(30deg) rotateX(10deg);
}
.box .text {
/* 单个元素的宽高 */
width: 100px;
height: 100px;
/* 给个定位 */
position: relative;
/* 3D模式 */
transform-style: preserve-3d;
/* 过度动画时间、动画曲线 */
transition: 2.5s ease-in-out;
/* 动画延时,通过 calc 和 --i 计算出每个 text 元素对应的延时时间 */
transition-delay: calc(0.25s * var(--i));
}
/* 方块左边空的一面 */
.box .text::before {
content: "";
width: 100%;
height: 100%;
background-color: #373737;
position: absolute;
/* 下面的旋转位移都按左边为基础 */
transform-origin: left;
/* 调整位置盖住左边面 */
transform: rotateY(90deg) translateX(-50%);
}
.box .text:first-child:before {
/* 第一块给个不一样的颜色 */
background-color: #e95e87;
}
.box:hover .text {
/* 鼠标放上去旋转一定角度 */
transform: rotateX(-540deg);
}
.box:hover .text:first-child {
/* 第一块来个不同方向 */
transform: rotateX(540deg);
}
.box .text span {
/* 内部元素居中对齐 */
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
/* 背景渐变颜色 */
background: linear-gradient(#434343, #535353);
/* 字体大小、粗细、颜色 */
font-size: 4em;
font-weight: 500;
color: #fff;
position: absolute;
/* 3D模式 */
transform-style: preserve-3d;
/* 旋转 90deg 的倍数,组成正方体四个面,调整到合适位置 */
transform: rotateX(calc(90deg * var(--j))) translateZ(50px);
}
.box .text:first-child span {
/* 第一个颜色不一样 */
background: linear-gradient(#e97089, #ed82ad);
}
/* 立体文字*/
* {
/* 常规初始化 */
margin: 0;
padding: 0;
box-sizing: border-box;
}
.box {
/* 父盒子确定整体宽高 */
width: 50vmin;
height: 50vmin;
/* 3D模式给一个透视距离 */
transform-style: preserve-3d;
perspective: 100vmin;
position: relative;
}
.layer {
/* 内部元素居中对齐,不要也行,就是有些时候不在中间 */
display: flex;
justify-content: center;
align-items: center;
/* 文字区域的宽高 */
width: 100%;
height: 100%;
/* 3D模式 */
transform-style: preserve-3d;
/* 先旋转一下方便查看样式,也是配合动画的初始化 */
transform: rotateX(40deg) rotateY(40deg) translateZ(0);
/* 旋转动画,只是为了更好地展示文字立体效果 */
animation: animate 10s infinite alternate ease-in-out;
/* 定位叠起来 */
position: absolute;
}
@keyframes animate {
100% {
/* 动画就是简单左右旋转 */
transform: rotateX(-40deg) rotateY(-40deg);
}
}
.layer::after {
/* 给每层的 after 填充文字 */
content: "立体文字";
/* 文字颜色、大小、粗细、行高、居中 */
color: #f5f5f5;
font-size: 20vmin;
font-weight: bold;
line-height: 22vmin;
text-align: center;
/* 文字阴影 */
text-shadow: 0.4vmin 0 1vmin rgba(0, 0, 0, 0.2);
position: absolute;
}
.layer:nth-child(1)::after {
/* 第一层就是正常显示的一层 */
transform: translateZ(0);
}
.layer:nth-child(2)::after {
/* 后面的每一层都逐层 -0.15vmin */
transform: translateZ(-0.15vmin);
}
.layer:nth-child(3)::after {
transform: translateZ(-0.3vmin);
}
.layer:nth-child(4)::after {
transform: translateZ(-0.45vmin);
}
.layer:nth-child(5)::after {
transform: translateZ(-0.6vmin);
}
.layer:nth-child(6)::after {
transform: translateZ(-0.75vmin);
}
.layer:nth-child(7)::after {
transform: translateZ(-0.9vmin);
}
.layer:nth-child(8)::after {
transform: translateZ(-1.05vmin);
}
.layer:nth-child(9)::after {
transform: translateZ(-1.2vmin);
}
.layer:nth-child(10)::after {
transform: translateZ(-1.35vmin);
}
.layer:nth-child(11)::after {
transform: translateZ(-1.5vmin);
}
.layer:nth-child(12)::after {
transform: translateZ(-1.65vmin);
}
.layer:nth-child(13)::after {
transform: translateZ(-1.8vmin);
}
.layer:nth-child(14)::after {
transform: translateZ(-1.95vmin);
}
.layer:nth-child(15)::after {
transform: translateZ(-2.1vmin);
}
.layer:nth-child(16)::after {
transform: translateZ(-2.25vmin);
}
.layer:nth-child(17)::after {
transform: translateZ(-2.4vmin);
}
.layer:nth-child(18)::after {
transform: translateZ(-2.55vmin);
}
.layer:nth-child(19)::after {
transform: translateZ(-2.7vmin);
}
.layer:nth-child(20)::after {
transform: translateZ(-2.85vmin);
}
/* 第 10 层往后 */
.layer:nth-child(n + 10)::after {
/* 文本宽度和颜色,后面会覆盖掉,其实也就中间有一个阴影层次感 */
-webkit-text-stroke: 0.3vmin rgba(0, 0, 0, 0.2);
}
/* 第 11 层往后 */
.layer:nth-child(n + 11)::after {
/* 文字要大一圈,换个颜色 */
-webkit-text-stroke: 1.5vmin #1f90ff;
/* 字体阴影,还是加强层次感 */
text-shadow: 0.6vmin 0 0.6vmin #00366b, 0.5vmin 0.5vmin 0.5vmin #002951,
0 0.6vmin 0.6vmin #00366b;
}
/* 第 12 层往后 */
.layer:nth-child(n + 12)::after {
/* 换个字体颜色 */
-webkit-text-stroke: 1.5vmin #0077ee;
}
.layer:last-child:after {
/* 最后一层来个黑色阴影垫底 */
-webkit-text-stroke: 1.5vmin rgba(0, 0, 0, 0.1);
}
.layer:first-child:after {
/* 把第一层的颜色改成最亮,字体阴影也不要 */
color: #fff;
text-shadow: none;
}
</style>

View File

@ -0,0 +1,51 @@
<template>
<div>
<input v-model="name" placeholder="Name" />
<input v-model="username" placeholder="Username" />
<input v-model="password" placeholder="Password" />
<button @click="register">Register</button>
<p class="error-message">{{ errorMessage }}</p>
</div>
</template>
<script setup lang="ts">
import axios from 'axios';
import { ref, getCurrentInstance } from 'vue';
// setup
const { proxy } = getCurrentInstance();
const name = ref('');
const username = ref('');
const password = ref('');
const errorMessage = ref('');
const register = async () => {
try {
const response = await axios.post(`${proxy.$baseUrl}/admin/create`, {
name: name.value,
username: username.value,
password: password.value
});
console.log(response.data);
errorMessage.value = ''; //
} catch (error) {
if (axios.isAxiosError(error)) {
if (error.response) {
errorMessage.value = error.response.data.message || '注册失败,请检查输入信息。';
} else {
errorMessage.value = '网络错误,请稍后再试。';
}
} else {
errorMessage.value = '未知错误,请稍后再试。';
}
console.error(error);
}
};
</script>
<style scoped>
.error-message {
color: red;
}
</style>

View File

@ -0,0 +1,41 @@
<script setup lang="ts">
defineProps<{
msg: string
}>()
</script>
<template>
<div class="greetings">
<h1 class="green">{{ msg }}</h1>
<h3>
Youve successfully created a project with
<a href="https://vitejs.dev/" target="_blank" rel="noopener">Vite</a> +
<a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>. What's next?
</h3>
</div>
</template>
<style scoped>
h1 {
font-weight: 500;
font-size: 2.6rem;
position: relative;
top: -10px;
}
h3 {
font-size: 1.2rem;
}
.greetings h1,
.greetings h3 {
text-align: center;
}
@media (min-width: 1024px) {
.greetings h1,
.greetings h3 {
text-align: left;
}
}
</style>

View File

@ -0,0 +1,49 @@
<template>
<div>
<select @change="navigateToRoute">
<option v-for="route in routes" :key="route.path" :value="route.path">
{{ route.name }}
</option>
</select>
</div>
<div>
<button @click="logout">注销{{ currentUser?.username }}</button>
</div>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router';
import { useUserStore } from '@/stores/userStore';
const userStore = useUserStore();
const router = useRouter();
const currentUser = userStore.currentUser;
const routes = [
{ path: '/', name: '登录' },
{ path: '/home', name: '首页' },
{ path: '/list', name: '列表' },
{ path: '/form', name: '表单' },
{ path: '/kanban', name: '看板' },
{ path: '/ceshi', name: '测试' },
];
function navigateToRoute(event) {
const routePath = event.target.value;
router.push(routePath);
}
const logout = async () => {
// token
localStorage.removeItem('accessToken');
//
// router.push({ name: '' });
window.location.href = '/'; // /login
localStorage.setItem('isLoggedIn', 'False'); //
console.log('执行完毕');
};
</script>

View File

@ -0,0 +1,10 @@
<template>
<div>
<input/>
</div>
</template>
<script>
</script>

View File

@ -0,0 +1,42 @@
<template>
<div>
<button @click="fetchAdmins">获取管理员列表</button>
<ul>
<li v-for="admin in admins" :key="admin.id">
{{ admin.name }} - {{ admin.username }}
</li>
</ul>
<p>{{ errorMessage }}</p>
</div>
</template>
<script setup lang="ts">
import axios from 'axios';
import { ref, getCurrentInstance } from 'vue';
// setup
const { proxy } = getCurrentInstance();
const admins = ref([]);
const errorMessage = ref('');
const fetchAdmins = async () => {
try {
const response = await axios.get(`${proxy.$baseUrl}/admin/search`);
admins.value = response.data;
errorMessage.value = '';
} catch (error) {
if (axios.isAxiosError(error)) {
if (error.response) {
errorMessage.value = error.response.data.detail || '获取管理员列表失败,请检查输入信息。';
} else {
errorMessage.value = '网络错误,请稍后再试。';
}
} else {
errorMessage.value = '未知错误,请稍后再试。';
}
console.error(error);
}
};
</script>

View File

@ -0,0 +1,88 @@
<script setup lang="ts">
import WelcomeItem from './WelcomeItem.vue'
import DocumentationIcon from './icons/IconDocumentation.vue'
import ToolingIcon from './icons/IconTooling.vue'
import EcosystemIcon from './icons/IconEcosystem.vue'
import CommunityIcon from './icons/IconCommunity.vue'
import SupportIcon from './icons/IconSupport.vue'
</script>
<template>
<WelcomeItem>
<template #icon>
<DocumentationIcon />
</template>
<template #heading>Documentation</template>
Vues
<a href="https://vuejs.org/" target="_blank" rel="noopener">official documentation</a>
provides you with all information you need to get started.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<ToolingIcon />
</template>
<template #heading>Tooling</template>
This project is served and bundled with
<a href="https://vitejs.dev/guide/features.html" target="_blank" rel="noopener">Vite</a>. The
recommended IDE setup is
<a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VSCode</a> +
<a href="https://github.com/johnsoncodehk/volar" target="_blank" rel="noopener">Volar</a>. If
you need to test your components and web pages, check out
<a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a> and
<a href="https://on.cypress.io/component" target="_blank" rel="noopener"
>Cypress Component Testing</a
>.
<br />
More instructions are available in <code>README.md</code>.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<EcosystemIcon />
</template>
<template #heading>Ecosystem</template>
Get official tools and libraries for your project:
<a href="https://pinia.vuejs.org/" target="_blank" rel="noopener">Pinia</a>,
<a href="https://router.vuejs.org/" target="_blank" rel="noopener">Vue Router</a>,
<a href="https://test-utils.vuejs.org/" target="_blank" rel="noopener">Vue Test Utils</a>, and
<a href="https://github.com/vuejs/devtools" target="_blank" rel="noopener">Vue Dev Tools</a>. If
you need more resources, we suggest paying
<a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">Awesome Vue</a>
a visit.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<CommunityIcon />
</template>
<template #heading>Community</template>
Got stuck? Ask your question on
<a href="https://chat.vuejs.org" target="_blank" rel="noopener">Vue Land</a>, our official
Discord server, or
<a href="https://stackoverflow.com/questions/tagged/vue.js" target="_blank" rel="noopener"
>StackOverflow</a
>. You should also subscribe to
<a href="https://news.vuejs.org" target="_blank" rel="noopener">our mailing list</a> and follow
the official
<a href="https://twitter.com/vuejs" target="_blank" rel="noopener">@vuejs</a>
twitter account for latest news in the Vue world.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<SupportIcon />
</template>
<template #heading>Support Vue</template>
As an independent project, Vue relies on community backing for its sustainability. You can help
us by
<a href="https://vuejs.org/sponsor/" target="_blank" rel="noopener">becoming a sponsor</a>.
</WelcomeItem>
</template>

View File

@ -0,0 +1,87 @@
<template>
<div class="item">
<i>
<slot name="icon"></slot>
</i>
<div class="details">
<h3>
<slot name="heading"></slot>
</h3>
<slot></slot>
</div>
</div>
</template>
<style scoped>
.item {
margin-top: 2rem;
display: flex;
position: relative;
}
.details {
flex: 1;
margin-left: 1rem;
}
i {
display: flex;
place-items: center;
place-content: center;
width: 32px;
height: 32px;
color: var(--color-text);
}
h3 {
font-size: 1.2rem;
font-weight: 500;
margin-bottom: 0.4rem;
color: var(--color-heading);
}
@media (min-width: 1024px) {
.item {
margin-top: 0;
padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
}
i {
top: calc(50% - 25px);
left: -26px;
position: absolute;
border: 1px solid var(--color-border);
background: var(--color-background);
border-radius: 8px;
width: 50px;
height: 50px;
}
.item:before {
content: ' ';
border-left: 1px solid var(--color-border);
position: absolute;
left: 0;
bottom: calc(50% + 25px);
height: calc(50% - 25px);
}
.item:after {
content: ' ';
border-left: 1px solid var(--color-border);
position: absolute;
left: 0;
top: calc(50% + 25px);
height: calc(50% - 25px);
}
.item:first-of-type:before {
display: none;
}
.item:last-of-type:after {
display: none;
}
}
</style>

View File

@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
/>
</svg>
</template>

Some files were not shown because too many files have changed in this diff Show More