This commit is contained in:
邹方成 2025-09-30 22:26:02 +08:00
parent 79d1bbb43f
commit 25681c16a4
35 changed files with 11681 additions and 984 deletions

71
Pipfile Normal file
View File

@ -0,0 +1,71 @@
[[source]]
url = "https://pypi.doubanio.com/simple"
verify_ssl = true
name = "pip_conf_index_global"
[packages]
aerich = "==0.8.1"
aiosqlite = "==0.20.0"
annotated-types = "==0.7.0"
anyio = "==4.8.0"
argon2-cffi = "==23.1.0"
argon2-cffi-bindings = "==21.2.0"
asyncclick = "==8.1.8"
black = "==24.10.0"
certifi = "==2024.12.14"
cffi = "==1.17.1"
click = "==8.1.8"
dictdiffer = "==0.9.0"
dnspython = "==2.7.0"
email-validator = "==2.2.0"
fastapi = "==0.111.0"
fastapi-cli = "==0.0.7"
h11 = "==0.14.0"
httpcore = "==1.0.7"
httptools = "==0.6.4"
httpx = "==0.28.1"
idna = "==3.10"
iso8601 = "==2.1.0"
isort = "==5.13.2"
jinja2 = "==3.1.5"
loguru = "==0.7.3"
markdown-it-py = "==3.0.0"
markupsafe = "==3.0.2"
mdurl = "==0.1.2"
mypy-extensions = "==1.0.0"
orjson = "==3.10.14"
packaging = "==24.2"
passlib = "==1.7.4"
pathspec = "==0.12.1"
platformdirs = "==4.3.6"
pycparser = "==2.22"
pydantic = "==2.10.5"
pydantic-core = "==2.27.2"
pydantic-settings = "==2.7.1"
pygments = "==2.19.1"
pyjwt = "==2.10.1"
pypika-tortoise = "==0.3.2"
python-dotenv = "==1.0.1"
python-multipart = "==0.0.20"
pytz = "==2024.2"
pyyaml = "==6.0.2"
rich = "==13.9.4"
rich-toolkit = "==0.13.2"
ruff = "==0.9.1"
shellingham = "==1.5.4"
sniffio = "==1.3.1"
starlette = "==0.37.2"
tortoise-orm = "==0.23.0"
typer = "==0.15.1"
typing-extensions = "==4.12.2"
ujson = "==5.10.0"
uvicorn = "==0.34.0"
uvloop = "==0.21.0"
watchfiles = "==1.0.4"
websockets = "==14.1"
[dev-packages]
[requires]
python_version = "3.9"
python_full_version = "3.9.6"

View File

