first commit
This commit is contained in:
commit
35ca477a2d
31
README.md
Executable file
31
README.md
Executable 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
17
backend/1.py
Executable 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
16
backend/2.py
Executable 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
22
backend/README.md
Executable 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
1
backend/__init__.py
Executable file
@ -0,0 +1 @@
|
||||
from . import models
|
BIN
backend/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
backend/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/__pycache__/database.cpython-312.pyc
Executable file
BIN
backend/__pycache__/database.cpython-312.pyc
Executable file
Binary file not shown.
BIN
backend/__pycache__/main.cpython-312.pyc
Normal file
BIN
backend/__pycache__/main.cpython-312.pyc
Normal file
Binary file not shown.
13
backend/database/1脚本.py
Executable file
13
backend/database/1脚本.py
Executable 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
19
backend/database/2迁移.py
Executable 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
23
backend/database/3.py
Executable 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
|
BIN
backend/database/__pycache__/database.cpython-312.pyc
Executable file
BIN
backend/database/__pycache__/database.cpython-312.pyc
Executable file
Binary file not shown.
116
backend/database/alembic.ini
Executable file
116
backend/database/alembic.ini
Executable 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
|
1
backend/database/alembic/README
Executable file
1
backend/database/alembic/README
Executable file
@ -0,0 +1 @@
|
||||
Generic single-database configuration.
|
BIN
backend/database/alembic/__pycache__/env.cpython-312.pyc
Executable file
BIN
backend/database/alembic/__pycache__/env.cpython-312.pyc
Executable file
Binary file not shown.
89
backend/database/alembic/env.py
Executable file
89
backend/database/alembic/env.py
Executable 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()
|
26
backend/database/alembic/script.py.mako
Executable file
26
backend/database/alembic/script.py.mako
Executable 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"}
|
@ -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
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
21
backend/database/database.py
Executable file
21
backend/database/database.py
Executable 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
35
backend/main.py
Executable 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
5
backend/models/__init__.py
Executable 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
|
BIN
backend/models/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
backend/models/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/models/__pycache__/admin.cpython-312.pyc
Executable file
BIN
backend/models/__pycache__/admin.cpython-312.pyc
Executable file
Binary file not shown.
BIN
backend/models/__pycache__/products.cpython-312.pyc
Executable file
BIN
backend/models/__pycache__/products.cpython-312.pyc
Executable file
Binary file not shown.
23
backend/models/admin.py
Executable file
23
backend/models/admin.py
Executable 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
20
backend/models/products.py
Executable 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
18
backend/requirements.txt
Executable 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
0
backend/routers/__init__.py
Executable file
BIN
backend/routers/__pycache__/__init__.cpython-312.pyc
Executable file
BIN
backend/routers/__pycache__/__init__.cpython-312.pyc
Executable file
Binary file not shown.
BIN
backend/routers/__pycache__/admin.cpython-312.pyc
Normal file
BIN
backend/routers/__pycache__/admin.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/routers/__pycache__/login.cpython-312.pyc
Executable file
BIN
backend/routers/__pycache__/login.cpython-312.pyc
Executable file
Binary file not shown.
BIN
backend/routers/__pycache__/products.cpython-312.pyc
Executable file
BIN
backend/routers/__pycache__/products.cpython-312.pyc
Executable file
Binary file not shown.
133
backend/routers/admin.py
Executable file
133
backend/routers/admin.py
Executable 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
48
backend/routers/products.py
Executable 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
0
backend/schemas/__init__.py
Executable file
BIN
backend/schemas/__pycache__/__init__.cpython-312.pyc
Executable file
BIN
backend/schemas/__pycache__/__init__.cpython-312.pyc
Executable file
Binary file not shown.
BIN
backend/schemas/__pycache__/admin.cpython-312.pyc
Executable file
BIN
backend/schemas/__pycache__/admin.cpython-312.pyc
Executable file
Binary file not shown.
BIN
backend/schemas/__pycache__/products.cpython-312.pyc
Executable file
BIN
backend/schemas/__pycache__/products.cpython-312.pyc
Executable file
Binary file not shown.
21
backend/schemas/admin.py
Executable file
21
backend/schemas/admin.py
Executable 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
22
backend/schemas/products.py
Executable 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
25
backend/vue3README.md
Executable 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 # 项目文档
|
||||
└── ...
|
30
vue/.gitignore
vendored
Executable file
30
vue/.gitignore
vendored
Executable 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
5
vue/.npmignore
Executable file
@ -0,0 +1,5 @@
|
||||
node_modules/
|
||||
test/
|
||||
README.md
|
||||
CHANGELOG.md
|
||||
LICENSE
|
3
vue/.vscode/extensions.json
vendored
Executable file
3
vue/.vscode/extensions.json
vendored
Executable file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["Vue.volar"]
|
||||
}
|
37
vue/README.md
Executable file
37
vue/README.md
Executable 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
1
vue/env.d.ts
vendored
Executable file
@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
23
vue/index.html
Executable file
23
vue/index.html
Executable 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
1901
vue/package-lock.json
generated
Executable file
File diff suppressed because it is too large
Load Diff
30
vue/package.json
Executable file
30
vue/package.json
Executable 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
BIN
vue/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.1 KiB |
0
vue/src/.env
Normal file
0
vue/src/.env
Normal file
49
vue/src/App.vue
Executable file
49
vue/src/App.vue
Executable 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
BIN
vue/src/assets/6shu.mp4
Normal file
Binary file not shown.
86
vue/src/assets/base.css
Executable file
86
vue/src/assets/base.css
Executable 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
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
1
vue/src/assets/logo.svg
Executable 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
35
vue/src/assets/main.css
Executable 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
409
vue/src/chishi/login灯.vue
Normal 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>
|
||||
|
0
vue/src/chishi/文字.vue
Normal file
0
vue/src/chishi/文字.vue
Normal file
0
vue/src/chishi/计数器.vue
Normal file
0
vue/src/chishi/计数器.vue
Normal file
433
vue/src/components/CeShi.vue
Normal file
433
vue/src/components/CeShi.vue
Normal 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>
|
51
vue/src/components/Form.vue
Normal file
51
vue/src/components/Form.vue
Normal 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>
|
41
vue/src/components/HelloWorld.vue
Executable file
41
vue/src/components/HelloWorld.vue
Executable file
@ -0,0 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
msg: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="greetings">
|
||||
<h1 class="green">{{ msg }}</h1>
|
||||
<h3>
|
||||
You’ve 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>
|
49
vue/src/components/Home.vue
Normal file
49
vue/src/components/Home.vue
Normal 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>
|
10
vue/src/components/Kanban.vue
Normal file
10
vue/src/components/Kanban.vue
Normal file
@ -0,0 +1,10 @@
|
||||
<template>
|
||||
|
||||
<div>
|
||||
<input/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
</script>
|
42
vue/src/components/List.vue
Normal file
42
vue/src/components/List.vue
Normal 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>
|
88
vue/src/components/TheWelcome.vue
Executable file
88
vue/src/components/TheWelcome.vue
Executable 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>
|
||||
|
||||
Vue’s
|
||||
<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>
|
87
vue/src/components/WelcomeItem.vue
Executable file
87
vue/src/components/WelcomeItem.vue
Executable 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>
|
7
vue/src/components/icons/IconCommunity.vue
Executable file
7
vue/src/components/icons/IconCommunity.vue
Executable 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
Loading…
Reference in New Issue
Block a user