Compare commits
2 Commits
ac705ecfcb
...
2b4b9a2e9c
| Author | SHA1 | Date | |
|---|---|---|---|
| 2b4b9a2e9c | |||
| b023fafebd |
@ -327,7 +327,7 @@ async def _extract_calculation_params_b1(data: UserValuationCreate) -> Dict[str,
|
||||
|
||||
# 流量因子B12相关参数
|
||||
# 近30天搜索指数S1 - 从社交媒体数据计算 TODO 需要使用第三方API
|
||||
baidu_index = 0
|
||||
baidu_index = 1
|
||||
|
||||
# 获取微信指数并计算近30天平均值
|
||||
try:
|
||||
@ -336,9 +336,9 @@ async def _extract_calculation_params_b1(data: UserValuationCreate) -> Dict[str,
|
||||
logger.info(f"资产 '{data.asset_name}' 的微信指数近30天平均值: {wechat_index}")
|
||||
except Exception as e:
|
||||
logger.error(f"获取微信指数失败: {e}")
|
||||
wechat_index = 0
|
||||
wechat_index = 1
|
||||
|
||||
weibo_index = 0
|
||||
weibo_index = 1
|
||||
search_index_s1 = calculate_search_index_s1(baidu_index, wechat_index, weibo_index) # 默认值,实际应从API获取
|
||||
|
||||
# 行业均值S2 - 从数据库查询行业数据计算
|
||||
@ -433,6 +433,7 @@ async def _extract_calculation_params_b2(data: UserValuationCreate) -> Dict[str,
|
||||
# 纹样基因值B22相关参数
|
||||
|
||||
# 以下三项需由后续模型/服务计算;此处提供默认可计算占位
|
||||
#
|
||||
# 历史传承度HI(用户填写)
|
||||
historical_inheritance = sum([safe_float(i) for i in data.historical_evidence])
|
||||
structure_complexity = 1.5 # 默认值 纹样基因熵值B22(系统计算)
|
||||
|
||||
@ -113,12 +113,9 @@ class MarketValueCCalculator:
|
||||
return:
|
||||
Dict: 包含所有中间计算结果和最终结果的字典
|
||||
"""
|
||||
# 获取动态默认值
|
||||
default_price = await self._get_dynamic_default_price(input_data)
|
||||
|
||||
# 计算市场竞价C1
|
||||
market_bidding_c1 = self.market_bidding_calculator.calculate_market_bidding_c1(
|
||||
transaction_data={'weighted_average_price': input_data.get('weighted_average_price', default_price)},
|
||||
transaction_data={'weighted_average_price': input_data.get('weighted_average_price', 0)},
|
||||
manual_bids=input_data.get('manual_bids', []),
|
||||
expert_valuations=input_data.get('expert_valuations', [])
|
||||
)
|
||||
|
||||
@ -23,7 +23,7 @@ class HeatCoefficientC2Calculator:
|
||||
"""
|
||||
# 计算浏览热度分
|
||||
browse_heat_score = self.calculate_browse_heat_score(daily_browse_volume, collection_count)
|
||||
|
||||
print("浏览热度分: ")
|
||||
|
||||
heat_coefficient = 1 + browse_heat_score
|
||||
|
||||
|
||||
@ -70,7 +70,7 @@ if __name__ == "__main__":
|
||||
manual_bids = [950.0, 1000.0, 1050.0, 1100.0] # 用户填写
|
||||
|
||||
# 优先级3:专家估值
|
||||
expert_valuations = [980.0, 1020.0, 990.0] # 系统配置
|
||||
expert_valuations = [0.0, 0.0, 0.0] # 系统配置
|
||||
|
||||
# 计算市场竞价C1
|
||||
market_bidding_c1 = calculator.calculate_market_bidding_c1(
|
||||
|
||||
@ -4,6 +4,8 @@
|
||||
import logging
|
||||
from typing import Optional, Dict, Any
|
||||
from app.models.industry import Industry
|
||||
from app.models.index import Index
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -44,6 +46,7 @@ async def get_industry_data_by_name(industry_name: str) -> Optional[Dict[str, An
|
||||
|
||||
|
||||
async def calculate_industry_average_s2(industry_name: str) -> float:
|
||||
# todo : 使用index 搜索的数据
|
||||
"""
|
||||
计算行业均值S2
|
||||
|
||||
@ -53,21 +56,13 @@ async def calculate_industry_average_s2(industry_name: str) -> float:
|
||||
Returns:
|
||||
行业均值S2,如果查询失败则返回0.0
|
||||
"""
|
||||
|
||||
try:
|
||||
industry_data = await get_industry_data_by_name(industry_name)
|
||||
if industry_data:
|
||||
index_data = await Index.filter(name=industry_name).first()
|
||||
if index_data:
|
||||
# S2 = ROE * 修正系数
|
||||
roe = industry_data.get('roe', 0.0)
|
||||
fix_num = industry_data.get('fix_num', 0.0)
|
||||
s2_value = roe * fix_num
|
||||
|
||||
# 确保S2值为正数,避免对数计算错误
|
||||
if s2_value <= 0:
|
||||
logger.warning(f"行业 {industry_name} S2计算值为负数或零: ROE={roe}, 修正系数={fix_num}, S2={s2_value},使用默认值0.01")
|
||||
s2_value = 0.01 # 使用小的正数避免对数计算错误
|
||||
|
||||
logger.info(f"行业 {industry_name} S2计算: ROE={roe}, 修正系数={fix_num}, S2={s2_value}")
|
||||
return s2_value
|
||||
logger.info(f"行业 {industry_name} S2计算: S2={index_data.search_num}")
|
||||
return index_data.search_num
|
||||
else:
|
||||
logger.warning(f"未找到行业 {industry_name} 的数据,返回默认值0.01")
|
||||
return 0.01 # 返回小的正数而不是0.0
|
||||
|
||||
3
node_modules/.vite/deps_temp_e96670e1/package.json
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"type": "module"
|
||||
}
|
||||
2
web/.env
@ -1,3 +1,3 @@
|
||||
VITE_TITLE = '成都文化产权交易所'
|
||||
VITE_TITLE = 'Vue FastAPI Admin'
|
||||
|
||||
VITE_PORT = 3100
|
||||
@ -5,4 +5,4 @@ VITE_PUBLIC_PATH = '/'
|
||||
VITE_USE_PROXY = true
|
||||
|
||||
# base api
|
||||
VITE_BASE_API = 'https://value.cdcee.net/api/v1'
|
||||
VITE_BASE_API = '/api/v1'
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
VITE_PUBLIC_PATH = '/'
|
||||
|
||||
# base api
|
||||
VITE_BASE_API = 'https://value.cdcee.net/api/v1'
|
||||
VITE_BASE_API = '/api/v1'
|
||||
|
||||
# 是否启用压缩
|
||||
VITE_USE_COMPRESS = true
|
||||
|
||||
@ -17,7 +17,7 @@ export const PROXY_CONFIG = {
|
||||
* @转发路径 http://localhost:9999/api/v1/user
|
||||
*/
|
||||
'/api/v1': {
|
||||
target: 'http://124.222.245.240:8080',
|
||||
target: 'http://127.0.0.1:9999',
|
||||
changeOrigin: true,
|
||||
},
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@ import vue from '@vitejs/plugin-vue'
|
||||
import Unocss from 'unocss/vite'
|
||||
|
||||
// rollup打包分析插件
|
||||
import { visualizer } from 'rollup-plugin-visualizer'
|
||||
import visualizer from 'rollup-plugin-visualizer'
|
||||
// 压缩
|
||||
import viteCompression from 'vite-plugin-compression'
|
||||
|
||||
|
||||
@ -26,7 +26,7 @@
|
||||
<div class="right-0 bottom-0 loading-spin-item loading-delay-1500"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="loading-title"><%= title %></div> -->
|
||||
<div class="loading-title"><%= title %></div>
|
||||
</div>
|
||||
<script src="/resource/loading.js"></script>
|
||||
</div>
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
"name": "vue-fastapi-admin-web",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
@ -16,13 +15,11 @@
|
||||
"@iconify/json": "^2.2.228",
|
||||
"@iconify/vue": "^4.1.1",
|
||||
"@unocss/eslint-config": "^0.55.0",
|
||||
"@vicons/ionicons5": "^0.13.0",
|
||||
"@vueuse/core": "^10.3.0",
|
||||
"@zclzone/eslint-config": "^0.0.4",
|
||||
"axios": "^1.4.0",
|
||||
"dayjs": "^1.11.9",
|
||||
"dotenv": "^16.3.1",
|
||||
"echarts": "^5.4.3",
|
||||
"eslint": "^8.46.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"naive-ui": "^2.34.4",
|
||||
|
||||
8
web/pnpm-lock.yaml
generated
@ -17,9 +17,6 @@ importers:
|
||||
'@unocss/eslint-config':
|
||||
specifier: ^0.55.0
|
||||
version: 0.55.7(eslint@8.57.0)(typescript@5.5.4)
|
||||
'@vicons/ionicons5':
|
||||
specifier: ^0.13.0
|
||||
version: 0.13.0
|
||||
'@vueuse/core':
|
||||
specifier: ^10.3.0
|
||||
version: 10.11.0(vue@3.4.34(typescript@5.5.4))
|
||||
@ -536,9 +533,6 @@ packages:
|
||||
peerDependencies:
|
||||
vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0
|
||||
|
||||
'@vicons/ionicons5@0.13.0':
|
||||
resolution: {integrity: sha512-zvZKBPjEXKN7AXNo2Na2uy+nvuv6SP4KAMQxpKL2vfHMj0fSvuw7JZcOPCjQC3e7ayssKnaoFVAhbYcW6v41qQ==}
|
||||
|
||||
'@vitejs/plugin-vue@4.6.2':
|
||||
resolution: {integrity: sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
@ -2993,8 +2987,6 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- rollup
|
||||
|
||||
'@vicons/ionicons5@0.13.0': {}
|
||||
|
||||
'@vitejs/plugin-vue@4.6.2(vite@4.5.3(@types/node@22.0.0)(sass@1.77.8)(terser@5.31.3))(vue@3.4.34(typescript@5.5.4))':
|
||||
dependencies:
|
||||
vite: 4.5.3(@types/node@22.0.0)(sass@1.77.8)(terser@5.31.3)
|
||||
|
||||
@ -5,14 +5,6 @@ export default {
|
||||
getUserInfo: () => request.get('/base/userinfo'),
|
||||
getUserMenu: () => request.get('/base/usermenu'),
|
||||
getUserApi: () => request.get('/base/userapi'),
|
||||
// 手机号
|
||||
registerPhone: (data) => request.post('/app-user/register', data, { noNeedToken: true }),
|
||||
loginPhone: (data) => request.post('/app-user/login', data, { noNeedToken: true }),
|
||||
// pages
|
||||
getIndustryList: () => request.get('/industry/list'),
|
||||
getHistoryList: (params) => request.get('/app-valuations/', { params }),
|
||||
valuations: (data = {}) => request.post('/app-valuations/', data),
|
||||
deleteValuations: (params = {}) => request.delete(`/app-valuations/${params.id}`),
|
||||
// profile
|
||||
updatePassword: (data = {}) => request.post('/base/update_password', data),
|
||||
// users
|
||||
@ -47,28 +39,4 @@ 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 }),
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ const Layout = () => import('@/layout/index.vue')
|
||||
export const basicRoutes = [
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/pages', // 默认跳转到首页
|
||||
redirect: '/workbench', // 默认跳转到首页
|
||||
meta: { order: 0 },
|
||||
},
|
||||
{
|
||||
@ -95,12 +95,6 @@ export const basicRoutes = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'pages',
|
||||
path: '/pages',
|
||||
component: () => import('@/views/pages/index.vue'),
|
||||
isHidden: true,
|
||||
},
|
||||
{
|
||||
name: '403',
|
||||
path: '/403',
|
||||
|
||||
@ -77,13 +77,13 @@ export const usePermissionStore = defineStore('permission', {
|
||||
},
|
||||
actions: {
|
||||
async generateRoutes() {
|
||||
// const res = await api.getUserMenu() // 调用接口获取后端传来的菜单路由
|
||||
// this.accessRoutes = buildRoutes(res.data) // 处理成前端路由格式
|
||||
const res = await api.getUserMenu() // 调用接口获取后端传来的菜单路由
|
||||
this.accessRoutes = buildRoutes(res.data) // 处理成前端路由格式
|
||||
return this.accessRoutes
|
||||
},
|
||||
async getAccessApis() {
|
||||
// const res = await api.getUserApi()
|
||||
// this.accessApis = res.data
|
||||
const res = await api.getUserApi()
|
||||
this.accessApis = res.data
|
||||
return this.accessApis
|
||||
},
|
||||
resetPermission() {
|
||||
|
||||
@ -36,15 +36,14 @@ export const useUserStore = defineStore('user', {
|
||||
actions: {
|
||||
async getUserInfo() {
|
||||
try {
|
||||
// const res = await api.getUserInfo()
|
||||
// if (res.code === 401) {
|
||||
// this.logout()
|
||||
// return
|
||||
// }
|
||||
// const { id, username, email, avatar, roles, is_superuser, is_active } = res.data
|
||||
// this.userInfo = { id, username, email, avatar, roles, is_superuser, is_active }
|
||||
// return res.data
|
||||
return {}
|
||||
const res = await api.getUserInfo()
|
||||
if (res.code === 401) {
|
||||
this.logout()
|
||||
return
|
||||
}
|
||||
const { id, username, email, avatar, roles, is_superuser, is_active } = res.data
|
||||
this.userInfo = { id, username, email, avatar, roles, is_superuser, is_active }
|
||||
return res.data
|
||||
} catch (error) {
|
||||
return error
|
||||
}
|
||||
|
||||
@ -16,5 +16,4 @@ export function createAxios(options = {}) {
|
||||
|
||||
export const request = createAxios({
|
||||
baseURL: import.meta.env.VITE_BASE_API,
|
||||
Authorization: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxOCwicGhvbmUiOiIxNTg1MDIwMTEzOSIsImV4cCI6MTc2MDYzNTc0NX0.Z-2oCgVYlLo4JVuFLwNWqhj2iYyAvkZxWQp0h6AlhuI'
|
||||
})
|
||||
|
||||
@ -26,10 +26,7 @@ export function resResolve(response) {
|
||||
const code = data?.code ?? status
|
||||
/** 根据code处理对应的操作,并返回处理后的message */
|
||||
const message = resolveResError(code, data?.msg ?? statusText)
|
||||
console.log(message,'message')
|
||||
if(message){
|
||||
window.$message?.error(message, { keepAliveOnHover: true })
|
||||
}
|
||||
window.$message?.error(message, { keepAliveOnHover: true })
|
||||
return Promise.reject({ code, message, error: data || response })
|
||||
}
|
||||
return Promise.resolve(data)
|
||||
@ -40,10 +37,7 @@ export async function resReject(error) {
|
||||
const code = error?.code
|
||||
/** 根据code处理对应的操作,并返回处理后的message */
|
||||
const message = resolveResError(code, error.message)
|
||||
console.log(message,'message')
|
||||
if(message){
|
||||
window.$message?.error(message)
|
||||
}
|
||||
window.$message?.error(message)
|
||||
return Promise.reject({ code, message, error })
|
||||
}
|
||||
const { data, status } = error.response
|
||||
@ -60,9 +54,6 @@ export async function resReject(error) {
|
||||
// 后端返回的response数据
|
||||
const code = data?.code ?? status
|
||||
const message = resolveResError(code, data?.msg ?? error.message)
|
||||
console.log(message,'message')
|
||||
if(message){
|
||||
window.$message?.error(message, { keepAliveOnHover: true })
|
||||
}
|
||||
window.$message?.error(message, { keepAliveOnHover: true })
|
||||
return Promise.reject({ code, message, error: error.response?.data || error.response })
|
||||
}
|
||||
|
||||
@ -2,40 +2,49 @@
|
||||
<AppPage :show-footer="true" bg-cover :style="{ backgroundImage: `url(${bgImg})` }">
|
||||
<div
|
||||
style="transform: translateY(25px)"
|
||||
class="m-auto max-w-1500 min-w-750 f-c-c rounded-12 bg-white bg-opacity-80"
|
||||
class="m-auto max-w-1500 min-w-345 f-c-c rounded-10 bg-white bg-opacity-60 p-15 card-shadow"
|
||||
dark:bg-dark
|
||||
>
|
||||
<div w-750 px-20 style="height: 400px; padding-top: 50px; text-align: center;">
|
||||
<img style="width: 371px; height: 60px; margin: auto;" src="@/assets/images/logo.png" alt="">
|
||||
<div mt-50 style="text-align: center; font-size: 48px; color: #303133; line-height: 48px; font-weight: 600; ">
|
||||
非遗IP价值评估系统
|
||||
</div>
|
||||
<div style="text-align: center; margin-top: 16px; color: #606266;">
|
||||
基于深度学习算法的智能评估系统,为您的知识产权和非物质文化遗产提供专业的价值评估服务
|
||||
<div hidden w-380 px-20 py-35 md:block>
|
||||
<icon-custom-front-page pt-10 text-300 color-primary></icon-custom-front-page>
|
||||
</div>
|
||||
|
||||
<div w-320 flex-col px-20 py-35>
|
||||
<h5 f-c-c text-24 font-normal color="#6a6a6a">
|
||||
<icon-custom-logo mr-10 text-50 color-primary />{{ $t('app_name') }}
|
||||
</h5>
|
||||
<div mt-30>
|
||||
<n-input
|
||||
v-model:value="loginInfo.username"
|
||||
autofocus
|
||||
class="h-50 items-center pl-10 text-16"
|
||||
placeholder="admin"
|
||||
:maxlength="20"
|
||||
/>
|
||||
</div>
|
||||
<div mt-30>
|
||||
<n-input
|
||||
v-model:value="loginInfo.phone"
|
||||
style="display: inline-block; width: 260px; height: 42px; text-align: left; line-height: 42px;"
|
||||
placeholder="请输入手机号"
|
||||
v-model:value="loginInfo.password"
|
||||
class="h-50 items-center pl-10 text-16"
|
||||
type="password"
|
||||
show-password-on="mousedown"
|
||||
placeholder="123456"
|
||||
:maxlength="20"
|
||||
@keypress.enter="handleRegister"
|
||||
>
|
||||
<template #prefix>
|
||||
<img style="width: 18px; height: 18px; margin-right: 8px;" src="@/assets/images/phone.png" alt="">
|
||||
</template>
|
||||
</n-input>
|
||||
@keypress.enter="handleLogin"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div mt-20>
|
||||
<n-button
|
||||
w-126
|
||||
h-42
|
||||
h-50
|
||||
w-full
|
||||
rounded-5
|
||||
text-16
|
||||
type="primary"
|
||||
style="background: linear-gradient( 93deg, #880C22 0%, #A30113 100%); margin-left: 20px; font-size: 16px; border: none !important;"
|
||||
@click="handleRegister"
|
||||
:loading="loading"
|
||||
@click="handleLogin"
|
||||
>
|
||||
立即登录
|
||||
<img style="width: 18px; height: 18px; margin-left: 2px;" src="@/assets/images/go.png" alt="">
|
||||
{{ $t('views.login.text_login') }}
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
@ -45,7 +54,7 @@
|
||||
|
||||
<script setup>
|
||||
import { lStorage, setToken } from '@/utils'
|
||||
import bgImg from '@/assets/images/login_bg.png'
|
||||
import bgImg from '@/assets/images/login_bg.webp'
|
||||
import api from '@/api'
|
||||
import { addDynamicRoutes } from '@/router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
@ -55,55 +64,45 @@ const { query } = useRoute()
|
||||
const { t } = useI18n({ useScope: 'global' })
|
||||
|
||||
const loginInfo = ref({
|
||||
phone: '',
|
||||
username: '',
|
||||
password: '',
|
||||
})
|
||||
|
||||
initLoginInfo()
|
||||
|
||||
function initLoginInfo() {
|
||||
if(localStorage.getItem('phone')){
|
||||
loginInfo.value.phone = localStorage.getItem('phone')
|
||||
const localLoginInfo = lStorage.get('loginInfo')
|
||||
if (localLoginInfo) {
|
||||
loginInfo.value.username = localLoginInfo.username || ''
|
||||
loginInfo.value.password = localLoginInfo.password || ''
|
||||
}
|
||||
// const localLoginInfo = lStorage.get('loginInfo')
|
||||
// if (localLoginInfo) {
|
||||
// loginInfo.value.phone = localLoginInfo.phone || ''
|
||||
// loginInfo.value.password = localLoginInfo.password || ''
|
||||
// }
|
||||
}
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
async function handleRegister() {
|
||||
const { phone } = loginInfo.value
|
||||
if (!phone) {
|
||||
$message.warning('请输入手机号')
|
||||
async function handleLogin() {
|
||||
const { username, password } = loginInfo.value
|
||||
if (!username || !password) {
|
||||
$message.warning(t('views.login.message_input_username_password'))
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
await api.registerPhone({ phone })
|
||||
.then(res=>{
|
||||
handleLogin()
|
||||
})
|
||||
.catch(res=>{
|
||||
handleLogin()
|
||||
})
|
||||
}
|
||||
|
||||
async function handleLogin() {
|
||||
const { phone } = loginInfo.value
|
||||
loading.value = true
|
||||
await api.loginPhone({ phone, password: phone.slice(5,11) }).catch(res=>{
|
||||
setToken(res.error.access_token)
|
||||
try {
|
||||
loading.value = true
|
||||
$message.loading(t('views.login.message_verifying'))
|
||||
const res = await api.login({ username, password: password.toString() })
|
||||
$message.success(t('views.login.message_login_success'))
|
||||
setToken(res.data.access_token)
|
||||
await addDynamicRoutes()
|
||||
if (query.redirect) {
|
||||
const path = query.redirect
|
||||
localStorage.setItem('phone', phone)
|
||||
console.log('path', { path, query })
|
||||
Reflect.deleteProperty(query, 'redirect')
|
||||
router.push({ path, query })
|
||||
} else {
|
||||
router.push('/')
|
||||
}
|
||||
loading.value = false
|
||||
})
|
||||
|
||||
} catch (e) {
|
||||
console.error('login error', e.error)
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -34,7 +34,7 @@
|
||||
:key="i"
|
||||
class="mb-10 mt-10 w-300 cursor-pointer"
|
||||
hover:card-shadow
|
||||
title=""
|
||||
title="Vue FastAPI Admin"
|
||||
size="small"
|
||||
>
|
||||
<p op-60>{{ dummyText }}</p>
|
||||
|
||||
8
web1/.env.development
Normal file
@ -0,0 +1,8 @@
|
||||
# 资源公共路径,需要以 /开头和结尾
|
||||
VITE_PUBLIC_PATH = '/'
|
||||
|
||||
# 是否启用代理
|
||||
VITE_USE_PROXY = true
|
||||
|
||||
# base api
|
||||
VITE_BASE_API = 'https://value.cdcee.net/api/v1'
|
||||
11
web1/.env.production
Normal file
@ -0,0 +1,11 @@
|
||||
# 资源公共路径,需要以 /开头和结尾
|
||||
VITE_PUBLIC_PATH = '/'
|
||||
|
||||
# base api
|
||||
VITE_BASE_API = 'https://value.cdcee.net/api/v1'
|
||||
|
||||
# 是否启用压缩
|
||||
VITE_USE_COMPRESS = true
|
||||
|
||||
# 压缩类型
|
||||
VITE_COMPRESS_TYPE = gzip
|
||||
62
web1/.eslint-global-variables.json
Normal file
@ -0,0 +1,62 @@
|
||||
{
|
||||
"globals": {
|
||||
"$loadingBar": true,
|
||||
"$message": true,
|
||||
"defineOptions": true,
|
||||
"$dialog": true,
|
||||
"$notification": true,
|
||||
"EffectScope": true,
|
||||
"computed": true,
|
||||
"createApp": true,
|
||||
"customRef": true,
|
||||
"defineAsyncComponent": true,
|
||||
"defineComponent": true,
|
||||
"effectScope": true,
|
||||
"getCurrentInstance": true,
|
||||
"getCurrentScope": true,
|
||||
"h": true,
|
||||
"inject": true,
|
||||
"isProxy": true,
|
||||
"isReactive": true,
|
||||
"isReadonly": true,
|
||||
"isRef": true,
|
||||
"markRaw": true,
|
||||
"nextTick": true,
|
||||
"onActivated": true,
|
||||
"onBeforeMount": true,
|
||||
"onBeforeUnmount": true,
|
||||
"onBeforeUpdate": true,
|
||||
"onDeactivated": true,
|
||||
"onErrorCaptured": true,
|
||||
"onMounted": true,
|
||||
"onRenderTracked": true,
|
||||
"onRenderTriggered": true,
|
||||
"onScopeDispose": true,
|
||||
"onServerPrefetch": true,
|
||||
"onUnmounted": true,
|
||||
"onUpdated": true,
|
||||
"provide": true,
|
||||
"reactive": true,
|
||||
"readonly": true,
|
||||
"ref": true,
|
||||
"resolveComponent": true,
|
||||
"shallowReactive": true,
|
||||
"shallowReadonly": true,
|
||||
"shallowRef": true,
|
||||
"toRaw": true,
|
||||
"toRef": true,
|
||||
"toRefs": true,
|
||||
"triggerRef": true,
|
||||
"unref": true,
|
||||
"useAttrs": true,
|
||||
"useCssModule": true,
|
||||
"useCssVars": true,
|
||||
"useRoute": true,
|
||||
"useRouter": true,
|
||||
"useSlots": true,
|
||||
"watch": true,
|
||||
"watchEffect": true,
|
||||
"watchPostEffect": true,
|
||||
"watchSyncEffect": true
|
||||
}
|
||||
}
|
||||
4
web1/.eslintignore
Normal file
@ -0,0 +1,4 @@
|
||||
node_modules
|
||||
dist
|
||||
public
|
||||
package.json
|
||||
26
web1/.gitignore
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
# 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
|
||||
stats.html
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
3
web1/.prettierignore
Normal file
@ -0,0 +1,3 @@
|
||||
/node_modules/**
|
||||
/dist/*
|
||||
/public/*
|
||||
6
web1/.prettierrc.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"printWidth": 100,
|
||||
"singleQuote": true,
|
||||
"semi": false,
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
20
web1/README.md
Normal file
@ -0,0 +1,20 @@
|
||||
## 快速开始
|
||||
|
||||
进入前端目录
|
||||
|
||||
```sh
|
||||
cd web
|
||||
```
|
||||
|
||||
安装依赖(建议使用pnpm: https://pnpm.io/zh/installation)
|
||||
|
||||
```sh
|
||||
npm i -g pnpm # 已安装可忽略
|
||||
pnpm i # 或者 npm i
|
||||
```
|
||||
|
||||
启动
|
||||
|
||||
```sh
|
||||
pnpm dev
|
||||
```
|
||||
13
web1/build/config/define.js
Normal file
@ -0,0 +1,13 @@
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
/**
|
||||
* * 此处定义的是全局常量,启动或打包后将添加到window中
|
||||
* https://vitejs.cn/config/#define
|
||||
*/
|
||||
|
||||
// 项目构建时间
|
||||
const _BUILD_TIME_ = JSON.stringify(dayjs().format('YYYY-MM-DD HH:mm:ss'))
|
||||
|
||||
export const viteDefine = {
|
||||
_BUILD_TIME_,
|
||||
}
|
||||
1
web1/build/config/index.js
Normal file
@ -0,0 +1 @@
|
||||
export * from './define'
|
||||
23
web1/build/constant.js
Normal file
@ -0,0 +1,23 @@
|
||||
export const OUTPUT_DIR = 'dist'
|
||||
|
||||
export const PROXY_CONFIG = {
|
||||
// /**
|
||||
// * @desc 替换匹配值
|
||||
// * @请求路径 http://localhost:3100/api/user
|
||||
// * @转发路径 http://localhost:9999/api/v1 +/user
|
||||
// */
|
||||
// '/api': {
|
||||
// target: 'http://localhost:9999/api/v1',
|
||||
// changeOrigin: true,
|
||||
// rewrite: (path) => path.replace(new RegExp('^/api'), ''),
|
||||
// },
|
||||
/**
|
||||
* @desc 不替换匹配值
|
||||
* @请求路径 http://localhost:3100/api/v1/user
|
||||
* @转发路径 http://localhost:9999/api/v1/user
|
||||
*/
|
||||
'/api/v1': {
|
||||
target: 'http://124.222.245.240:8080',
|
||||
changeOrigin: true,
|
||||
},
|
||||
}
|
||||
15
web1/build/plugin/html.js
Normal file
@ -0,0 +1,15 @@
|
||||
import { createHtmlPlugin } from 'vite-plugin-html'
|
||||
|
||||
export function configHtmlPlugin(viteEnv, isBuild) {
|
||||
const { VITE_TITLE } = viteEnv
|
||||
|
||||
const htmlPlugin = createHtmlPlugin({
|
||||
minify: isBuild,
|
||||
inject: {
|
||||
data: {
|
||||
title: VITE_TITLE,
|
||||
},
|
||||
},
|
||||
})
|
||||
return htmlPlugin
|
||||
}
|
||||
35
web1/build/plugin/index.js
Normal file
@ -0,0 +1,35 @@
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
/**
|
||||
* * unocss插件,原子css
|
||||
* https://github.com/antfu/unocss
|
||||
*/
|
||||
import Unocss from 'unocss/vite'
|
||||
|
||||
// rollup打包分析插件
|
||||
import { visualizer } from 'rollup-plugin-visualizer'
|
||||
// 压缩
|
||||
import viteCompression from 'vite-plugin-compression'
|
||||
|
||||
import { configHtmlPlugin } from './html'
|
||||
import unplugin from './unplugin'
|
||||
|
||||
export function createVitePlugins(viteEnv, isBuild) {
|
||||
const plugins = [vue(), ...unplugin, configHtmlPlugin(viteEnv, isBuild), Unocss()]
|
||||
|
||||
if (viteEnv.VITE_USE_COMPRESS) {
|
||||
plugins.push(viteCompression({ algorithm: viteEnv.VITE_COMPRESS_TYPE || 'gzip' }))
|
||||
}
|
||||
|
||||
if (isBuild) {
|
||||
plugins.push(
|
||||
visualizer({
|
||||
open: true,
|
||||
gzipSize: true,
|
||||
brotliSize: true,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
return plugins
|
||||
}
|
||||
46
web1/build/plugin/unplugin.js
Normal file
@ -0,0 +1,46 @@
|
||||
import { resolve } from 'path'
|
||||
import AutoImport from 'unplugin-auto-import/vite'
|
||||
import Components from 'unplugin-vue-components/vite'
|
||||
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'
|
||||
import { FileSystemIconLoader } from 'unplugin-icons/loaders'
|
||||
import IconsResolver from 'unplugin-icons/resolver'
|
||||
|
||||
/**
|
||||
* * unplugin-icons插件,自动引入iconify图标
|
||||
* usage: https://github.com/antfu/unplugin-icons
|
||||
* 图标库: https://icones.js.org/
|
||||
*/
|
||||
import Icons from 'unplugin-icons/vite'
|
||||
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
|
||||
|
||||
import { getSrcPath } from '../utils'
|
||||
|
||||
const customIconPath = resolve(getSrcPath(), 'assets/svg')
|
||||
|
||||
export default [
|
||||
AutoImport({
|
||||
imports: ['vue', 'vue-router'],
|
||||
dts: false,
|
||||
}),
|
||||
Icons({
|
||||
compiler: 'vue3',
|
||||
customCollections: {
|
||||
custom: FileSystemIconLoader(customIconPath),
|
||||
},
|
||||
scale: 1,
|
||||
defaultClass: 'inline-block',
|
||||
}),
|
||||
Components({
|
||||
resolvers: [
|
||||
NaiveUiResolver(),
|
||||
IconsResolver({ customCollections: ['custom'], componentPrefix: 'icon' }),
|
||||
],
|
||||
dts: false,
|
||||
}),
|
||||
createSvgIconsPlugin({
|
||||
iconDirs: [customIconPath],
|
||||
symbolId: 'icon-custom-[dir]-[name]',
|
||||
inject: 'body-last',
|
||||
customDomId: '__CUSTOM_SVG_ICON__',
|
||||
}),
|
||||
]
|
||||
15
web1/build/script/build-cname.js
Normal file
@ -0,0 +1,15 @@
|
||||
import { resolve } from 'path'
|
||||
import chalk from 'chalk'
|
||||
import { writeFileSync } from 'fs-extra'
|
||||
import { OUTPUT_DIR } from '../constant'
|
||||
import { getEnvConfig, getRootPath } from '../utils'
|
||||
|
||||
export function runBuildCNAME() {
|
||||
const { VITE_CNAME } = getEnvConfig()
|
||||
if (!VITE_CNAME) return
|
||||
try {
|
||||
writeFileSync(resolve(getRootPath(), `${OUTPUT_DIR}/CNAME`), VITE_CNAME)
|
||||
} catch (error) {
|
||||
console.log(chalk.red('CNAME file failed to package:\n' + error))
|
||||
}
|
||||
}
|
||||
14
web1/build/script/index.js
Normal file
@ -0,0 +1,14 @@
|
||||
import chalk from 'chalk'
|
||||
import { runBuildCNAME } from './build-cname'
|
||||
|
||||
export const runBuild = async () => {
|
||||
try {
|
||||
runBuildCNAME()
|
||||
console.log(`✨ ${chalk.cyan('build successfully!')}`)
|
||||
} catch (error) {
|
||||
console.log(chalk.red('vite build error:\n' + error))
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
runBuild()
|
||||
70
web1/build/utils.js
Normal file
@ -0,0 +1,70 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import dotenv from 'dotenv'
|
||||
|
||||
/**
|
||||
* * 项目根路径
|
||||
* @descrition 结尾不带/
|
||||
*/
|
||||
export function getRootPath() {
|
||||
return path.resolve(process.cwd())
|
||||
}
|
||||
|
||||
/**
|
||||
* * 项目src路径
|
||||
* @param srcName src目录名称(默认: "src")
|
||||
* @descrition 结尾不带斜杠
|
||||
*/
|
||||
export function getSrcPath(srcName = 'src') {
|
||||
return path.resolve(getRootPath(), srcName)
|
||||
}
|
||||
|
||||
export function convertEnv(envOptions) {
|
||||
const result = {}
|
||||
if (!envOptions) return result
|
||||
|
||||
for (const envKey in envOptions) {
|
||||
let envVal = envOptions[envKey]
|
||||
if (['true', 'false'].includes(envVal)) envVal = envVal === 'true'
|
||||
|
||||
if (['VITE_PORT'].includes(envKey)) envVal = +envVal
|
||||
|
||||
result[envKey] = envVal
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前环境下生效的配置文件名
|
||||
*/
|
||||
function getConfFiles() {
|
||||
const script = process.env.npm_lifecycle_script
|
||||
const reg = new RegExp('--mode ([a-z_\\d]+)')
|
||||
const result = reg.exec(script)
|
||||
if (result) {
|
||||
const mode = result[1]
|
||||
return ['.env', '.env.local', `.env.${mode}`]
|
||||
}
|
||||
return ['.env', '.env.local', '.env.production']
|
||||
}
|
||||
|
||||
export function getEnvConfig(match = 'VITE_', confFiles = getConfFiles()) {
|
||||
let envConfig = {}
|
||||
confFiles.forEach((item) => {
|
||||
try {
|
||||
if (fs.existsSync(path.resolve(process.cwd(), item))) {
|
||||
const env = dotenv.parse(fs.readFileSync(path.resolve(process.cwd(), item)))
|
||||
envConfig = { ...envConfig, ...env }
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Error in parsing ${item}`, e)
|
||||
}
|
||||
})
|
||||
const reg = new RegExp(`^(${match})`)
|
||||
Object.keys(envConfig).forEach((key) => {
|
||||
if (!reg.test(key)) {
|
||||
Reflect.deleteProperty(envConfig, key)
|
||||
}
|
||||
})
|
||||
return envConfig
|
||||
}
|
||||
16
web1/i18n/index.js
Normal file
@ -0,0 +1,16 @@
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import { lStorage } from '@/utils'
|
||||
|
||||
import messages from './messages'
|
||||
|
||||
const currentLocale = lStorage.get('locale')
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
globalInjection: true,
|
||||
locale: currentLocale || 'cn',
|
||||
fallbackLocale: 'cn',
|
||||
messages: messages,
|
||||
})
|
||||
|
||||
export default i18n
|
||||
62
web1/i18n/messages/cn.json
Normal file
@ -0,0 +1,62 @@
|
||||
{
|
||||
"lang": "中文",
|
||||
"app_name": "Vue FastAPI Admin",
|
||||
"header": {
|
||||
"label_profile": "个人信息",
|
||||
"label_logout": "退出登录",
|
||||
"label_logout_dialog_title": "提示",
|
||||
"text_logout_confirm": "确认退出?",
|
||||
"text_logout_success": "已退出登录"
|
||||
},
|
||||
"views": {
|
||||
"login": {
|
||||
"text_login": "登录",
|
||||
"message_input_username_password": "请输入用户名和密码",
|
||||
"message_verifying": "正在验证...",
|
||||
"message_login_success": "登录成功"
|
||||
},
|
||||
"workbench": {
|
||||
"label_workbench": "工作台",
|
||||
"text_hello": "hello, {username}",
|
||||
"text_welcome": "今天又是元气满满的一天!",
|
||||
"label_number_of_items": "项目数",
|
||||
"label_upcoming": "待办",
|
||||
"label_information": "消息",
|
||||
"label_project": "项目",
|
||||
"label_more": "更多"
|
||||
},
|
||||
"profile": {
|
||||
"label_profile": "个人中心",
|
||||
"label_modify_information": "修改信息",
|
||||
"label_change_password": "修改密码",
|
||||
"label_avatar": "头像",
|
||||
"label_username": "用户姓名",
|
||||
"label_email": "邮箱",
|
||||
"label_old_password": "旧密码",
|
||||
"label_new_password": "新密码",
|
||||
"label_confirm_password": "确认密码",
|
||||
"placeholder_username": "请填写姓名",
|
||||
"placeholder_email": "请填写邮箱",
|
||||
"placeholder_old_password": "请输入旧密码",
|
||||
"placeholder_new_password": "请输入新密码",
|
||||
"placeholder_confirm_password": "请再次输入新密码",
|
||||
"message_username_required": "请输入昵称",
|
||||
"message_old_password_required": "请输入旧密码",
|
||||
"message_new_password_required": "请输入新密码",
|
||||
"message_password_confirmation_required": "请再次输入密码",
|
||||
"message_password_confirmation_diff": "两次密码输入不一致"
|
||||
},
|
||||
"errors": {
|
||||
"label_error": "错误页",
|
||||
"text_back_to_home": "返回首页"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"text": {
|
||||
"update_success": "修改成功"
|
||||
},
|
||||
"buttons": {
|
||||
"update": "修改"
|
||||
}
|
||||
}
|
||||
}
|
||||
62
web1/i18n/messages/en.json
Normal file
@ -0,0 +1,62 @@
|
||||
{
|
||||
"lang": "English",
|
||||
"app_name": "Vue FastAPI Admin",
|
||||
"header": {
|
||||
"label_profile": "Profile",
|
||||
"label_logout": "Logout",
|
||||
"label_logout_dialog_title": "Hint",
|
||||
"text_logout_confirm": "Logout confirm",
|
||||
"text_logout_success": "Logout success"
|
||||
},
|
||||
"views": {
|
||||
"login": {
|
||||
"text_login": "Login",
|
||||
"message_input_username_password": "Please enter username and password",
|
||||
"message_verifying": "Verifying...",
|
||||
"message_login_success": "Login successful"
|
||||
},
|
||||
"workbench": {
|
||||
"label_workbench": "Workbench",
|
||||
"text_hello": "hello, {username}",
|
||||
"text_welcome": "Today is another day full of energy!",
|
||||
"label_number_of_items": "Number of items",
|
||||
"label_upcoming": "Upcoming",
|
||||
"label_information": "Information",
|
||||
"label_project": "Project",
|
||||
"label_more": "More"
|
||||
},
|
||||
"profile": {
|
||||
"label_profile": "Profile",
|
||||
"label_modify_information": "Modify your information",
|
||||
"label_change_password": "Change password",
|
||||
"label_avatar": "Avatar",
|
||||
"label_username": "Username",
|
||||
"label_email": "Email",
|
||||
"label_old_password": "Old password",
|
||||
"label_new_password": "New password",
|
||||
"label_confirm_password": "Password confirmation",
|
||||
"placeholder_username": "Please fill in your name",
|
||||
"placeholder_email": "Please fill in your email address",
|
||||
"placeholder_old_password": "Please enter the old password",
|
||||
"placeholder_new_password": "Please enter a new password",
|
||||
"placeholder_confirm_password": "Please enter the confirm password",
|
||||
"message_username_required": "Please enter username",
|
||||
"message_old_password_required": "Please enter the old password",
|
||||
"message_new_password_required": "Please enter a new password",
|
||||
"message_password_confirmation_required": "Please enter confirm password",
|
||||
"message_password_confirmation_diff": "Two password inputs are inconsistent"
|
||||
},
|
||||
"errors": {
|
||||
"label_error": "Error",
|
||||
"text_back_to_home": "Back to home"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"text": {
|
||||
"update_success": "Update success"
|
||||
},
|
||||
"buttons": {
|
||||
"update": "Update"
|
||||
}
|
||||
}
|
||||
}
|
||||
7
web1/i18n/messages/index.js
Normal file
@ -0,0 +1,7 @@
|
||||
import * as en from './en.json'
|
||||
import * as cn from './cn.json'
|
||||
|
||||
export default {
|
||||
en,
|
||||
cn,
|
||||
}
|
||||
35
web1/index.html
Normal file
@ -0,0 +1,35 @@
|
||||
<!doctype html>
|
||||
<html lang="cn">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="Expires" content="0" />
|
||||
<meta http-equiv="Pragma" content="no-cache" />
|
||||
<meta http-equiv="Cache-control" content="no-cache" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="icon" href="/favicon.svg" />
|
||||
<link rel="stylesheet" href="/resource/loading.css" />
|
||||
|
||||
<title><%= title %></title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app">
|
||||
<!-- 白屏时的loading效果 -->
|
||||
<div class="loading-container">
|
||||
<div id="loadingLogo" class="loading-svg"></div>
|
||||
<div class="loading-spin__container">
|
||||
<div class="loading-spin">
|
||||
<div class="left-0 top-0 loading-spin-item"></div>
|
||||
<div class="left-0 bottom-0 loading-spin-item loading-delay-500"></div>
|
||||
<div class="right-0 top-0 loading-spin-item loading-delay-1000"></div>
|
||||
<div class="right-0 bottom-0 loading-spin-item loading-delay-1500"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="loading-title"><%= title %></div> -->
|
||||
</div>
|
||||
<script src="/resource/loading.js"></script>
|
||||
</div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
14
web1/jsconfig.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"baseUrl": "./",
|
||||
"moduleResolution": "node",
|
||||
"paths": {
|
||||
"~/*": ["./*"],
|
||||
"@/*": ["src/*"]
|
||||
},
|
||||
"jsx": "preserve",
|
||||
"allowJs": true
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
60
web1/package.json
Normal file
@ -0,0 +1,60 @@
|
||||
{
|
||||
"name": "vue-fastapi-admin-web",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint --ext .js,.vue .",
|
||||
"lint:fix": "eslint --fix --ext .js,.vue .",
|
||||
"lint:staged": "lint-staged",
|
||||
"prettier": "npx prettier --write ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@iconify/json": "^2.2.228",
|
||||
"@iconify/vue": "^4.1.1",
|
||||
"@unocss/eslint-config": "^0.55.0",
|
||||
"@vicons/ionicons5": "^0.13.0",
|
||||
"@vueuse/core": "^10.3.0",
|
||||
"@zclzone/eslint-config": "^0.0.4",
|
||||
"axios": "^1.4.0",
|
||||
"dayjs": "^1.11.9",
|
||||
"dotenv": "^16.3.1",
|
||||
"echarts": "^5.4.3",
|
||||
"eslint": "^8.46.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"naive-ui": "^2.34.4",
|
||||
"pinia": "^2.1.6",
|
||||
"rollup-plugin-visualizer": "^5.9.2",
|
||||
"sass": "^1.65.1",
|
||||
"typescript": "^5.1.6",
|
||||
"unocss": "^0.55.0",
|
||||
"unplugin-auto-import": "^0.16.6",
|
||||
"unplugin-icons": "^0.16.5",
|
||||
"unplugin-vue-components": "^0.25.1",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-html": "^3.2.0",
|
||||
"vite-plugin-svg-icons": "^2.0.1",
|
||||
"vue": "^3.3.4",
|
||||
"vue-i18n": "9",
|
||||
"vue-router": "^4.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"vite": "^4.4.6"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,vue}": [
|
||||
"eslint --ext .js,.vue ."
|
||||
]
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"@zclzone",
|
||||
"@unocss",
|
||||
".eslint-global-variables.json"
|
||||
]
|
||||
}
|
||||
}
|
||||
5290
web1/pnpm-lock.yaml
generated
Normal file
1
web1/public/favicon.svg
Normal file
|
After Width: | Height: | Size: 10 KiB |
91
web1/public/resource/loading.css
Normal file
@ -0,0 +1,91 @@
|
||||
.loading-container {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.loading-svg {
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.loading-spin__container {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
margin: 36px 0;
|
||||
}
|
||||
|
||||
.loading-spin {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
animation: loadingSpin 1s linear infinite;
|
||||
}
|
||||
|
||||
.left-0 {
|
||||
left: 0;
|
||||
}
|
||||
.right-0 {
|
||||
right: 0;
|
||||
}
|
||||
.top-0 {
|
||||
top: 0;
|
||||
}
|
||||
.bottom-0 {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.loading-spin-item {
|
||||
position: absolute;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
background-color: var(--primary-color);
|
||||
border-radius: 8px;
|
||||
-webkit-animation: loadingPulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
animation: loadingPulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
}
|
||||
|
||||
@keyframes loadingSpin {
|
||||
from {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes loadingPulse {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: .5;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-delay-500 {
|
||||
-webkit-animation-delay: 500ms;
|
||||
animation-delay: 500ms;
|
||||
}
|
||||
.loading-delay-1000 {
|
||||
-webkit-animation-delay: 1000ms;
|
||||
animation-delay: 1000ms;
|
||||
}
|
||||
.loading-delay-1500 {
|
||||
-webkit-animation-delay: 1500ms;
|
||||
animation-delay: 1500ms;
|
||||
}
|
||||
|
||||
.loading-title {
|
||||
font-size: 28px;
|
||||
font-weight: 500;
|
||||
color: #6a6a6a;
|
||||
}
|
||||
25
web1/public/resource/loading.js
Normal file
1
web1/settings/index.js
Normal file
@ -0,0 +1 @@
|
||||
export * from './theme.json'
|
||||
37
web1/settings/theme.json
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"header": {
|
||||
"height": 60
|
||||
},
|
||||
"tags": {
|
||||
"visible": true,
|
||||
"height": 50
|
||||
},
|
||||
"naiveThemeOverrides": {
|
||||
"common": {
|
||||
"primaryColor": "#F4511E",
|
||||
"primaryColorHover": "#F4511E",
|
||||
"primaryColorPressed": "#2B4C59FF",
|
||||
"primaryColorSuppl": "#F4511E",
|
||||
|
||||
"infoColor": "#2080F0FF",
|
||||
"infoColorHover": "#4098FCFF",
|
||||
"infoColorPressed": "#1060C9FF",
|
||||
"infoColorSuppl": "#4098FCFF",
|
||||
|
||||
"successColor": "#18A058FF",
|
||||
"successColorHover": "#F4511E",
|
||||
"successColorPressed": "#0C7A43FF",
|
||||
"successColorSuppl": "#F4511E",
|
||||
|
||||
"warningColor": "#F0A020FF",
|
||||
"warningColorHover": "#FCB040FF",
|
||||
"warningColorPressed": "#C97C10FF",
|
||||
"warningColorSuppl": "#FCB040FF",
|
||||
|
||||
"errorColor": "#D03050FF",
|
||||
"errorColorHover": "#DE576DFF",
|
||||
"errorColorPressed": "#AB1F3FFF",
|
||||
"errorColorSuppl": "#DE576DFF"
|
||||
}
|
||||
}
|
||||
}
|
||||
11
web1/src/App.vue
Normal file
@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<AppProvider>
|
||||
<router-view v-slot="{ Component }">
|
||||
<component :is="Component" />
|
||||
</router-view>
|
||||
</AppProvider>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import AppProvider from '@/components/common/AppProvider.vue'
|
||||
</script>
|
||||
74
web1/src/api/index.js
Normal file
@ -0,0 +1,74 @@
|
||||
import { request } from '@/utils'
|
||||
|
||||
export default {
|
||||
login: (data) => request.post('/base/access_token', data, { noNeedToken: true }),
|
||||
getUserInfo: () => request.get('/base/userinfo'),
|
||||
getUserMenu: () => request.get('/base/usermenu'),
|
||||
getUserApi: () => request.get('/base/userapi'),
|
||||
// 手机号
|
||||
registerPhone: (data) => request.post('/app-user/register', data, { noNeedToken: true }),
|
||||
loginPhone: (data) => request.post('/app-user/login', data, { noNeedToken: true }),
|
||||
// pages
|
||||
getIndustryList: () => request.get('/industry/list'),
|
||||
getHistoryList: (params) => request.get('/app-valuations/', { params }),
|
||||
valuations: (data = {}) => request.post('/app-valuations/', data),
|
||||
deleteValuations: (params = {}) => request.delete(`/app-valuations/${params.id}`),
|
||||
// profile
|
||||
updatePassword: (data = {}) => request.post('/base/update_password', data),
|
||||
// users
|
||||
getUserList: (params = {}) => request.get('/user/list', { params }),
|
||||
getUserById: (params = {}) => request.get('/user/get', { params }),
|
||||
createUser: (data = {}) => request.post('/user/create', data),
|
||||
updateUser: (data = {}) => request.post('/user/update', data),
|
||||
deleteUser: (params = {}) => request.delete(`/user/delete`, { params }),
|
||||
resetPassword: (data = {}) => request.post(`/user/reset_password`, data),
|
||||
// role
|
||||
getRoleList: (params = {}) => request.get('/role/list', { params }),
|
||||
createRole: (data = {}) => request.post('/role/create', data),
|
||||
updateRole: (data = {}) => request.post('/role/update', data),
|
||||
deleteRole: (params = {}) => request.delete('/role/delete', { params }),
|
||||
updateRoleAuthorized: (data = {}) => request.post('/role/authorized', data),
|
||||
getRoleAuthorized: (params = {}) => request.get('/role/authorized', { params }),
|
||||
// menus
|
||||
getMenus: (params = {}) => request.get('/menu/list', { params }),
|
||||
createMenu: (data = {}) => request.post('/menu/create', data),
|
||||
updateMenu: (data = {}) => request.post('/menu/update', data),
|
||||
deleteMenu: (params = {}) => request.delete('/menu/delete', { params }),
|
||||
// apis
|
||||
getApis: (params = {}) => request.get('/api/list', { params }),
|
||||
createApi: (data = {}) => request.post('/api/create', data),
|
||||
updateApi: (data = {}) => request.post('/api/update', data),
|
||||
deleteApi: (params = {}) => request.delete('/api/delete', { params }),
|
||||
refreshApi: (data = {}) => request.post('/api/refresh', data),
|
||||
// depts
|
||||
getDepts: (params = {}) => request.get('/dept/list', { params }),
|
||||
createDept: (data = {}) => request.post('/dept/create', data),
|
||||
updateDept: (data = {}) => request.post('/dept/update', data),
|
||||
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 }),
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 642 B After Width: | Height: | Size: 642 B |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 472 B After Width: | Height: | Size: 472 B |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.7 MiB |
BIN
web1/src/assets/images/login_bg.webp
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 662 B After Width: | Height: | Size: 662 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
230
web1/src/assets/js/icons.js
Normal file
@ -0,0 +1,230 @@
|
||||
export default [
|
||||
'mdi-air-humidifier-off',
|
||||
'mdi-chili-off',
|
||||
'mdi-cigar-off',
|
||||
'mdi-clock-time-eight',
|
||||
'mdi-clock-time-eight-outline',
|
||||
'mdi-clock-time-eleven',
|
||||
'mdi-clock-time-eleven-outline',
|
||||
'mdi-clock-time-five',
|
||||
'mdi-clock-time-five-outline',
|
||||
'mdi-clock-time-four',
|
||||
'mdi-clock-time-four-outline',
|
||||
'mdi-clock-time-nine',
|
||||
'mdi-clock-time-nine-outline',
|
||||
'mdi-clock-time-one',
|
||||
'mdi-clock-time-one-outline',
|
||||
'mdi-clock-time-seven',
|
||||
'mdi-clock-time-seven-outline',
|
||||
'mdi-clock-time-six',
|
||||
'mdi-clock-time-six-outline',
|
||||
'mdi-clock-time-ten',
|
||||
'mdi-clock-time-ten-outline',
|
||||
'mdi-clock-time-three',
|
||||
'mdi-clock-time-three-outline',
|
||||
'mdi-clock-time-twelve',
|
||||
'mdi-clock-time-twelve-outline',
|
||||
'mdi-clock-time-two',
|
||||
'mdi-clock-time-two-outline',
|
||||
'mdi-cog-refresh',
|
||||
'mdi-cog-refresh-outline',
|
||||
'mdi-cog-sync',
|
||||
'mdi-cog-sync-outline',
|
||||
'mdi-content-save-cog',
|
||||
'mdi-content-save-cog-outline',
|
||||
'mdi-cosine-wave',
|
||||
'mdi-cube-off',
|
||||
'mdi-cube-off-outline',
|
||||
'mdi-dome-light',
|
||||
'mdi-download-box',
|
||||
'mdi-download-box-outline',
|
||||
'mdi-download-circle',
|
||||
'mdi-download-circle-outline',
|
||||
'mdi-fan-alert',
|
||||
'mdi-fan-chevron-down',
|
||||
'mdi-fan-chevron-up',
|
||||
'mdi-fan-minus',
|
||||
'mdi-fan-plus',
|
||||
'mdi-fan-remove',
|
||||
'mdi-fan-speed-1',
|
||||
'mdi-fan-speed-2',
|
||||
'mdi-fan-speed-3',
|
||||
'mdi-food-drumstick',
|
||||
'mdi-food-drumstick-off',
|
||||
'mdi-food-drumstick-off-outline',
|
||||
'mdi-food-drumstick-outline',
|
||||
'mdi-food-steak',
|
||||
'mdi-food-steak-off',
|
||||
'mdi-fuse-alert',
|
||||
'mdi-fuse-off',
|
||||
'mdi-heart-minus',
|
||||
'mdi-heart-minus-outline',
|
||||
'mdi-heart-off-outline',
|
||||
'mdi-heart-plus',
|
||||
'mdi-heart-plus-outline',
|
||||
'mdi-heart-remove',
|
||||
'mdi-heart-remove-outline',
|
||||
'mdi-hours-24',
|
||||
'mdi-incognito-circle',
|
||||
'mdi-incognito-circle-off',
|
||||
'mdi-lingerie',
|
||||
'mdi-microwave-off',
|
||||
'mdi-minus-circle-off',
|
||||
'mdi-minus-circle-off-outline',
|
||||
'mdi-motion-sensor-off',
|
||||
'mdi-pail-minus',
|
||||
'mdi-pail-minus-outline',
|
||||
'mdi-pail-off',
|
||||
'mdi-pail-off-outline',
|
||||
'mdi-pail-outline',
|
||||
'mdi-pail-plus',
|
||||
'mdi-pail-plus-outline',
|
||||
'mdi-pail-remove',
|
||||
'mdi-pail-remove-outline',
|
||||
'mdi-pine-tree-fire',
|
||||
'mdi-power-plug-off-outline',
|
||||
'mdi-power-plug-outline',
|
||||
'mdi-printer-eye',
|
||||
'mdi-printer-search',
|
||||
'mdi-puzzle-check',
|
||||
'mdi-puzzle-check-outline',
|
||||
'mdi-rug',
|
||||
'mdi-sawtooth-wave',
|
||||
'mdi-set-square',
|
||||
'mdi-smoking-pipe-off',
|
||||
'mdi-spoon-sugar',
|
||||
'mdi-square-wave',
|
||||
'mdi-table-split-cell',
|
||||
'mdi-ticket-percent-outline',
|
||||
'mdi-triangle-wave',
|
||||
'mdi-waveform',
|
||||
'mdi-wizard-hat',
|
||||
'mdi-ab-testing',
|
||||
'mdi-abjad-arabic',
|
||||
'mdi-abjad-hebrew',
|
||||
'mdi-abugida-devanagari',
|
||||
'mdi-abugida-thai',
|
||||
'mdi-access-point',
|
||||
'mdi-access-point-network',
|
||||
'mdi-access-point-network-off',
|
||||
'mdi-account',
|
||||
'mdi-account-alert',
|
||||
'mdi-account-alert-outline',
|
||||
'mdi-account-arrow-left',
|
||||
'mdi-account-arrow-left-outline',
|
||||
'mdi-account-arrow-right',
|
||||
'mdi-account-arrow-right-outline',
|
||||
'mdi-account-box',
|
||||
'mdi-account-box-multiple',
|
||||
'mdi-account-box-multiple-outline',
|
||||
'mdi-account-box-outline',
|
||||
'mdi-account-cancel',
|
||||
'mdi-account-cancel-outline',
|
||||
'mdi-account-cash',
|
||||
'mdi-account-cash-outline',
|
||||
'mdi-account-check',
|
||||
'mdi-account-check-outline',
|
||||
'mdi-account-child',
|
||||
'mdi-account-child-circle',
|
||||
'mdi-account-child-outline',
|
||||
'mdi-account-circle',
|
||||
'mdi-account-circle-outline',
|
||||
'mdi-account-clock',
|
||||
'mdi-account-clock-outline',
|
||||
'mdi-account-cog',
|
||||
'mdi-account-cog-outline',
|
||||
'mdi-account-convert',
|
||||
'mdi-account-convert-outline',
|
||||
'mdi-account-cowboy-hat',
|
||||
'mdi-account-details',
|
||||
'mdi-account-details-outline',
|
||||
'mdi-account-edit',
|
||||
'mdi-account-edit-outline',
|
||||
'mdi-account-group',
|
||||
'mdi-account-group-outline',
|
||||
'mdi-account-hard-hat',
|
||||
'mdi-account-heart',
|
||||
'mdi-account-heart-outline',
|
||||
'mdi-account-key',
|
||||
'mdi-account-key-outline',
|
||||
'mdi-account-lock',
|
||||
'mdi-account-lock-outline',
|
||||
'mdi-account-minus',
|
||||
'mdi-account-minus-outline',
|
||||
'mdi-account-multiple',
|
||||
'mdi-account-multiple-check',
|
||||
'mdi-account-multiple-check-outline',
|
||||
'mdi-account-multiple-minus',
|
||||
'mdi-account-multiple-minus-outline',
|
||||
'mdi-account-multiple-outline',
|
||||
'mdi-account-multiple-plus',
|
||||
'mdi-account-multiple-plus-outline',
|
||||
'mdi-account-multiple-remove',
|
||||
'mdi-account-multiple-remove-outline',
|
||||
'mdi-account-music',
|
||||
'mdi-account-music-outline',
|
||||
'mdi-account-network',
|
||||
'mdi-account-network-outline',
|
||||
'mdi-account-off',
|
||||
'mdi-account-off-outline',
|
||||
'mdi-account-outline',
|
||||
'mdi-account-plus',
|
||||
'mdi-account-plus-outline',
|
||||
'mdi-account-question',
|
||||
'mdi-account-question-outline',
|
||||
'mdi-account-remove',
|
||||
'mdi-account-remove-outline',
|
||||
'mdi-account-search',
|
||||
'mdi-account-search-outline',
|
||||
'mdi-account-settings',
|
||||
'mdi-account-settings-outline',
|
||||
'mdi-account-star',
|
||||
'mdi-account-star-outline',
|
||||
'mdi-account-supervisor',
|
||||
'mdi-account-supervisor-circle',
|
||||
'mdi-account-supervisor-outline',
|
||||
'mdi-account-switch',
|
||||
'mdi-account-switch-outline',
|
||||
'mdi-account-tie',
|
||||
'mdi-account-tie-outline',
|
||||
'mdi-account-tie-voice',
|
||||
'mdi-account-tie-voice-off',
|
||||
'mdi-account-tie-voice-off-outline',
|
||||
'mdi-account-tie-voice-outline',
|
||||
'mdi-account-voice',
|
||||
'mdi-adjust',
|
||||
'mdi-adobe',
|
||||
'mdi-adobe-acrobat',
|
||||
'mdi-air-conditioner',
|
||||
'mdi-air-filter',
|
||||
'mdi-air-horn',
|
||||
'mdi-air-humidifier',
|
||||
'mdi-air-purifier',
|
||||
'mdi-airbag',
|
||||
'mdi-airballoon',
|
||||
'mdi-airballoon-outline',
|
||||
'mdi-airplane',
|
||||
'mdi-airplane-landing',
|
||||
'mdi-airplane-off',
|
||||
'mdi-airplane-takeoff',
|
||||
'mdi-airport',
|
||||
'mdi-alarm',
|
||||
'mdi-alarm-bell',
|
||||
'mdi-alarm-check',
|
||||
'mdi-alarm-light',
|
||||
'mdi-alarm-light-outline',
|
||||
'mdi-alarm-multiple',
|
||||
'mdi-alarm-note',
|
||||
'mdi-alarm-note-off',
|
||||
'mdi-alarm-off',
|
||||
'mdi-alarm-plus',
|
||||
'mdi-alarm-snooze',
|
||||
'mdi-album',
|
||||
'mdi-alert',
|
||||
'mdi-alert-box',
|
||||
'mdi-alert-box-outline',
|
||||
'mdi-alert-circle',
|
||||
'mdi-alert-circle-check',
|
||||
'mdi-alert-circle-check-outline',
|
||||
'mdi-alert-circle-outline',
|
||||
]
|
||||
1
web1/src/assets/svg/forbidden.svg
Normal file
|
After Width: | Height: | Size: 60 KiB |
1
web1/src/assets/svg/front-page.svg
Normal file
|
After Width: | Height: | Size: 101 KiB |
1
web1/src/assets/svg/logo.svg
Normal file
|
After Width: | Height: | Size: 10 KiB |
1
web1/src/assets/svg/network-error.svg
Normal file
|
After Width: | Height: | Size: 86 KiB |
1
web1/src/assets/svg/no-data.svg
Normal file
|
After Width: | Height: | Size: 92 KiB |
1
web1/src/assets/svg/not-found.svg
Normal file
|
After Width: | Height: | Size: 71 KiB |
1
web1/src/assets/svg/server-error.svg
Normal file
|
After Width: | Height: | Size: 87 KiB |
1
web1/src/assets/svg/service-unavailable.svg
Normal file
|
After Width: | Height: | Size: 91 KiB |
1
web1/src/assets/svg/unauthorized.svg
Normal file
|
After Width: | Height: | Size: 60 KiB |
3
web1/src/components/common/AppFooter.vue
Normal file
@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<footer f-c-c flex-col text-14 color="#6a6a6a"></footer>
|
||||
</template>
|
||||
67
web1/src/components/common/AppProvider.vue
Normal file
@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<n-config-provider
|
||||
wh-full
|
||||
:locale="zhCN"
|
||||
:date-locale="dateZhCN"
|
||||
:theme="appStore.isDark ? darkTheme : undefined"
|
||||
:theme-overrides="naiveThemeOverrides"
|
||||
>
|
||||
<n-loading-bar-provider>
|
||||
<n-dialog-provider>
|
||||
<n-notification-provider>
|
||||
<n-message-provider>
|
||||
<slot></slot>
|
||||
<NaiveProviderContent />
|
||||
</n-message-provider>
|
||||
</n-notification-provider>
|
||||
</n-dialog-provider>
|
||||
</n-loading-bar-provider>
|
||||
</n-config-provider>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineComponent, h } from 'vue'
|
||||
import {
|
||||
zhCN,
|
||||
dateZhCN,
|
||||
darkTheme,
|
||||
useLoadingBar,
|
||||
useDialog,
|
||||
useMessage,
|
||||
useNotification,
|
||||
} from 'naive-ui'
|
||||
import { useCssVar } from '@vueuse/core'
|
||||
import { kebabCase } from 'lodash-es'
|
||||
import { setupMessage, setupDialog } from '@/utils'
|
||||
import { naiveThemeOverrides } from '~/settings'
|
||||
import { useAppStore } from '@/store'
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
||||
function setupCssVar() {
|
||||
const common = naiveThemeOverrides.common
|
||||
for (const key in common) {
|
||||
useCssVar(`--${kebabCase(key)}`, document.documentElement).value = common[key] || ''
|
||||
if (key === 'primaryColor') window.localStorage.setItem('__THEME_COLOR__', common[key] || '')
|
||||
}
|
||||
}
|
||||
|
||||
// 挂载naive组件的方法至window, 以便在全局使用
|
||||
function setupNaiveTools() {
|
||||
window.$loadingBar = useLoadingBar()
|
||||
window.$notification = useNotification()
|
||||
|
||||
window.$message = setupMessage(useMessage())
|
||||
window.$dialog = setupDialog(useDialog())
|
||||
}
|
||||
|
||||
const NaiveProviderContent = defineComponent({
|
||||
setup() {
|
||||
setupCssVar()
|
||||
setupNaiveTools()
|
||||
},
|
||||
render() {
|
||||
return h('div')
|
||||
},
|
||||
})
|
||||
</script>
|
||||
82
web1/src/components/common/LoadingEmptyWrapper.vue
Normal file
@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<div v-if="reloadFlag" class="relative">
|
||||
<slot></slot>
|
||||
<div v-show="showPlaceholder" class="absolute-lt h-full w-full" :class="placeholderClass">
|
||||
<div v-show="loading" class="absolute-center">
|
||||
<n-spin :show="true" :size="loadingSize" />
|
||||
</div>
|
||||
<div v-show="isEmpty" class="absolute-center">
|
||||
<div class="relative">
|
||||
<icon-custom-no-data :class="iconClass" />
|
||||
<p class="absolute-lb w-full text-center" :class="descClass">{{ emptyDesc }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="!network" class="absolute-center">
|
||||
<div
|
||||
class="relative"
|
||||
:class="{ 'cursor-pointer': showNetworkReload }"
|
||||
@click="handleReload"
|
||||
>
|
||||
<icon-custom-network-error :class="iconClass" />
|
||||
<p class="absolute-lb w-full text-center" :class="descClass">{{ networkErrorDesc }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, nextTick, watch, onUnmounted } from 'vue'
|
||||
|
||||
defineOptions({ name: 'LoadingEmptyWrapper' })
|
||||
|
||||
const NETWORK_ERROR_MSG = '网络似乎开了小差~'
|
||||
|
||||
const props = {
|
||||
loading: false,
|
||||
empty: false,
|
||||
loadingSize: 'medium',
|
||||
placeholderClass: 'bg-white dark:bg-dark transition-background-color duration-300 ease-in-out',
|
||||
emptyDesc: '暂无数据',
|
||||
iconClass: 'text-320px text-primary',
|
||||
descClass: 'text-16px text-#666',
|
||||
showNetworkReload: false,
|
||||
}
|
||||
|
||||
// 网络状态
|
||||
const network = ref(window.navigator.onLine)
|
||||
const reloadFlag = ref(true)
|
||||
|
||||
// 数据是否为空
|
||||
const isEmpty = computed(() => props.empty && !props.loading && network.value)
|
||||
|
||||
const showPlaceholder = computed(() => props.loading || isEmpty.value || !network.value)
|
||||
|
||||
const networkErrorDesc = computed(() =>
|
||||
props.showNetworkReload ? `${NETWORK_ERROR_MSG}, 点击重试` : NETWORK_ERROR_MSG,
|
||||
)
|
||||
|
||||
function handleReload() {
|
||||
if (!props.showNetworkReload) return
|
||||
reloadFlag.value = false
|
||||
nextTick(() => {
|
||||
reloadFlag.value = true
|
||||
})
|
||||
}
|
||||
|
||||
const stopHandle = watch(
|
||||
() => props.loading,
|
||||
(newValue) => {
|
||||
// 结束加载判断一下网络状态
|
||||
if (!newValue) {
|
||||
network.value = window.navigator.onLine
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
onUnmounted(() => {
|
||||
stopHandle()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
160
web1/src/components/common/ScrollX.vue
Normal file
@ -0,0 +1,160 @@
|
||||
<template>
|
||||
<div ref="wrapper" class="wrapper" @mousewheel.prevent="handleMouseWheel">
|
||||
<template v-if="showArrow && isOverflow">
|
||||
<div class="left" @click="handleMouseWheel({ wheelDelta: 120 })">
|
||||
<icon-ic:baseline-keyboard-arrow-left />
|
||||
</div>
|
||||
<div class="right" @click="handleMouseWheel({ wheelDelta: -120 })">
|
||||
<icon-ic:baseline-keyboard-arrow-right />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div
|
||||
ref="content"
|
||||
v-resize="refreshIsOverflow"
|
||||
class="content"
|
||||
:class="{ overflow: isOverflow && showArrow }"
|
||||
:style="{
|
||||
transform: `translateX(${translateX}px)`,
|
||||
}"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { debounce, useResize } from '@/utils'
|
||||
|
||||
defineProps({
|
||||
showArrow: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
})
|
||||
|
||||
const translateX = ref(0)
|
||||
const content = ref(null)
|
||||
const wrapper = ref(null)
|
||||
const isOverflow = ref(false)
|
||||
|
||||
const refreshIsOverflow = debounce(() => {
|
||||
const wrapperWidth = wrapper.value?.offsetWidth
|
||||
const contentWidth = content.value?.offsetWidth
|
||||
isOverflow.value = contentWidth > wrapperWidth
|
||||
resetTranslateX(wrapperWidth, contentWidth)
|
||||
}, 200)
|
||||
|
||||
function handleMouseWheel(e) {
|
||||
const { wheelDelta } = e
|
||||
const wrapperWidth = wrapper.value?.offsetWidth
|
||||
const contentWidth = content.value?.offsetWidth
|
||||
/**
|
||||
* @wheelDelta 平行滚动的值 >0: 右移 <0: 左移
|
||||
* @translateX 内容translateX的值
|
||||
* @wrapperWidth 容器的宽度
|
||||
* @contentWidth 内容的宽度
|
||||
*/
|
||||
if (wheelDelta < 0) {
|
||||
if (wrapperWidth > contentWidth && translateX.value < -10) return
|
||||
if (wrapperWidth <= contentWidth && contentWidth + translateX.value - wrapperWidth < -10) return
|
||||
}
|
||||
if (wheelDelta > 0 && translateX.value > 10) {
|
||||
return
|
||||
}
|
||||
|
||||
translateX.value += wheelDelta
|
||||
resetTranslateX(wrapperWidth, contentWidth)
|
||||
}
|
||||
|
||||
const resetTranslateX = debounce(function (wrapperWidth, contentWidth) {
|
||||
if (!isOverflow.value) {
|
||||
translateX.value = 0
|
||||
} else if (-translateX.value > contentWidth - wrapperWidth) {
|
||||
translateX.value = wrapperWidth - contentWidth
|
||||
} else if (translateX.value > 0) {
|
||||
translateX.value = 0
|
||||
}
|
||||
}, 200)
|
||||
|
||||
const observer = ref(null)
|
||||
onMounted(() => {
|
||||
refreshIsOverflow()
|
||||
|
||||
observer.value = useResize(document.body, refreshIsOverflow)
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
observer.value?.disconnect()
|
||||
})
|
||||
|
||||
function handleScroll(x, width) {
|
||||
const wrapperWidth = wrapper.value?.offsetWidth
|
||||
const contentWidth = content.value?.offsetWidth
|
||||
if (contentWidth <= wrapperWidth) return
|
||||
|
||||
// 当 x 小于可视范围的最小值时
|
||||
if (x < -translateX.value + 150) {
|
||||
translateX.value = -(x - 150)
|
||||
resetTranslateX(wrapperWidth, contentWidth)
|
||||
}
|
||||
|
||||
// 当 x 大于可视范围的最大值时
|
||||
if (x + width > -translateX.value + wrapperWidth) {
|
||||
translateX.value = wrapperWidth - (x + width)
|
||||
resetTranslateX(wrapperWidth, contentWidth)
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
handleScroll,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.wrapper {
|
||||
display: flex;
|
||||
background-color: #fff;
|
||||
|
||||
z-index: 9;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
.content {
|
||||
padding: 0 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
transition: transform 0.5s;
|
||||
&.overflow {
|
||||
padding-left: 30px;
|
||||
padding-right: 30px;
|
||||
}
|
||||
}
|
||||
.left,
|
||||
.right {
|
||||
background-color: #fff;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
|
||||
width: 20px;
|
||||
height: 35px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
font-size: 18px;
|
||||
border: 1px solid #e0e0e6;
|
||||
border-radius: 2px;
|
||||
|
||||
z-index: 2;
|
||||
cursor: pointer;
|
||||
}
|
||||
.left {
|
||||
left: 0;
|
||||
}
|
||||
.right {
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
22
web1/src/components/icon/CustomIcon.vue
Normal file
@ -0,0 +1,22 @@
|
||||
<script setup>
|
||||
/** 自定义图标 */
|
||||
const props = defineProps({
|
||||
/** 图标名称(assets/svg下的文件名) */
|
||||
icon: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
size: {
|
||||
type: Number,
|
||||
default: 14,
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TheIcon type="custom" v-bind="props" />
|
||||
</template>
|
||||
70
web1/src/components/icon/IconPicker.vue
Normal file
@ -0,0 +1,70 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { watchDebounced } from '@vueuse/core'
|
||||
import { NInput, NPopover } from 'naive-ui'
|
||||
|
||||
import TheIcon from './TheIcon.vue'
|
||||
import iconData from '@/assets/js/icons'
|
||||
|
||||
const props = defineProps({ value: String })
|
||||
const emit = defineEmits(['update:value'])
|
||||
|
||||
const choosed = ref(props.value) // 选中值
|
||||
const icons = ref(iconData)
|
||||
// const icons = ref(iconData.filter((icon) => icon.includes(choosed.value))) // 可选图标列表
|
||||
|
||||
function filterIcons() {
|
||||
icons.value = iconData.filter((item) => item.includes(choosed.value))
|
||||
}
|
||||
|
||||
function selectIcon(icon) {
|
||||
choosed.value = icon
|
||||
emit('update:value', choosed.value)
|
||||
}
|
||||
|
||||
watchDebounced(
|
||||
choosed,
|
||||
() => {
|
||||
filterIcons()
|
||||
emit('update:value', choosed.value)
|
||||
},
|
||||
{ debounce: 200 },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full">
|
||||
<NPopover trigger="click" placement="bottom-start">
|
||||
<template #trigger>
|
||||
<NInput v-model:value="choosed" placeholder="请输入图标名称" @update:value="filterIcons">
|
||||
<template #prefix>
|
||||
<span class="i-mdi:magnify text-18" />
|
||||
</template>
|
||||
<template #suffix>
|
||||
<TheIcon :icon="choosed" :size="18" />
|
||||
</template>
|
||||
</NInput>
|
||||
</template>
|
||||
<template #footer>
|
||||
更多图标去
|
||||
<a class="text-blue" target="_blank" href="https://icones.js.org/collection/all">
|
||||
Icones
|
||||
</a>
|
||||
查看
|
||||
</template>
|
||||
<ul v-if="icons.length" class="h-150 w-300 overflow-y-scroll">
|
||||
<li
|
||||
v-for="(icon, index) in icons"
|
||||
:key="index"
|
||||
class="mx-5 inline-block cursor-pointer hover:text-cyan"
|
||||
@click="selectIcon(icon)"
|
||||
>
|
||||
<TheIcon :icon="icon" :size="18" />
|
||||
</li>
|
||||
</ul>
|
||||
<div v-else>
|
||||
<TheIcon :icon="choosed" :size="18" />
|
||||
</div>
|
||||
</NPopover>
|
||||
</div>
|
||||
</template>
|
||||
24
web1/src/components/icon/SvgIcon.vue
Normal file
@ -0,0 +1,24 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
icon: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
prefix: {
|
||||
type: String,
|
||||
default: 'icon-custom',
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: 'currentColor',
|
||||
},
|
||||
})
|
||||
|
||||
const symbolId = computed(() => `#${props.prefix}-${props.icon}`)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg aria-hidden="true" width="1em" height="1em">
|
||||
<use :xlink:href="symbolId" :fill="color" />
|
||||
</svg>
|
||||
</template>
|
||||
22
web1/src/components/icon/TheIcon.vue
Normal file
@ -0,0 +1,22 @@
|
||||
<script setup>
|
||||
import { renderIcon } from '@/utils'
|
||||
|
||||
defineProps({
|
||||
icon: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
size: {
|
||||
type: Number,
|
||||
default: 14,
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component :is="renderIcon(icon, { size, color })" />
|
||||
</template>
|
||||
18
web1/src/components/page/AppPage.vue
Normal file
@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<transition name="fade-slide" mode="out-in" appear>
|
||||
<section class="cus-scroll-y wh-full flex-col bg-[#f5f6fb] p-15 dark:bg-hex-121212">
|
||||
<slot />
|
||||
<AppFooter v-if="showFooter" mt-15 />
|
||||
<n-back-top :bottom="20" />
|
||||
</section>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
showFooter: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
33
web1/src/components/page/CommonPage.vue
Normal file
@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<AppPage :show-footer="showFooter">
|
||||
<header v-if="showHeader" mb-15 min-h-45 flex items-center justify-between px-15>
|
||||
<slot v-if="$slots.header" name="header" />
|
||||
<template v-else>
|
||||
<h2 text-22 font-normal text-hex-333 dark:text-hex-ccc>{{ title || route.meta?.title }}</h2>
|
||||
<slot name="action" />
|
||||
</template>
|
||||
</header>
|
||||
|
||||
<n-card flex-1 rounded-10>
|
||||
<slot />
|
||||
</n-card>
|
||||
</AppPage>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
showFooter: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showHeader: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
})
|
||||
const route = useRoute()
|
||||
</script>
|
||||
26
web1/src/components/query-bar/QueryBar.vue
Normal file
@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<div
|
||||
bg="#fafafc"
|
||||
min-h-60
|
||||
flex
|
||||
items-start
|
||||
justify-between
|
||||
b-1
|
||||
rounded-8
|
||||
p-15
|
||||
bc-ccc
|
||||
dark:bg-black
|
||||
>
|
||||
<n-space wrap :size="[35, 15]">
|
||||
<slot />
|
||||
<div>
|
||||
<n-button secondary type="primary" @click="emit('reset')">重置</n-button>
|
||||
<n-button ml-20 type="primary" @click="emit('search')">搜索</n-button>
|
||||
</div>
|
||||
</n-space>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const emit = defineEmits(['search', 'reset'])
|
||||
</script>
|
||||
34
web1/src/components/query-bar/QueryBarItem.vue
Normal file
@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<div flex items-center>
|
||||
<label
|
||||
v-if="!isNullOrWhitespace(label)"
|
||||
w-80
|
||||
flex-shrink-0
|
||||
:style="{ width: labelWidth + 'px' }"
|
||||
>
|
||||
{{ label }}
|
||||
</label>
|
||||
<div>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { isNullOrWhitespace } from '@/utils'
|
||||
|
||||
defineProps({
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
labelWidth: {
|
||||
type: Number,
|
||||
default: 80,
|
||||
},
|
||||
contentWidth: {
|
||||
type: Number,
|
||||
default: 220,
|
||||
},
|
||||
})
|
||||
</script>
|
||||