@ -6,7 +6,11 @@ from .apis import apis_router
from .auditlog import auditlog_router
from .base import base_router
from .depts import depts_router
from .esg.esg import router as esg_router
from .index.index import router as index_router
from .industry.industry import router as industry_router
from .menus import menus_router
from .policy.policy import router as policy_router
from .roles import roles_router
from .users import users_router
@ -19,3 +23,7 @@ v1_router.include_router(menus_router, prefix="/menu", dependencies=[DependPermi
v1_router.include_router(apis_router, prefix="/api", dependencies=[DependPermission])
v1_router.include_router(depts_router, prefix="/dept", dependencies=[DependPermission])
v1_router.include_router(auditlog_router, prefix="/auditlog", dependencies=[DependPermission])
v1_router.include_router(esg_router, prefix="/esg", dependencies=[DependPermission])
v1_router.include_router(index_router, prefix="/index", dependencies=[DependPermission])
v1_router.include_router(industry_router, prefix="/industry", dependencies=[DependPermission])
v1_router.include_router(policy_router, prefix="/policy", dependencies=[DependPermission])

View File

71
app/api/v1/esg/esg.py Normal file
View File

@ -0,0 +1,71 @@
from fastapi import APIRouter, Query
from tortoise.expressions import Q
from app.controllers.esg import esg_controller
from app.schemas import Success, SuccessExtra
from app.schemas.esg import ESGCreate, ESGUpdate, ESGResponse
router = APIRouter(tags=["ESG管理"])
@router.get("/list", summary="查看ESG列表")
async def list_esg(
page: int = Query(1, description="页码"),
page_size: int = Query(10, description="每页数量"),
code: str = Query(None, description="ESG代码"),
name: str = Query(None, description="ESG名称"),
level: str = Query(None, description="ESG级别"),
):
q = Q()
if code:
q &= Q(code__contains=code)
if name:
q &= Q(name__contains=name)
if level:
q &= Q(level__contains=level)
total, esg_objs = await esg_controller.list(page=page, page_size=page_size, search=q, order=["id"])
data = [await obj.to_dict() for obj in esg_objs]
return SuccessExtra(data=data, total=total, page=page, page_size=page_size)
@router.get("/get", summary="查看ESG详情")
async def get_esg(
id: int = Query(..., description="ESG ID"),
):
esg_obj = await esg_controller.get(id=id)
data = await esg_obj.to_dict()
return Success(data=data)
@router.post("/create", summary="创建ESG")
async def create_esg(
esg_in: ESGCreate,
):
# 检查代码是否已存在
if await esg_controller.is_exist(esg_in.code):
return Success(code=400, msg="ESG代码已存在")
await esg_controller.create(obj_in=esg_in)
return Success(msg="创建成功")
@router.post("/update", summary="更新ESG")
async def update_esg(
esg_in: ESGUpdate,
):
# 检查代码是否已存在(排除当前记录)
if esg_in.code:
existing_obj = await esg_controller.model.filter(code=esg_in.code).exclude(id=esg_in.id).first()
if existing_obj:
return Success(code=400, msg="ESG代码已存在")
await esg_controller.update(id=esg_in.id, obj_in=esg_in)
return Success(msg="更新成功")
@router.delete("/delete", summary="删除ESG")
async def delete_esg(
esg_id: int = Query(..., description="ESG ID"),
):
await esg_controller.remove(id=esg_id)
return Success(msg="删除成功")

View File

68
app/api/v1/index/index.py Normal file
View File

@ -0,0 +1,68 @@
from fastapi import APIRouter, Query
from tortoise.expressions import Q
from app.controllers.index import index_controller
from app.schemas import Success, SuccessExtra
from app.schemas.index import IndexCreate, IndexUpdate, IndexResponse
router = APIRouter(tags=["指数管理"])
@router.get("/list", summary="查看指数列表")
async def list_index(
page: int = Query(1, description="页码"),
page_size: int = Query(10, description="每页数量"),
code: str = Query(None, description="指数代码"),
name: str = Query(None, description="指数名称"),
):
q = Q()
if code:
q &= Q(code__contains=code)
if name:
q &= Q(name__contains=name)
total, index_objs = await index_controller.list(page=page, page_size=page_size, search=q, order=["id"])
data = [await obj.to_dict() for obj in index_objs]
return SuccessExtra(data=data, total=total, page=page, page_size=page_size)
@router.get("/get", summary="查看指数详情")
async def get_index(
id: int = Query(..., description="指数 ID"),
):
index_obj = await index_controller.get(id=id)
data = await index_obj.to_dict()
return Success(data=data)
@router.post("/create", summary="创建指数")
async def create_index(
index_in: IndexCreate,
):
# 检查代码是否已存在
if await index_controller.is_exist(index_in.code):
return Success(code=400, msg="指数代码已存在")
await index_controller.create(obj_in=index_in)
return Success(msg="创建成功")
@router.post("/update", summary="更新指数")
async def update_index(
index_in: IndexUpdate,
):
# 检查代码是否已存在(排除当前记录)
if index_in.code:
existing_obj = await index_controller.model.filter(code=index_in.code).exclude(id=index_in.id).first()
if existing_obj:
return Success(code=400, msg="指数代码已存在")
await index_controller.update(id=index_in.id, obj_in=index_in)
return Success(msg="更新成功")
@router.delete("/delete", summary="删除指数")
async def delete_index(
index_id: int = Query(..., description="指数 ID"),
):
await index_controller.remove(id=index_id)
return Success(msg="删除成功")

View File

View File

@ -0,0 +1,68 @@
from fastapi import APIRouter, Query
from tortoise.expressions import Q
from app.controllers.industry import industry_controller
from app.schemas import Success, SuccessExtra
from app.schemas.industry import IndustryCreate, IndustryUpdate, IndustryResponse
router = APIRouter(tags=["行业管理"])
@router.get("/list", summary="查看行业列表")
async def list_industry(
page: int = Query(1, description="页码"),
page_size: int = Query(10, description="每页数量"),
code: str = Query(None, description="行业代码"),
name: str = Query(None, description="行业名称"),
):
q = Q()
if code:
q &= Q(code__contains=code)
if name:
q &= Q(name__contains=name)
total, industry_objs = await industry_controller.list(page=page, page_size=page_size, search=q, order=["id"])
data = [await obj.to_dict() for obj in industry_objs]
return SuccessExtra(data=data, total=total, page=page, page_size=page_size)
@router.get("/get", summary="查看行业详情")
async def get_industry(
id: int = Query(..., description="行业 ID"),
):
industry_obj = await industry_controller.get(id=id)
data = await industry_obj.to_dict()
return Success(data=data)
@router.post("/create", summary="创建行业")
async def create_industry(
industry_in: IndustryCreate,
):
# 检查代码是否已存在
if await industry_controller.is_exist(industry_in.code):
return Success(code=400, msg="行业代码已存在")
await industry_controller.create(obj_in=industry_in)
return Success(msg="创建成功")
@router.post("/update", summary="更新行业")
async def update_industry(
industry_in: IndustryUpdate,
):
# 检查代码是否已存在(排除当前记录)
if industry_in.code:
existing_obj = await industry_controller.model.filter(code=industry_in.code).exclude(id=industry_in.id).first()
if existing_obj:
return Success(code=400, msg="行业代码已存在")
await industry_controller.update(id=industry_in.id, obj_in=industry_in)
return Success(msg="更新成功")
@router.delete("/delete", summary="删除行业")
async def delete_industry(
industry_id: int = Query(..., description="行业 ID"),
):
await industry_controller.remove(id=industry_id)
return Success(msg="删除成功")

View File

View File

@ -0,0 +1,71 @@
from fastapi import APIRouter, Query
from tortoise.expressions import Q
from app.controllers.policy import policy_controller
from app.schemas import Success, SuccessExtra
from app.schemas.policy import PolicyCreate, PolicyUpdate, PolicyResponse
router = APIRouter(tags=["政策管理"])
@router.get("/list", summary="查看政策列表")
async def list_policy(
page: int = Query(1, description="页码"),
page_size: int = Query(10, description="每页数量"),
code: str = Query(None, description="政策代码"),
name: str = Query(None, description="政策名称"),
level: str = Query(None, description="政策级别"),
):
q = Q()
if code:
q &= Q(code__contains=code)
if name:
q &= Q(name__contains=name)
if level:
q &= Q(level__contains=level)
total, policy_objs = await policy_controller.list(page=page, page_size=page_size, search=q, order=["id"])
data = [await obj.to_dict() for obj in policy_objs]
return SuccessExtra(data=data, total=total, page=page, page_size=page_size)
@router.get("/get", summary="查看政策详情")
async def get_policy(
id: int = Query(..., description="政策 ID"),
):
policy_obj = await policy_controller.get(id=id)
data = await policy_obj.to_dict()
return Success(data=data)
@router.post("/create", summary="创建政策")
async def create_policy(
policy_in: PolicyCreate,
):
# 检查代码是否已存在
if await policy_controller.is_exist(policy_in.code):
return Success(code=400, msg="政策代码已存在")
await policy_controller.create(obj_in=policy_in)
return Success(msg="创建成功")
@router.post("/update", summary="更新政策")
async def update_policy(
policy_in: PolicyUpdate,
):
# 检查代码是否已存在(排除当前记录)
if policy_in.code:
existing_obj = await policy_controller.model.filter(code=policy_in.code).exclude(id=policy_in.id).first()
if existing_obj:
return Success(code=400, msg="政策代码已存在")
await policy_controller.update(id=policy_in.id, obj_in=policy_in)
return Success(msg="更新成功")
@router.delete("/delete", summary="删除政策")
async def delete_policy(
policy_id: int = Query(..., description="政策 ID"),
):
await policy_controller.remove(id=policy_id)
return Success(msg="删除成功")

View File

@ -33,7 +33,7 @@ class ApiController(CRUDBase[Api, ApiCreate, ApiUpdate]):
method = list(route.methods)[0]
path = route.path_format
summary = route.summary
tags = list(route.tags)[0]
tags = list(route.tags)[0] if route.tags else "default"
api_obj = await Api.filter(method=method, path=path).first()
if api_obj:
await api_obj.update_from_dict(dict(method=method, path=path, summary=summary, tags=tags)).save()

15
app/controllers/esg.py Normal file
View File

@ -0,0 +1,15 @@
from app.core.crud import CRUDBase
from app.models.esg import ESG
from app.schemas.esg import ESGCreate, ESGUpdate
class ESGController(CRUDBase[ESG, ESGCreate, ESGUpdate]):
def __init__(self):
super().__init__(model=ESG)
async def is_exist(self, code: str) -> bool:
"""检查行业代码是否已存在"""
return await self.model.filter(code=code).exists()
esg_controller = ESGController()

15
app/controllers/index.py Normal file
View File

@ -0,0 +1,15 @@
from app.core.crud import CRUDBase
from app.models.index import Index
from app.schemas.index import IndexCreate, IndexUpdate
class IndexController(CRUDBase[Index, IndexCreate, IndexUpdate]):
def __init__(self):
super().__init__(model=Index)
async def is_exist(self, code: str) -> bool:
"""检查行业代码是否已存在"""
return await self.model.filter(code=code).exists()
index_controller = IndexController()

View File

@ -0,0 +1,15 @@
from app.core.crud import CRUDBase
from app.models.industry import Industry
from app.schemas.industry import IndustryCreate, IndustryUpdate
class IndustryController(CRUDBase[Industry, IndustryCreate, IndustryUpdate]):
def __init__(self):
super().__init__(model=Industry)
async def is_exist(self, code: str) -> bool:
"""检查行业代码是否已存在"""
return await self.model.filter(code=code).exists()
industry_controller = IndustryController()

15
app/controllers/policy.py Normal file
View File

@ -0,0 +1,15 @@
from app.core.crud import CRUDBase
from app.models.policy import Policy
from app.schemas.policy import PolicyCreate, PolicyUpdate
class PolicyController(CRUDBase[Policy, PolicyCreate, PolicyUpdate]):
def __init__(self):
super().__init__(model=Policy)
async def is_exist(self, code: str) -> bool:
"""检查行业代码是否已存在"""
return await self.model.filter(code=code).exists()
policy_controller = PolicyController()

View File

@ -162,6 +162,67 @@ async def init_menus():
),
]
await Menu.bulk_create(children_menu)
# 创建系统数据管理菜单
data_menu = await Menu.create(
menu_type=MenuType.CATALOG,
name="系统数据",
path="/data",
order=2,
parent_id=0,
icon="carbon:data-base",
is_hidden=False,
component="Layout",
keepalive=False,
redirect="/data/industry",
)
data_children_menu = [
Menu(
menu_type=MenuType.MENU,
name="行业修正",
path="industry",
order=1,
parent_id=data_menu.id,
icon="carbon:industry",
is_hidden=False,
component="/data/industry",
keepalive=False,
),
Menu(
menu_type=MenuType.MENU,
name="政策匹配",
path="policy",
order=2,
parent_id=data_menu.id,
icon="carbon:policy",
is_hidden=False,
component="/data/policy",
keepalive=False,
),
Menu(
menu_type=MenuType.MENU,
name="ESG关联",
path="esg",
order=3,
parent_id=data_menu.id,
icon="carbon:earth-southeast-asia",
is_hidden=False,
component="/data/esg",
keepalive=False,
),
Menu(
menu_type=MenuType.MENU,
name="行业基准",
path="index",
order=4,
parent_id=data_menu.id,
icon="carbon:chart-line",
is_hidden=False,
component="/data/index",
keepalive=False,
),
]
await Menu.bulk_create(data_children_menu)
await Menu.create(
menu_type=MenuType.MENU,
name="一级菜单",

View File

@ -1,2 +1,6 @@
# 新增model需要在这里导入
from .admin import *
from .esg import *
from .index import *
from .industry import *
from .policy import *

View File

@ -17,3 +17,18 @@ class MethodType(StrEnum):
PUT = "PUT"
DELETE = "DELETE"
PATCH = "PATCH"
class PolicyType(StrEnum):
A = "A"
B = "B"
C = "C"
D = "D"
E = "E"
class ESGType(StrEnum):
A = "A"
B = "B"
C = "C"
D = "D"
E = "E"

15
app/models/esg.py Normal file
View File

@ -0,0 +1,15 @@
from tortoise import fields
from .base import BaseModel, TimestampMixin
from .enums import ESGType
# ESG关联价值对应表
class ESG(BaseModel, TimestampMixin):
code = fields.CharField(max_length=20, unique=True, description="行业代码", index=True)
name = fields.CharField(max_length=20, description="行业名称")
level = fields.CharEnumField(ESGType, description="ESG价值等级", index=True)
number = fields.IntField(default=0, description="ESG基准分")
remark = fields.TextField(default="简要说明ESG视角")
class Meta:
table = "esg"

16
app/models/index.py Normal file
View File

@ -0,0 +1,16 @@
from tortoise import fields
from app.schemas.menus import MenuType
from .base import BaseModel, TimestampMixin
from .enums import MethodType
# 行业基准搜索指数表
class Index(BaseModel, TimestampMixin):
code = fields.CharField(max_length=20, unique=True, description="行业代码", index=True)
name = fields.CharField(max_length=20, description="行业名称")
search_num = fields.FloatField(description="行业基准搜索指数", default=0)
remark = fields.TextField(default="备注")
class Meta:
table = "index"

14
app/models/industry.py Normal file
View File

@ -0,0 +1,14 @@
from tortoise import fields
from .base import BaseModel, TimestampMixin
# 行业修正系数与ROE
class Industry(BaseModel, TimestampMixin):
code = fields.CharField(max_length=20, unique=True, description="行业代码", index=True)
name = fields.CharField(max_length=20, description="行业名称")
roe = fields.FloatField(description="平均ROE",default=0)
fix_num = fields.FloatField(description="行业修正系数", default=0)
remark = fields.TextField(default="备注")
class Meta:
table = "industry"

16
app/models/policy.py Normal file
View File

@ -0,0 +1,16 @@
from tortoise import fields
from app.schemas.menus import MenuType
from .base import BaseModel, TimestampMixin
from .enums import PolicyType
# 政策匹配度对应表
class Policy(BaseModel, TimestampMixin):
code = fields.CharField(max_length=20, unique=True, description="行业代码", index=True)
name = fields.CharField(max_length=20, description="行业名称")
level = fields.CharEnumField(PolicyType, description="政策扶持等级")
score = fields.IntField( description="政策匹配度基准分",default=0)
class Meta:
table = "policy"

28
app/schemas/esg.py Normal file
View File

@ -0,0 +1,28 @@
from datetime import datetime
from typing import Optional
from pydantic import BaseModel, Field
from app.models.enums import ESGType
class BaseESG(BaseModel):
code: str = Field(..., description="行业代码", example="001")
name: str = Field(..., description="行业名称", example="农业")
level: ESGType = Field(..., description="ESG价值等级", example=ESGType.A)
number: int = Field(0, description="ESG基准分", example=85)
remark: str = Field("简要说明ESG视角", description="备注", example="环保友好型行业")
class ESGCreate(BaseESG):
pass
class ESGUpdate(BaseESG):
id: int
class ESGResponse(BaseESG):
id: int
created_at: Optional[datetime]
updated_at: Optional[datetime]

25
app/schemas/index.py Normal file
View File

@ -0,0 +1,25 @@
from datetime import datetime
from typing import Optional
from pydantic import BaseModel, Field
class BaseIndex(BaseModel):
code: str = Field(..., description="行业代码", example="001")
name: str = Field(..., description="行业名称", example="农业")
search_num: float = Field(0, description="行业基准搜索指数", example=1.25)
remark: str = Field("备注", description="备注", example="基准搜索指数说明")
class IndexCreate(BaseIndex):
pass
class IndexUpdate(BaseIndex):
id: int
class IndexResponse(BaseIndex):
id: int
created_at: Optional[datetime]
updated_at: Optional[datetime]

26
app/schemas/industry.py Normal file
View File

@ -0,0 +1,26 @@
from datetime import datetime
from typing import Optional
from pydantic import BaseModel, Field
class BaseIndustry(BaseModel):
code: str = Field(..., description="行业代码", example="001")
name: str = Field(..., description="行业名称", example="农业")
roe: float = Field(0, description="平均ROE", example=0.15)
fix_num: float = Field(0, description="行业修正系数", example=1.2)
remark: str = Field("备注", description="备注", example="行业修正系数说明")
class IndustryCreate(BaseIndustry):
pass
class IndustryUpdate(BaseIndustry):
id: int
class IndustryResponse(BaseIndustry):
id: int
created_at: Optional[datetime]
updated_at: Optional[datetime]

27
app/schemas/policy.py Normal file
View File

@ -0,0 +1,27 @@
from datetime import datetime
from typing import Optional
from pydantic import BaseModel, Field
from app.models.enums import PolicyType
class BasePolicy(BaseModel):
code: str = Field(..., description="行业代码", example="001")
name: str = Field(..., description="行业名称", example="农业")
level: PolicyType = Field(..., description="政策扶持等级", example=PolicyType.A)
score: int = Field(0, description="政策匹配度基准分", example=90)
class PolicyCreate(BasePolicy):
pass
class PolicyUpdate(BasePolicy):
id: int
class PolicyResponse(BasePolicy):
id: int
created_at: Optional[datetime]
updated_at: Optional[datetime]

0
app/utils/api_utils.py Normal file
View File

8415
web/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -28,9 +28,9 @@
"sass": "^1.65.1",
"typescript": "^5.1.6",
"unocss": "^0.55.0",
"unplugin-auto-import": "^0.16.6",
"unplugin-auto-import": "^0.15.3",
"unplugin-icons": "^0.16.5",
"unplugin-vue-components": "^0.25.1",
"unplugin-vue-components": "^0.24.1",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-html": "^3.2.0",
"vite-plugin-svg-icons": "^2.0.1",

2458
web/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -39,4 +39,28 @@ export default {
deleteDept: (params = {}) => request.delete('/dept/delete', { params }),
// auditlog
getAuditLogList: (params = {}) => request.get('/auditlog/list', { params }),
// esg
getESGList: (params = {}) => request.get('/esg/list', { params }),
getESGById: (params = {}) => request.get('/esg/get', { params }),
createESG: (data = {}) => request.post('/esg/create', data),
updateESG: (data = {}) => request.post('/esg/update', data),
deleteESG: (params = {}) => request.delete('/esg/delete', { params }),
// index
getIndexList: (params = {}) => request.get('/index/list', { params }),
getIndexById: (params = {}) => request.get('/index/get', { params }),
createIndex: (data = {}) => request.post('/index/create', data),
updateIndex: (data = {}) => request.post('/index/update', data),
deleteIndex: (params = {}) => request.delete('/index/delete', { params }),
// industry
getIndustryList: (params = {}) => request.get('/industry/list', { params }),
getIndustryById: (params = {}) => request.get('/industry/get', { params }),
createIndustry: (data = {}) => request.post('/industry/create', data),
updateIndustry: (data = {}) => request.post('/industry/update', data),
deleteIndustry: (params = {}) => request.delete('/industry/delete', { params }),
// policy
getPolicyList: (params = {}) => request.get('/policy/list', { params }),
getPolicyById: (params = {}) => request.get('/policy/get', { params }),
createPolicy: (data = {}) => request.post('/policy/create', data),
updatePolicy: (data = {}) => request.post('/policy/update', data),
deletePolicy: (params = {}) => request.delete('/policy/delete', { params }),
}

View File

@ -0,0 +1,282 @@
<script setup>
import { h, onMounted, ref, resolveDirective, withDirectives } from 'vue'
import {
NButton,
NForm,
NFormItem,
NInput,
NInputNumber,
NSpace,
NTag,
NPopconfirm,
} from 'naive-ui'
import CommonPage from '@/components/page/CommonPage.vue'
import QueryBarItem from '@/components/query-bar/QueryBarItem.vue'
import CrudModal from '@/components/table/CrudModal.vue'
import CrudTable from '@/components/table/CrudTable.vue'
import { formatDate, renderIcon } from '@/utils'
import { useCRUD } from '@/composables'
import api from '@/api'
import TheIcon from '@/components/icon/TheIcon.vue'
defineOptions({ name: 'ESG关联' })
const $table = ref(null)
const queryItems = ref({})
const vPermission = resolveDirective('permission')
const {
modalVisible,
modalTitle,
modalAction,
modalLoading,
handleSave,
modalForm,
modalFormRef,
handleEdit,
handleDelete,
handleAdd,
} = useCRUD({
name: 'ESG',
initForm: { code: '', name: '', level: '', number: '', remark: '' },
doCreate: api.createESG,
doUpdate: api.updateESG,
doDelete: api.deleteESG,
refresh: () => $table.value?.handleSearch(),
})
onMounted(() => {
$table.value?.handleSearch()
})
const columns = [
{
title: 'ESG代码',
key: 'code',
width: 120,
align: 'center',
ellipsis: { tooltip: true },
},
{
title: 'ESG名称',
key: 'name',
width: 200,
align: 'center',
ellipsis: { tooltip: true },
},
{
title: 'ESG级别',
key: 'level',
width: 120,
align: 'center',
render(row) {
const levelMap = {
'E': { type: 'success', text: '环境(E)' },
'S': { type: 'info', text: '社会(S)' },
'G': { type: 'warning', text: '治理(G)' },
'ESG': { type: 'error', text: '综合ESG' }
}
const level = levelMap[row.level] || { type: 'default', text: row.level }
return h(
NTag,
{ type: level.type },
{ default: () => level.text }
)
},
},
{
title: 'ESG编号',
key: 'number',
width: 120,
align: 'center',
ellipsis: { tooltip: true },
},
{
title: '备注',
key: 'remark',
width: 200,
align: 'center',
ellipsis: { tooltip: true },
},
{
title: '创建时间',
key: 'created_at',
align: 'center',
width: 180,
render(row) {
return h(
NButton,
{ size: 'small', type: 'text', ghost: true },
{
default: () => formatDate(row.created_at),
icon: renderIcon('mdi:update', { size: 16 }),
}
)
},
},
{
title: '操作',
key: 'actions',
width: 240,
align: 'center',
fixed: 'right',
hideInExcel: true,
render(row) {
return [
withDirectives(
h(
NButton,
{
size: 'small',
type: 'primary',
secondary: true,
onClick: () => handleEdit(row),
},
{ default: () => '编辑', icon: renderIcon('material-symbols:edit-outline', { size: 16 }) }
),
[[vPermission, 'post/api/v1/esg/update']]
),
withDirectives(
h(
NPopconfirm,
{
onPositiveClick: () => handleDelete([row.id], false),
},
{
default: () => '确认删除',
trigger: () =>
h(
NButton,
{ size: 'small', type: 'error', style: 'margin-left: 12px;' },
{ default: () => '删除', icon: renderIcon('material-symbols:delete-outline', { size: 16 }) }
),
}
),
[[vPermission, 'delete/api/v1/esg/delete']]
),
]
},
},
]
const modalRules = {
code: [
{
required: true,
message: '请输入ESG代码',
trigger: ['input', 'blur'],
},
],
name: [
{
required: true,
message: '请输入ESG名称',
trigger: ['input', 'blur'],
},
],
level: [
{
required: true,
message: '请输入ESG级别',
trigger: ['input', 'blur'],
},
],
number: [
{
required: true,
message: '请输入ESG编号',
trigger: ['input', 'blur'],
},
],
}
</script>
<template>
<CommonPage show-footer title="ESG关联">
<template #action>
<div>
<NButton
v-permission="'post/api/v1/esg/create'"
class="float-right mb-8"
type="primary"
@click="handleAdd"
>
<TheIcon icon="material-symbols:add" :size="18" class="mr-5" />新建ESG
</NButton>
</div>
</template>
<CrudTable
ref="$table"
v-model:query-items="queryItems"
:extra-params="{ ordering: 'id' }"
:scroll-x="1200"
:columns="columns"
:get-data="api.getESGList"
>
<template #queryBar>
<QueryBarItem label="ESG代码" :label-width="80">
<NInput
v-model:value="queryItems.code"
type="text"
placeholder="请输入ESG代码"
@keydown.enter="$table?.handleSearch"
/>
</QueryBarItem>
<QueryBarItem label="ESG名称" :label-width="80">
<NInput
v-model:value="queryItems.name"
type="text"
placeholder="请输入ESG名称"
@keydown.enter="$table?.handleSearch"
/>
</QueryBarItem>
<QueryBarItem label="ESG级别" :label-width="80">
<NInput
v-model:value="queryItems.level"
type="text"
placeholder="请输入ESG级别"
@keydown.enter="$table?.handleSearch"
/>
</QueryBarItem>
</template>
</CrudTable>
<!-- 新增/编辑弹窗 -->
<CrudModal
v-model:visible="modalVisible"
:title="modalTitle"
:loading="modalLoading"
:show-footer="true"
@save="handleSave"
>
<NForm
ref="modalFormRef"
label-placement="left"
label-align="left"
:label-width="80"
:model="modalForm"
:rules="modalRules"
>
<NFormItem label="ESG代码" path="code">
<NInput v-model:value="modalForm.code" placeholder="请输入ESG代码" />
</NFormItem>
<NFormItem label="ESG名称" path="name">
<NInput v-model:value="modalForm.name" placeholder="请输入ESG名称" />
</NFormItem>
<NFormItem label="ESG级别" path="level">
<NInput v-model:value="modalForm.level" placeholder="请输入ESG级别E, S, G, ESG" />
</NFormItem>
<NFormItem label="ESG编号" path="number">
<NInput v-model:value="modalForm.number" placeholder="请输入ESG编号" />
</NFormItem>
<NFormItem label="备注" path="remark">
<NInput v-model:value="modalForm.remark" type="textarea" placeholder="请输入备注" />
</NFormItem>
</NForm>
</CrudModal>
</CommonPage>
</template>

View File

@ -0,0 +1,257 @@
<script setup>
import { h, onMounted, ref, resolveDirective, withDirectives } from 'vue'
import {
NButton,
NForm,
NFormItem,
NInput,
NInputNumber,
NSpace,
NTag,
NPopconfirm,
} from 'naive-ui'
import CommonPage from '@/components/page/CommonPage.vue'
import QueryBarItem from '@/components/query-bar/QueryBarItem.vue'
import CrudModal from '@/components/table/CrudModal.vue'
import CrudTable from '@/components/table/CrudTable.vue'
import { formatDate, renderIcon } from '@/utils'
import { useCRUD } from '@/composables'
import api from '@/api'
import TheIcon from '@/components/icon/TheIcon.vue'
defineOptions({ name: '行业基准' })
const $table = ref(null)
const queryItems = ref({})
const vPermission = resolveDirective('permission')
const {
modalVisible,
modalTitle,
modalAction,
modalLoading,
handleSave,
modalForm,
modalFormRef,
handleEdit,
handleDelete,
handleAdd,
} = useCRUD({
name: '指数',
initForm: { code: '', name: '', search_num: 0, remark: '' },
doCreate: api.createIndex,
doUpdate: api.updateIndex,
doDelete: api.deleteIndex,
refresh: () => $table.value?.handleSearch(),
})
onMounted(() => {
$table.value?.handleSearch()
})
const columns = [
{
title: '指数代码',
key: 'code',
width: 120,
align: 'center',
ellipsis: { tooltip: true },
},
{
title: '指数名称',
key: 'name',
width: 200,
align: 'center',
ellipsis: { tooltip: true },
},
{
title: '搜索次数',
key: 'search_num',
width: 120,
align: 'center',
render(row) {
const getSearchType = (num) => {
if (num >= 1000) return 'error'
if (num >= 500) return 'warning'
if (num >= 100) return 'info'
return 'default'
}
return h(
NTag,
{ type: getSearchType(row.search_num) },
{ default: () => `${row.search_num}` }
)
},
},
{
title: '备注',
key: 'remark',
width: 200,
align: 'center',
ellipsis: { tooltip: true },
},
{
title: '创建时间',
key: 'created_at',
align: 'center',
width: 180,
render(row) {
return h(
NButton,
{ size: 'small', type: 'text', ghost: true },
{
default: () => formatDate(row.created_at),
icon: renderIcon('mdi:update', { size: 16 }),
}
)
},
},
{
title: '操作',
key: 'actions',
width: 240,
align: 'center',
fixed: 'right',
hideInExcel: true,
render(row) {
return [
withDirectives(
h(
NButton,
{
size: 'small',
type: 'primary',
secondary: true,
onClick: () => handleEdit(row),
},
{ default: () => '编辑', icon: renderIcon('material-symbols:edit-outline', { size: 16 }) }
),
[[vPermission, 'post/api/v1/index/update']]
),
withDirectives(
h(
NPopconfirm,
{
onPositiveClick: () => handleDelete([row.id], false),
},
{
default: () => '确认删除',
trigger: () =>
h(
NButton,
{ size: 'small', type: 'error', style: 'margin-left: 12px;' },
{ default: () => '删除', icon: renderIcon('material-symbols:delete-outline', { size: 16 }) }
),
}
),
[[vPermission, 'delete/api/v1/index/delete']]
),
]
},
},
]
const modalRules = {
code: [
{
required: true,
message: '请输入指数代码',
trigger: ['input', 'blur'],
},
],
name: [
{
required: true,
message: '请输入指数名称',
trigger: ['input', 'blur'],
},
],
search_num: [
{
required: true,
type: 'number',
message: '请输入搜索次数',
trigger: ['input', 'blur'],
},
],
}
</script>
<template>
<CommonPage show-footer title="行业基准">
<template #action>
<div>
<NButton
v-permission="'post/api/v1/index/create'"
class="float-right mb-8"
type="primary"
@click="handleAdd"
>
<TheIcon icon="material-symbols:add" :size="18" class="mr-5" />新建指数
</NButton>
</div>
</template>
<CrudTable
ref="$table"
v-model:query-items="queryItems"
:extra-params="{ ordering: 'id' }"
:scroll-x="1200"
:columns="columns"
:get-data="api.getIndexList"
>
<template #queryBar>
<QueryBarItem label="指数代码" :label-width="80">
<NInput
v-model:value="queryItems.code"
type="text"
placeholder="请输入指数代码"
@keydown.enter="$table?.handleSearch"
/>
</QueryBarItem>
<QueryBarItem label="指数名称" :label-width="80">
<NInput
v-model:value="queryItems.name"
type="text"
placeholder="请输入指数名称"
@keydown.enter="$table?.handleSearch"
/>
</QueryBarItem>
</template>
</CrudTable>
<!-- 新增/编辑弹窗 -->
<CrudModal
v-model:visible="modalVisible"
:title="modalTitle"
:loading="modalLoading"
:show-footer="true"
@save="handleSave"
>
<NForm
ref="modalFormRef"
label-placement="left"
label-align="left"
:label-width="80"
:model="modalForm"
:rules="modalRules"
>
<NFormItem label="指数代码" path="code">
<NInput v-model:value="modalForm.code" placeholder="请输入指数代码" />
</NFormItem>
<NFormItem label="指数名称" path="name">
<NInput v-model:value="modalForm.name" placeholder="请输入指数名称" />
</NFormItem>
<NFormItem label="搜索次数" path="search_num">
<NInputNumber v-model:value="modalForm.search_num" placeholder="请输入搜索次数" :min="0" :precision="0" />
</NFormItem>
<NFormItem label="备注" path="remark">
<NInput v-model:value="modalForm.remark" type="textarea" placeholder="请输入备注" />
</NFormItem>
</NForm>
</CrudModal>
</CommonPage>
</template>

View File

@ -0,0 +1,275 @@
<script setup>
import { h, onMounted, ref, resolveDirective, withDirectives } from 'vue'
import {
NButton,
NForm,
NFormItem,
NInput,
NInputNumber,
NSpace,
NTag,
NPopconfirm,
} from 'naive-ui'
import CommonPage from '@/components/page/CommonPage.vue'
import QueryBarItem from '@/components/query-bar/QueryBarItem.vue'
import CrudModal from '@/components/table/CrudModal.vue'
import CrudTable from '@/components/table/CrudTable.vue'
import { formatDate, renderIcon } from '@/utils'
import { useCRUD } from '@/composables'
import api from '@/api'
import TheIcon from '@/components/icon/TheIcon.vue'
defineOptions({ name: '行业修正' })
const $table = ref(null)
const queryItems = ref({})
const vPermission = resolveDirective('permission')
const {
modalVisible,
modalTitle,
modalAction,
modalLoading,
handleSave,
modalForm,
modalFormRef,
handleEdit,
handleDelete,
handleAdd,
} = useCRUD({
name: '行业',
initForm: { code: '', name: '', roe: 0, fix_num: 0, remark: '' },
doCreate: api.createIndustry,
doUpdate: api.updateIndustry,
doDelete: api.deleteIndustry,
refresh: () => $table.value?.handleSearch(),
})
onMounted(() => {
$table.value?.handleSearch()
})
const columns = [
{
title: '行业代码',
key: 'code',
width: 120,
align: 'center',
ellipsis: { tooltip: true },
},
{
title: '行业名称',
key: 'name',
width: 200,
align: 'center',
ellipsis: { tooltip: true },
},
{
title: 'ROE',
key: 'roe',
width: 100,
align: 'center',
render(row) {
return h(
NTag,
{ type: 'info' },
{ default: () => `${row.roe}%` }
)
},
},
{
title: '修正数值',
key: 'fix_num',
width: 120,
align: 'center',
render(row) {
return h(
NTag,
{ type: row.fix_num > 0 ? 'success' : row.fix_num < 0 ? 'error' : 'default' },
{ default: () => row.fix_num }
)
},
},
{
title: '备注',
key: 'remark',
width: 200,
align: 'center',
ellipsis: { tooltip: true },
},
{
title: '创建时间',
key: 'created_at',
align: 'center',
width: 180,
render(row) {
return h(
NButton,
{ size: 'small', type: 'text', ghost: true },
{
default: () => formatDate(row.created_at),
icon: renderIcon('mdi:update', { size: 16 }),
}
)
},
},
{
title: '操作',
key: 'actions',
width: 240,
align: 'center',
fixed: 'right',
hideInExcel: true,
render(row) {
return [
withDirectives(
h(
NButton,
{
size: 'small',
type: 'primary',
secondary: true,
onClick: () => handleEdit(row),
},
{ default: () => '编辑', icon: renderIcon('material-symbols:edit-outline', { size: 16 }) }
),
[[vPermission, 'post/api/v1/industry/update']]
),
withDirectives(
h(
NPopconfirm,
{
onPositiveClick: () => handleDelete([row.id], false),
},
{
default: () => '确认删除',
trigger: () =>
h(
NButton,
{ size: 'small', type: 'error', style: 'margin-left: 12px;' },
{ default: () => '删除', icon: renderIcon('material-symbols:delete-outline', { size: 16 }) }
),
}
),
[[vPermission, 'delete/api/v1/industry/delete']]
),
]
},
},
]
const modalRules = {
code: [
{
required: true,
message: '请输入行业代码',
trigger: ['input', 'blur'],
},
],
name: [
{
required: true,
message: '请输入行业名称',
trigger: ['input', 'blur'],
},
],
roe: [
{
required: true,
type: 'number',
message: '请输入ROE值',
trigger: ['input', 'blur'],
},
],
fix_num: [
{
required: true,
type: 'number',
message: '请输入修正数值',
trigger: ['input', 'blur'],
},
],
}
</script>
<template>
<CommonPage show-footer title="行业修正">
<template #action>
<div>
<NButton
v-permission="'post/api/v1/industry/create'"
class="float-right mb-8"
type="primary"
@click="handleAdd"
>
<TheIcon icon="material-symbols:add" :size="18" class="mr-5" />新建行业
</NButton>
</div>
</template>
<CrudTable
ref="$table"
v-model:query-items="queryItems"
:extra-params="{ ordering: 'id' }"
:scroll-x="1200"
:columns="columns"
:get-data="api.getIndustryList"
>
<template #queryBar>
<QueryBarItem label="行业代码" :label-width="80">
<NInput
v-model:value="queryItems.code"
type="text"
placeholder="请输入行业代码"
@keydown.enter="$table?.handleSearch"
/>
</QueryBarItem>
<QueryBarItem label="行业名称" :label-width="80">
<NInput
v-model:value="queryItems.name"
type="text"
placeholder="请输入行业名称"
@keydown.enter="$table?.handleSearch"
/>
</QueryBarItem>
</template>
</CrudTable>
<!-- 新增/编辑弹窗 -->
<CrudModal
v-model:visible="modalVisible"
:title="modalTitle"
:loading="modalLoading"
:show-footer="true"
@save="handleSave"
>
<NForm
ref="modalFormRef"
label-placement="left"
label-align="left"
:label-width="80"
:model="modalForm"
:rules="modalRules"
>
<NFormItem label="行业代码" path="code">
<NInput v-model:value="modalForm.code" placeholder="请输入行业代码" />
</NFormItem>
<NFormItem label="行业名称" path="name">
<NInput v-model:value="modalForm.name" placeholder="请输入行业名称" />
</NFormItem>
<NFormItem label="ROE" path="roe">
<NInputNumber v-model:value="modalForm.roe" placeholder="请输入ROE值" :precision="2" :step="0.01" />
</NFormItem>
<NFormItem label="修正数值" path="fix_num">
<NInputNumber v-model:value="modalForm.fix_num" placeholder="请输入修正数值" :precision="2" :step="0.01" />
</NFormItem>
<NFormItem label="备注" path="remark">
<NInput v-model:value="modalForm.remark" type="textarea" placeholder="请输入备注" />
</NFormItem>
</NForm>
</CrudModal>
</CommonPage>
</template>

View File

@ -0,0 +1,284 @@
<script setup>
import { h, onMounted, ref, resolveDirective, withDirectives } from 'vue'
import {
NButton,
NForm,
NFormItem,
NInput,
NInputNumber,
NSpace,
NTag,
NPopconfirm,
} from 'naive-ui'
import CommonPage from '@/components/page/CommonPage.vue'
import QueryBarItem from '@/components/query-bar/QueryBarItem.vue'
import CrudModal from '@/components/table/CrudModal.vue'
import CrudTable from '@/components/table/CrudTable.vue'
import { formatDate, renderIcon } from '@/utils'
import { useCRUD } from '@/composables'
import api from '@/api'
import TheIcon from '@/components/icon/TheIcon.vue'
defineOptions({ name: '政策匹配' })
const $table = ref(null)
const queryItems = ref({})
const vPermission = resolveDirective('permission')
const {
modalVisible,
modalTitle,
modalAction,
modalLoading,
handleSave,
modalForm,
modalFormRef,
handleEdit,
handleDelete,
handleAdd,
} = useCRUD({
name: '政策',
initForm: { code: '', name: '', level: '', score: 0 },
doCreate: api.createPolicy,
doUpdate: api.updatePolicy,
doDelete: api.deletePolicy,
refresh: () => $table.value?.handleSearch(),
})
onMounted(() => {
$table.value?.handleSearch()
})
const columns = [
{
title: '政策代码',
key: 'code',
width: 120,
align: 'center',
ellipsis: { tooltip: true },
},
{
title: '政策名称',
key: 'name',
width: 200,
align: 'center',
ellipsis: { tooltip: true },
},
{
title: '政策级别',
key: 'level',
width: 120,
align: 'center',
render(row) {
const levelMap = {
'national': { type: 'error', text: '国家级' },
'provincial': { type: 'warning', text: '省级' },
'municipal': { type: 'info', text: '市级' },
'county': { type: 'default', text: '县级' }
}
const level = levelMap[row.level] || { type: 'default', text: row.level }
return h(
NTag,
{ type: level.type },
{ default: () => level.text }
)
},
},
{
title: '政策评分',
key: 'score',
width: 120,
align: 'center',
render(row) {
const getScoreType = (score) => {
if (score >= 80) return 'success'
if (score >= 60) return 'warning'
return 'error'
}
return h(
NTag,
{ type: getScoreType(row.score) },
{ default: () => `${row.score}` }
)
},
},
{
title: '创建时间',
key: 'created_at',
align: 'center',
width: 180,
render(row) {
return h(
NButton,
{ size: 'small', type: 'text', ghost: true },
{
default: () => formatDate(row.created_at),
icon: renderIcon('mdi:update', { size: 16 }),
}
)
},
},
{
title: '操作',
key: 'actions',
width: 240,
align: 'center',
fixed: 'right',
hideInExcel: true,
render(row) {
return [
withDirectives(
h(
NButton,
{
size: 'small',
type: 'primary',
secondary: true,
onClick: () => handleEdit(row),
},
{ default: () => '编辑', icon: renderIcon('material-symbols:edit-outline', { size: 16 }) }
),
[[vPermission, 'post/api/v1/policy/update']]
),
withDirectives(
h(
NPopconfirm,
{
onPositiveClick: () => handleDelete([row.id], false),
},
{
default: () => '确认删除',
trigger: () =>
h(
NButton,
{ size: 'small', type: 'error', style: 'margin-left: 12px;' },
{ default: () => '删除', icon: renderIcon('material-symbols:delete-outline', { size: 16 }) }
),
}
),
[[vPermission, 'delete/api/v1/policy/delete']]
),
]
},
},
]
const modalRules = {
code: [
{
required: true,
message: '请输入政策代码',
trigger: ['input', 'blur'],
},
],
name: [
{
required: true,
message: '请输入政策名称',
trigger: ['input', 'blur'],
},
],
level: [
{
required: true,
message: '请输入政策级别',
trigger: ['input', 'blur'],
},
],
score: [
{
required: true,
type: 'number',
message: '请输入政策评分',
trigger: ['input', 'blur'],
},
],
}
</script>
<template>
<CommonPage show-footer title="政策匹配">
<template #action>
<div>
<NButton
v-permission="'post/api/v1/policy/create'"
class="float-right mb-8"
type="primary"
@click="handleAdd"
>
<TheIcon icon="material-symbols:add" :size="18" class="mr-5" />新建政策
</NButton>
</div>
</template>
<CrudTable
ref="$table"
v-model:query-items="queryItems"
:extra-params="{ ordering: 'id' }"
:scroll-x="1200"
:columns="columns"
:get-data="api.getPolicyList"
>
<template #queryBar>
<QueryBarItem label="政策代码" :label-width="80">
<NInput
v-model:value="queryItems.code"
type="text"
placeholder="请输入政策代码"
@keydown.enter="$table?.handleSearch"
/>
</QueryBarItem>
<QueryBarItem label="政策名称" :label-width="80">
<NInput
v-model:value="queryItems.name"
type="text"
placeholder="请输入政策名称"
@keydown.enter="$table?.handleSearch"
/>
</QueryBarItem>
<QueryBarItem label="政策级别" :label-width="80">
<NInput
v-model:value="queryItems.level"
type="text"
placeholder="请输入政策级别"
@keydown.enter="$table?.handleSearch"
/>
</QueryBarItem>
</template>
</CrudTable>
<!-- 新增/编辑弹窗 -->
<CrudModal
v-model:visible="modalVisible"
:title="modalTitle"
:loading="modalLoading"
:show-footer="true"
@save="handleSave"
>
<NForm
ref="modalFormRef"
label-placement="left"
label-align="left"
:label-width="80"
:model="modalForm"
:rules="modalRules"
>
<NFormItem label="政策代码" path="code">
<NInput v-model:value="modalForm.code" placeholder="请输入政策代码" />
</NFormItem>
<NFormItem label="政策名称" path="name">
<NInput v-model:value="modalForm.name" placeholder="请输入政策名称" />
</NFormItem>
<NFormItem label="政策级别" path="level">
<NInput v-model:value="modalForm.level" placeholder="请输入政策级别national, provincial, municipal, county" />
</NFormItem>
<NFormItem label="政策评分" path="score">
<NInputNumber v-model:value="modalForm.score" placeholder="请输入政策评分" :min="0" :max="100" :precision="0" />
</NFormItem>
</NForm>
</CrudModal>
</CommonPage>
</template>