This commit is contained in:
yuhawu 2025-08-06 10:19:01 +08:00
parent fe89d930f1
commit cec79556cc
11 changed files with 2212 additions and 0 deletions

View File

@ -0,0 +1,73 @@
import request from '@/utils/axios'
/**
* 获取所有省级数据
*/
export function getProvinces() {
return request({
url: '/district/provinces',
method: 'get'
})
}
/**
* 根据省份ID获取城市列表
*/
export function getCitiesByProvinceId(provinceId) {
return request({
url: `/district/cities/${provinceId}`,
method: 'get'
})
}
/**
* 根据城市ID获取区县列表
*/
export function getDistrictsByCityId(cityId) {
return request({
url: `/district/districts/${cityId}`,
method: 'get'
})
}
/**
* 获取省级市级树形结构
*/
export function getDistrictTree() {
return request({
url: '/district/getDistrictTree',
method: 'get'
})
}
/**
* 新增物流模板
*/
export function createTemplate(data) {
return request({
url: '/logistics/logistics',
method: 'post',
data: data
})
}
/**
* 获取运费信息
*/
export function getFreInfo(id) {
return request({
url: '/logistics/logistics/' + id,
method: 'get'
})
}
/**
* 更新物流模板
*/
export function UpdateTemplate(data) {
return request({
url: '/logistics/logistics',
method: 'put',
data: data
})
}

View File

@ -0,0 +1,65 @@
import request from '@/utils/axios'
/**
* 查询物流管理列表
*/
export function listLogistics(query) {
return request({
url: '/logistics/list',
method: 'get',
params: query
})
}
/**
* 查询物流管理详细
*/
export function getLogistics(id) {
return request({
url: '/logistics/' + id,
method: 'get'
})
}
/**
* 新增物流管理
*/
export function addLogistics(data) {
return request({
url: '/logistics',
method: 'post',
data: data
})
}
/**
* 修改物流管理
*/
export function updateLogistics(data) {
return request({
url: '/logistics',
method: 'put',
data: data
})
}
/**
* 删除物流管理
*/
export function delLogistics(id) {
return request({
url: '/logistics/' + id,
method: 'delete'
})
}
/**
* 导出物流管理
*/
export function exportLogistics(query) {
return request({
url: '/logistics/export',
method: 'get',
params: query
})
}

View File

@ -0,0 +1,64 @@
import request from '@/utils/axios'
/**
* 获取货区名称列表
*/
export function depotNameList() {
return request({
url: '/depot/nameList',
method: 'get'
})
}
/**
* 查询货架列表
*/
export function listShelves(query) {
return request({
url: '/shelves/list',
method: 'get',
params: query
})
}
/**
* 查询货架详细
*/
export function getShelves(id) {
return request({
url: '/shelves/' + id,
method: 'get'
})
}
/**
* 新增货架
*/
export function addShelves(data) {
return request({
url: '/shelves',
method: 'post',
data: data
})
}
/**
* 修改货架
*/
export function updateShelves(data) {
return request({
url: '/shelves',
method: 'put',
data: data
})
}
/**
* 删除货架
*/
export function delShelves(id) {
return request({
url: '/shelves/' + id,
method: 'delete'
})
}

View File

@ -0,0 +1,16 @@
import instance from '../../utils/axios.js'
// 用户登录相关API
const userLoginApi = {
// 用户登录 - 直接发送FormData
userLogin: (data) => {
return instance.post('/userLogin/login', data);
},
// 获取用户信息
getUserInfo: (accessToken) => instance.get('/userLogin/getUserInfo', {
params: { accessToken }
})
};
// 导出模块
export { userLoginApi };

19
src/config/api.js Normal file
View File

@ -0,0 +1,19 @@
// API配置
const config = {
// 从环境变量获取API基础URL
baseURL: import.meta.env.VITE_API_BASE_URL || '',
timeout: 10000
}
// 获取当前环境配置
export const getApiConfig = () => {
return config
}
// 获取完整的API URL
export const getApiUrl = (path) => {
const { baseURL } = getApiConfig()
return `${baseURL}${path}`
}
export default config

View File

@ -0,0 +1,44 @@
import { hasPermission, hasAnyPermission } from '@/utils/permission'
/**
* 权限指令
* v-permission="'system:user:add'" 单个权限
* v-permission="['system:user:add', 'system:user:update']" 多个权限任意一个
*/
export const permission = {
mounted(el, binding) {
const { value } = binding
if (value) {
let hasAuth = false
if (Array.isArray(value)) {
hasAuth = hasAnyPermission(value)
} else {
hasAuth = hasPermission(value)
}
if (!hasAuth) {
el.parentNode && el.parentNode.removeChild(el)
}
}
}
}
/**
* 权限指令所有权限都需要
* v-permission-all="['system:user:add', 'system:user:update']"
*/
export const permissionAll = {
mounted(el, binding) {
const { value } = binding
if (value && Array.isArray(value)) {
const hasAuth = value.every(code => hasPermission(code))
if (!hasAuth) {
el.parentNode && el.parentNode.removeChild(el)
}
}
}
}

View File

@ -0,0 +1,198 @@
<template>
<el-menu router :default-active="$route.path" :collapse="false" unique-opened background-color="#304156"
text-color="#bfcbd9" active-text-color="#409EFF">
<template v-for="item in filteredMenuTree" :key="item.id">
<!-- 有子菜单的情况 -->
<el-sub-menu v-if="item.children && item.children.length > 0" :index="item.path || item.id.toString()">
<template #title>
<el-icon v-if="item.icon">
<component :is="getIcon(item.icon)" />
</el-icon>
<span>{{ item.name }}</span>
</template>
<el-menu-item v-for="child in item.children" :key="child.id" :index="child.path">
{{ child.name }}
</el-menu-item>
</el-sub-menu>
<!-- 单个菜单项 -->
<el-menu-item v-else-if="item.path" :index="item.path">
<el-icon v-if="item.icon">
<component :is="getIcon(item.icon)" />
</el-icon>
<span>{{ item.name }}</span>
</el-menu-item>
</template>
</el-menu>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, watch } from 'vue'
import {
Setting, Shop, Notebook, Monitor, Document as DocIcon,
User, Message, ShoppingCart, Connection, Box,
TrendCharts, HomeFilled
} from '@element-plus/icons-vue'
import { getUserMenuTree } from '@/api/permission'
import { hasPermission, getUserPermissions } from '@/utils/permission'
const userMenuTree = ref([])
//
const iconMap = {
'Setting': Setting,
'Shop': Shop,
'Notebook': Notebook,
'Monitor': Monitor,
'Document': DocIcon,
'User': User,
'Message': Message,
'ShoppingCart': ShoppingCart,
'Connection': Connection,
'Box': Box,
'TrendCharts': TrendCharts,
'HomeFilled': HomeFilled
}
const getIcon = (iconName: string) => {
return iconMap[iconName] || Setting
}
//
const filteredMenuTree = computed(() => {
return filterMenuByPermission(userMenuTree.value)
})
const filterMenuByPermission = (menuItems: any[]): any[] => {
if (!menuItems || menuItems.length === 0) return []
return menuItems.filter(item => {
//
if (item.children && item.children.length > 0) {
const filteredChildren = filterMenuByPermission(item.children)
// type=1
if (item.type === 1) {
if (filteredChildren.length > 0) {
item.children = filteredChildren
return true
}
return false
}
// type=2
if (item.type === 2) {
if (item.code && !hasPermission(item.code)) {
return false
}
item.children = filteredChildren
return true
}
item.children = filteredChildren
return filteredChildren.length > 0
}
//
//
if (item.type === 1) {
return false
}
// type=2
if (item.type === 2) {
//
if (item.code) {
return hasPermission(item.code)
}
//
return true
}
//
return false
})
}
const loadUserMenu = async () => {
try {
//
const permissions = getUserPermissions()
if (permissions.length === 0) {
console.log('权限数据未加载,等待权限初始化...')
//
setTimeout(loadUserMenu, 500)
return
}
const res = await getUserMenuTree()
if (res.code === 200) {
userMenuTree.value = res.data || []
console.log('用户菜单树:', userMenuTree.value)
console.log('用户权限列表:', permissions)
console.log('过滤后的菜单:', filteredMenuTree.value)
}
} catch (error) {
console.error('获取用户菜单失败:', error)
}
}
//
watch(() => getUserPermissions(), (newPermissions, oldPermissions) => {
console.log('权限变化检测:', { old: oldPermissions?.length || 0, new: newPermissions?.length || 0 })
if (newPermissions.length > 0 && (oldPermissions?.length || 0) === 0) {
console.log('权限首次加载,重新获取菜单')
loadUserMenu()
}
}, { deep: true })
onMounted(() => {
loadUserMenu()
})
</script>
<style scoped>
.el-menu {
height: 100%;
border-right: none;
}
.el-menu-item.is-active {
background-color: #263445 !important;
}
:deep(.el-menu) {
border-right: none;
}
:deep(.el-sub-menu.is-opened) {
>.el-sub-menu__title,
.el-menu-item {
background-color: #1a1a1a !important;
}
}
:deep(.el-menu-item):hover,
:deep(.el-sub-menu__title):hover {
background-color: #1a1a1a !important;
}
:deep(.el-menu-item.is-active) {
background-color: #1a1a1a !important;
color: var(--el-menu-active-color);
}
:deep(.el-menu-item.is-active + .el-sub-menu .el-sub-menu__title),
:deep(.el-menu-item.is-active)~.el-sub-menu .el-sub-menu__title {
background-color: #1a1a1a !important;
}
:deep(.el-sub-menu) {
&.is-active {
>.el-sub-menu__title {
background-color: #1a1a1a !important;
}
}
}
</style>

48
src/utils/permission.js Normal file
View File

@ -0,0 +1,48 @@
import { getUserPermissionCodes } from '@/api/permission'
let userPermissions = []
/**
* 初始化用户权限
*/
export async function initUserPermissions() {
try {
const res = await getUserPermissionCodes()
if (res.code === 200) {
userPermissions = res.data.filter(code => code && code.trim() !== '')
}
} catch (error) {
console.error('获取用户权限失败:', error)
}
}
/**
* 检查用户是否有指定权限
*/
export function hasPermission(permissionCode) {
if (!permissionCode) return true
return userPermissions.includes(permissionCode)
}
/**
* 检查用户是否有任意一个权限
*/
export function hasAnyPermission(permissionCodes) {
if (!permissionCodes || permissionCodes.length === 0) return true
return permissionCodes.some(code => hasPermission(code))
}
/**
* 检查用户是否有所有权限
*/
export function hasAllPermissions(permissionCodes) {
if (!permissionCodes || permissionCodes.length === 0) return true
return permissionCodes.every(code => hasPermission(code))
}
/**
* 获取用户所有权限
*/
export function getUserPermissions() {
return userPermissions
}

View File

@ -0,0 +1,157 @@
<template>
<div class="monitor-dashboard-wrapper">
<!-- 嵌入Go监控系统的原生页面 -->
<iframe ref="monitorFrame" :src="monitorUrl" class="monitor-iframe" frameborder="0"
@load="onFrameLoad"></iframe>
<!-- 加载状态 -->
<div v-if="loading" class="loading-overlay">
<div class="loading-spinner">
<i class="el-icon-loading"></i>
</div>
<p>正在加载监控大屏...</p>
</div>
<!-- 错误状态 -->
<div v-if="error" class="error-overlay">
<el-icon>
<Warning />
</el-icon>
<p>{{ error }}</p>
<el-button @click="reload" type="primary">重新加载</el-button>
</div>
</div>
</template>
<script>
import { ref, onMounted } from 'vue'
import { Warning } from '@element-plus/icons-vue'
export default {
name: 'MonitorDashboard',
components: {
Warning
},
setup() {
const monitorFrame = ref(null)
const loading = ref(true)
const error = ref('')
const monitorUrl = ref('http://118.195.145.61') // Go
//
const checkMonitorService = async () => {
try {
const response = await fetch(`${monitorUrl.value}/api/v1/health`)
if (!response.ok) {
throw new Error('监控服务不可用')
}
return true
} catch (err) {
error.value = '无法连接到监控服务请确保Go监控服务已启动'
loading.value = false
return false
}
}
const onFrameLoad = () => {
loading.value = false
error.value = ''
}
const reload = () => {
loading.value = true
error.value = ''
if (monitorFrame.value) {
monitorFrame.value.src = monitorUrl.value
}
}
onMounted(async () => {
//
const isAvailable = await checkMonitorService()
if (!isAvailable) {
return
}
// iframe
if (monitorFrame.value) {
monitorFrame.value.src = monitorUrl.value
}
})
return {
monitorFrame,
monitorUrl,
loading,
error,
onFrameLoad,
reload
}
}
}
</script>
<style scoped>
.monitor-dashboard-wrapper {
position: relative;
width: 100%;
height: 100vh;
overflow: hidden;
}
.monitor-iframe {
width: 100%;
height: 100%;
border: none;
}
.loading-overlay,
.error-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: rgba(0, 0, 0, 0.8);
color: white;
z-index: 1000;
}
.loading-overlay p,
.error-overlay p {
margin-top: 20px;
font-size: 16px;
}
.error-overlay .el-icon {
font-size: 48px;
color: #f56c6c;
}
.error-overlay .el-button {
margin-top: 20px;
}
.loading-spinner {
font-size: 32px;
animation: rotate 2s linear infinite;
}
.loading-spinner i {
color: #409EFF;
}
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
</style>

View File

@ -0,0 +1,968 @@
<template>
<div class="p-2">
<div v-show="showSearch" class="mb-10">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="物流模板名称" prop="templateName" label-width="100">
<el-input v-model="queryParams.templateName" placeholder="请输入物流模板名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="详细地址" prop="deliveryAddress" label-width="100">
<el-input v-model="queryParams.deliveryAddress" placeholder="请输入详细地址" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="运送方式" prop="shipping">
<el-input v-model="queryParams.shipping" placeholder="请输入运送方式" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">删除</el-button>
</el-col>
<!-- <el-col :span="1.5">-->
<!-- <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['zhishu:logistics:export']">导出</el-button>-->
<!-- </el-col>-->
<el-button icon="Search" @click="showSearch = !showSearch">{{ showSearch ? '隐藏搜索' : '显示搜索' }}</el-button>
</el-row>
</template>
<el-table v-loading="loading" :data="logisticsList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="" align="center" prop="id" v-if="false" />
<el-table-column label="物流模板名称" align="center" prop="templateName" />
<el-table-column label="详细地址" align="center" prop="deliveryAddress">
<template #default="{ row }">
<span v-if="row.deliveryAddress!=null">{{row.deliveryAddress}}</span>
<span v-else></span>
</template>
</el-table-column>
<el-table-column label="计价方式" align="center" prop="pricingMethod">
<template #default="{ row }">
<span v-if="row.pricingMethod==0">按重量</span>
<span v-else-if="row.pricingMethod==1">按标准本数图书专用</span>
<span v-else-if="row.pricingMethod==2">按件数</span>
<span v-else-if="row.pricingMethod==3">单独设置运费</span>
<span v-else></span>
</template>
</el-table-column>
<el-table-column label="运送方式" align="center" prop="shipping">
<template #default="{ row }">
<span v-if="row.shipping==0">快递</span>
<span v-else-if="row.shipping==1">物流</span>
</template>
</el-table-column>
<el-table-column label="模板状态" align="center" prop="status">
<template #default="{ row }">
<el-tag :type="row.status === '0' ? 'success' : 'danger'" :effect="row.status === '0' ? 'dark' : 'light'"
size="large">
{{ row.status === '0' ? '正常' : '异常(请先修改模版内容)' }}
</el-tag>
</template>
</el-table-column>
<!-- <el-table-column label="仓库名称" align="center" prop="warehouseName" />-->
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改运费模版" placement="top">
<el-button link type="primary" icon="Document" @click="handleFreightTemplate(scope.row)"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<el-pagination
v-show="total > 0"
:total="total"
v-model:current-page="queryParams.pageNum"
v-model:page-size="queryParams.pageSize"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
@size-change="getList"
@current-change="getList"
/>
</el-card>
<!-- 添加或修改物流管理对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
<el-form ref="logisticsFormRef" :model="form" :rules="Temrules" label-width="80px">
<!-- <el-form-item label="货区名称" prop="depotId" >-->
<!-- <el-select v-model="selectedId" value-key="id" placeholder="请选择一级货区" :reserve-keyword="false" clearable filterable style="width: 100%" :loading="loading" @update:model-value="handleDepotChange">-->
<!-- <el-option v-for="item in depotList" :key="item.id" :label="item.name+''+item.unit" :value="item" />-->
<!-- </el-select>-->
<!-- </el-form-item>-->
<el-form-item label="模板名称" prop="templateName">
<el-input v-model="form.templateName" placeholder="请输入物流模板名称" />
</el-form-item>
<el-form-item label="发货地" prop="deliveryArea">
<el-cascader v-model="freightForm.deliveryArea" :props="cascaderProps" placeholder="请选择发货地" clearable
style="width: 100%" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
<!-- 运费模板对话框 -->
<el-dialog title="运费模板设置" v-model="freightDialog.visible" width="1000px" append-to-body>
<div class="freight-template-container">
<el-form :model="freightForm" ref="templatesFrom" label-width="80px" :rules="freightRule" >
<el-form-item label="模板名称">
<el-input v-model="freightForm.templateName" placeholder="请输入模板名称" />
</el-form-item>
<el-form-item label="发货地">
<el-cascader v-model="freightForm.deliveryArea" :props="cascaderProps" placeholder="请选择发货地" clearable
style="width: 100%" />
</el-form-item>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="默认联系人" label-width="100" prop="contact">
<el-input v-model="freightForm.contact" placeholder="请输入联系人" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="手机号" prop="phoneNumber">
<el-input v-model="freightForm.phoneNumber" placeholder="请输入手机号" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="详细地址" prop="fullAddress">
<el-input v-model="freightForm.fullAddress" placeholder="请输入详细地址" />
</el-form-item>
<el-form-item label="计价方式">
<el-radio-group v-model="freightForm.pricingMethod">
<el-radio :value="'weight'" border size="large">按重量</el-radio>
<el-radio :value="'book'" border size="large">按标准本数图书专用</el-radio>
<el-radio :value="'piece'" border size="large">按件数</el-radio>
<el-radio :value="'custom'" border size="large">单独设置运费</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="运送方式">
<el-select v-model="freightForm.deliveryMethod" placeholder="请选择运送方式">
<el-option label="快递" value="express" />
<el-option label="物流" value="logistics" />
</el-select>
<el-button type="danger" plain size="small" style="margin-left: 10px"
@click="deleteDeliveryMethod">删除</el-button>
</el-form-item>
<el-form-item label="运送范围">
<el-table :data="deliveryRanges" style="width: 100%" border>
<el-table-column label="运送范围" width="200">
<template #default="{ row }">
{{ row.region }}
</template>
</el-table-column>
<!-- 动态列 -->
<template v-for="column in dynamicColumns" :key="column.prop">
<el-table-column :label="column.label" :width="column.width">
<template #default="{ row }">
<el-input
v-model="row[column.prop]"
type="number"
:placeholder="column.placeholder"
@blur="column.validate ? validateFee(row[column.prop]) : null"
/>
</template>
</el-table-column>
</template>
<el-table-column label="操作" width="150">
<template #default="{ row }">
<el-button size="small" type="primary" link @click="editRegion(row)">编辑地区</el-button>
<el-button size="small" type="danger" link @click="deleteRegion(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-form-item>
<el-form-item label="配送说明">
<el-input v-model="freightForm.deliveryNote" type="textarea" :rows="3"
placeholder="请输入配送说明输入的内容将会在商品详情页面展示不超过500个字" />
</el-form-item>
</el-form>
</div>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="saveFreightTemplate">确定</el-button>
<el-button @click="cancelFreightTemplate"> </el-button>
</div>
</template>
</el-dialog>
<!-- 区域编辑对话框 -->
<el-dialog :title="regionDialog.title" v-model="regionDialog.visible" width="600px" append-to-body>
<div class="region-edit-container">
<el-checkbox-group v-model="selectedProvinces">
<el-checkbox v-for="province in allProvinces" :key="province" :value="province" :disabled="deliveryRanges.some(item => item.region !== regionDialog.currentRegion && item.region.includes(province))">
{{ province }}
</el-checkbox>
</el-checkbox-group>
</div>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="handleSaveRegion"> </el-button>
<el-button @click="regionDialog.visible = false"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Logistics">
import { ref, reactive, computed, onMounted, toRefs } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
// API
import {
listLogistics,
getLogistics,
delLogistics,
addLogistics,
updateLogistics,
} from '@/api/modules/logistics'
import {
getCitiesByProvinceId,
getDistrictsByCityId,
getFreInfo,
getProvinces,
UpdateTemplate
} from '@/api/modules/district'
// import { depotNameList } from '@/api/modules/shelves'
let curruntRow = null;
let isValid = true; //
const queryFormRef = ref();
const logisticsFormRef = ref();
const templatesFrom = ref();
//
const freightDialog = reactive({
visible: false,
title: '运费模板设置'
});
//
const freightForm = reactive({
templateName: '',
contact: '',
phoneNumber: '',
fullAddress: '',
deliveryArea: [],
pricingMethod: '',
deliveryMethod: 'express',
deliveryNote: ''
});
const validateFee = async (row) => {
if(row === null || row === ''){
isValid = false;
ElMessage.error(' 首费/运费不能为空');
} else {
isValid = true;
}
}
//
const defaultDeliveryRanges = ref([
{
region: '河北',
firstWeight: 1.0,
firstFee: 1.0,
cities: ['保定', '沧州', '承德', '邯郸', '衡水', '廊坊', '秦皇岛', '石家庄', '唐山', '邢台', '张家口'], //
continueWeight: 1.0,
continueFee: 1.0
},
{
region: '北京',
firstWeight: 1.0,
firstFee: 1.0,
continueWeight: 1.0,
continueFee: 1.0
},
{
region: '天津',
firstWeight: 1.0,
firstFee: 1.0,
continueWeight: 1.0,
continueFee: 1.0
},
{
region: '重庆',
firstWeight: 1.0,
firstFee: 1.0,
continueWeight: 1.0,
continueFee: 1.0
},
{
region: '上海、江苏、浙江、安徽、江西、山西、山东、内蒙古、湖南、湖北、河南、广东、广西、福建、海南、辽宁、吉林、黑龙江、陕西、云南、贵州、四川',
firstWeight: 1.0,
firstFee: 1.0,
continueWeight: 1.0,
continueFee: 1.0
},
{
region: '甘肃、宁夏、青海',
firstWeight: 1.0,
firstFee: 1.0,
continueWeight: 1.0,
continueFee: 1.0
},
{
region: '新疆、西藏',
firstWeight: 1.0,
firstFee: 1.0,
continueWeight: 1.0,
continueFee: 1.0
},
{
region: '香港、澳门、台湾、海外',
firstWeight: 1.0,
firstFee: 1.0,
continueWeight: 1.0,
continueFee: 1.0
}
]);
//
const deliveryRanges =ref([
{
region: '河北',
firstWeight: 1.0,
firstFee: 1.0,
cities: ['保定', '沧州', '承德', '邯郸', '衡水', '廊坊', '秦皇岛', '石家庄', '唐山', '邢台', '张家口'], //
continueWeight: 1.0,
continueFee: 1.0
},
{
region: '北京',
firstWeight: 1.0,
firstFee: 1.0,
continueWeight: 1.0,
continueFee: 1.0
},
{
region: '天津',
firstWeight: 1.0,
firstFee: 1.0,
continueWeight: 1.0,
continueFee: 1.0
},
{
region: '重庆',
firstWeight: 1.0,
firstFee: 1.0,
continueWeight: 1.0,
continueFee: 1.0
},
{
region: '上海、江苏、浙江、安徽、江西、山西、山东、内蒙古、湖南、湖北、河南、广东、广西、福建、海南、辽宁、吉林、黑龙江、陕西、云南、贵州、四川',
firstWeight: 1.0,
firstFee: 1.0,
continueWeight: 1.0,
continueFee: 1.0
},
{
region: '甘肃、宁夏、青海',
firstWeight: 1.0,
firstFee: 1.0,
continueWeight: 1.0,
continueFee: 1.0
},
{
region: '新疆、西藏',
firstWeight: 1.0,
firstFee: 1.0,
continueWeight: 1.0,
continueFee: 1.0
},
{
region: '香港、澳门、台湾、海外',
firstWeight: 1.0,
firstFee: 1.0,
continueWeight: 1.0,
continueFee: 1.0
}
]);
// @blur="validateFee(row.firstFee)"
//
const dynamicColumns = computed(() => {
const method = freightForm.pricingMethod;
if (method === 'weight') {
return [
{ prop: 'firstWeight', label: '首重(千克)', placeholder: '请输入首重', width: '120' },
{ prop: 'firstFee', label: '首费(元)', placeholder: '请输入首费', width: '120',validate: true },
{ prop: 'continueWeight', label: '续重(千克)', placeholder: '请输入续重', width: '120' },
{ prop: 'continueFee', label: '续费(元)', placeholder: '请输入续费', width: '120' },
];
} else if (method === 'book') {
return [
{ prop: 'firstWeight', label: '首重本数(本)', placeholder: '请输入首本', width: '120' },
{ prop: 'firstFee', label: '首费(元)', placeholder: '请输入首费', width: '120',validate: true },
{ prop: 'continueWeight', label: '续重本数(本)', placeholder: '请输入续本', width: '120' },
{ prop: 'continueFee', label: '续费(元)', placeholder: '请输入续费', width: '120' },
];
} else if (method === 'piece') {
return [
{ prop: 'firstWeight', label: '首件数(件)', placeholder: '请输入首件', width: '120' },
{ prop: 'firstFee', label: '首费(元)', placeholder: '请输入首费', width: '120',validate: true },
{ prop: 'continueWeight', label: '续件数(件)', placeholder: '请输入续件', width: '120' },
{ prop: 'continueFee', label: '续费(元)', placeholder: '请输入续费', width: '120' },
];
} else if(method === 'custom'){
return [
{ prop: 'firstFee', label: '运费(元)', placeholder: '请输入运费', width: '120',validate: true },
];
}
});
const handleFreightTemplate = async (row) => {
freightDialog.visible = true;
//
//
const res = await getFreInfo(row.id);
console.log(row.id);
freightForm.templateName=row.templateName;
const { deliveryProvince: pro, deliveryCity: city, deliveryArea: area } = res;
freightForm.deliveryArea=[Number(pro),Number(city),Number(area)]
//
const pricingMethodMap = {
0:'weight',
1:'book',
2:'piece',
3:'custom'
};
freightForm.pricingMethod= pricingMethodMap[row.pricingMethod];
//
const deliveryMethodMap = {
0:'express',
1:'logistics'
};
freightForm.deliveryMethod=deliveryMethodMap[row.shipping]
// dataRange=row.shippingRange;
if(row.shippingRange!=null){
const parsedData=JSON.parse(row.shippingRange);
deliveryRanges.value = transformData(parsedData)
}else{
// 使
deliveryRanges.value = JSON.parse(JSON.stringify(defaultDeliveryRanges.value))
}
freightForm.contact=res.contact;
freightForm.phoneNumber=res.phoneNumber;
freightForm.fullAddress=res.fullAddress;
freightForm.deliveryNote=res.remark;
curruntRow = row;
console.log(freightForm)
};
const transformData= (dataRange) => {
const result=[];
for (const region in dataRange) {
const [firstWeight, firstFee, continueWeight, continueFee] = dataRange[region];
result.push({
region,
firstWeight: parseFloat(firstWeight) || 0.0,
firstFee: parseFloat(firstFee)||0.0,
continueWeight: parseFloat(continueWeight) || 0.0,
continueFee: parseFloat(continueFee)||0.0
});
}
return result;
}
const freightRule= reactive({
contact: [
{ required: true, message: '请输入默认联系人', trigger: 'blur' }
],
phoneNumber: [
{ required: true, message: '请输入联系电话', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
],
fullAddress: [
{ required: true, message: '请输入详细地址', trigger: 'blur' }
]
}
)
/** 删除运送方式 */
const deleteDeliveryMethod = async () => {
try {
await ElMessageBox.confirm('确定要删除该运送方式吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
ElMessage.success('删除成功');
// API
} catch (error) {
console.log('取消删除');
}
};
const allProvinces = ['北京', '天津', '河北', '山西', '内蒙古', '辽宁', '吉林', '黑龙江', '上海', '江苏', '浙江', '安徽', '福建', '江西', '山东', '河南', '湖北', '湖南', '广东', '广西', '海南', '重庆', '四川', '贵州', '云南', '西藏', '陕西', '甘肃', '青海', '宁夏', '新疆', '香港', '澳门', '台湾'];
//
const regionDialog = reactive({
visible: false,
title: '编辑地区',
currentRegion: ''
});
//
const selectedProvinces = ref([]);
const editRegion = (row) => {
regionDialog.visible = true;
regionDialog.currentRegion = row.region;
//
selectedProvinces.value = row.region.split('、');
};
/** 删除地区 */
const deleteRegion = async (row) => {
try {
await ElMessageBox.confirm('确定要删除该地区吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
const index = deliveryRanges.value.findIndex(item => item.region === row.region);
if (index !== -1) {
deliveryRanges.value.splice(index, 1);
ElMessage.success('删除成功');
}
} catch (error) {
console.log('取消删除');
}
};
/** 保存运费模板 */
const saveFreightTemplate = async () => {
if (!templatesFrom.value) return;
try {
const valid = await templatesFrom.value.validate();
if(valid) {
buttonLoading.value = true;
//
const [provinceId, cityId, districtId] = freightForm.deliveryArea;
//
const pricingMethodMap = {
'weight': 0,
'book': 1,
'piece': 2,
'custom': 3
};
//
const deliveryMethodMap = {
'express': 0,
'logistics': 1
};
const shippingRanges = deliveryRanges.value.reduce((acc, range) => {
acc[range.region] = [range.firstWeight, range.firstFee, range.continueWeight, range.continueFee];
return acc;
}, {});
//
const templateData = {
id: curruntRow.id,
template_name: freightForm.templateName,
contact: freightForm.contact,
phoneNumber: freightForm.phoneNumber,
fullAddress: freightForm.fullAddress,
delivery_province: provinceId,
delivery_city: cityId,
delivery_area: districtId,
pricing_method: pricingMethodMap[freightForm.pricingMethod],
shipping: deliveryMethodMap[freightForm.deliveryMethod],
shipping_range: shippingRanges,
warehouse_id: curruntRow.id,
remark: freightForm.deliveryNote
};
if (isValid) {
await UpdateTemplate(templateData);
ElMessage.success('运费模板保存成功');
freightDialog.visible = false;
} else {
ElMessage.error('首费/运费不能为空');
}
buttonLoading.value = false;
getList();
}
} catch (error) {
console.error('表单验证或保存失败:', error);
ElMessage.error('操作失败,请检查输入信息');
buttonLoading.value = false;
}
};
/** 取消运费模板 */
const cancelFreightTemplate = () => {
freightDialog.visible = false;
};
/** 保存区域设置 */
const handleSaveRegion = () => {
if (selectedProvinces.value.length === 0) {
ElMessage.error('请至少选择一个省份');
return;
}
//
const unselectedProvinces = getUnselectedProvinces();
// deliveryRangesregion
const index = deliveryRanges.value.findIndex(item => item.region === regionDialog.currentRegion);
if (index !== -1) {
//
deliveryRanges.value[index].region = selectedProvinces.value.join('、');
//
if (unselectedProvinces.length > 0) {
//
const unselectedIndex = deliveryRanges.value.findIndex(item =>
//
unselectedProvinces.some(province => item.region.includes(province)));
if (unselectedIndex !== -1 && unselectedIndex !== index) {
//
deliveryRanges.value[unselectedIndex].region = unselectedProvinces.join('、');
} else {
//
deliveryRanges.value.push({
region: unselectedProvinces.join('、'),
firstWeight: 1.0,
firstFee: 1.0,
continueWeight: 1.0,
continueFee: 1.0
});
}
}
}
regionDialog.visible = false;
};
//
const cascaderProps = reactive({
lazy: true,
async lazyLoad(node, resolve) {
const { level, value: parentId } = node;
try {
let nodes = [];
if (level === 0) { //
const res = await getProvinces();
nodes = res.data.map(formatNode(0));
}
else if (level === 1) { //
const res = await getCitiesByProvinceId(parentId);
nodes = res.data.map(formatNode(1));
}
else if (level === 2) { //
const res = await getDistrictsByCityId(parentId);
nodes = res.data.map(formatNode(2, true));
}
resolve(nodes);
} catch (error) {
resolve([]);
}
}
});
const formatNode = (level, isLeaf = false) => (item) => ({
value: item.id,
label: item.name,
leaf: isLeaf,
level: level
});
//
const getUnselectedProvinces = () => {
//
const originalProvinces = regionDialog.currentRegion.split('、');
//
const selectedProvincesSet = new Set(selectedProvinces.value);
//
return originalProvinces.filter(province => !selectedProvincesSet.has(province));
};
const logisticsList = ref([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const dialog = reactive({
visible: false,
title: ''
});
const initFormData = {
id: undefined,
templateName: undefined,
deliveryProvince: undefined,
deliveryCity: undefined,
deliveryArea: undefined,
deliveryAddress: undefined,
pricingMethod: undefined,
shipping: undefined,
firWbv: undefined,
firPrice: undefined,
continueWbv: undefined,
continuePrice: undefined,
status: undefined,
shippingRange: undefined,
warehouseId: undefined,
warehouseName: undefined
}
const data = reactive({
form: {...initFormData},
queryParams: {
pageNum: 1,
pageSize: 10,
id:undefined,
templateName: undefined,
deliveryProvince: undefined,
deliveryCity: undefined,
deliveryArea: undefined,
deliveryAddress: undefined,
pricingMethod: undefined,
shipping: undefined,
firWbv: undefined,
firPrice: undefined,
continueWbv: undefined,
continuePrice: undefined,
status: undefined,
shippingRange: undefined,
warehouseId: undefined,
warehouseName:undefined,
params: {
}
},
rules: {
}
});
const { queryParams, form } = toRefs(data);
//
const Temrules = reactive({
templateName: [
{
required: true,
message: '模版名称不能为空',
trigger: ['blur', 'change']
}
],
//
deliveryArea: [
{
required: true,
message: '请选择发货地',
trigger: 'change', //
validator: (_, __, callback) => {
const message = freightForm.deliveryArea;
if(message.length == 0){
callback(new Error('请选择完整地址'));
} else {
callback();
}
}
}
]
})
// 使
// const selectedId = ref(null)
// const depotList = ref([])
// const loadData = async () => {
// loading.value = true
// try {
// const res = await depotNameList()
// depotList.value = res.rows
// } catch (error) {
// console.error(':', error)
// } finally {
// loading.value = false
// }
// }
// const handleDepotChange = (val) => {
// form.value.depotId = val?.id || null
// form.value.depotName = val?.name || ''
// }
/** 查询物流管理列表 */
const getList = async () => {
loading.value = true;
const res = await listLogistics(queryParams.value);
logisticsList.value = res.rows;
total.value = res.total;
loading.value = false;
}
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
}
/** 表单重置 */
const reset = () => {
form.value = {...initFormData};
logisticsFormRef.value?.resetFields();
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
}
/** 多选框选中数据 */
const handleSelectionChange = (selection) => {
ids.value = selection.map(item => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
}
/** 新增按钮操作 */
const handleAdd = () => {
reset();
freightForm.deliveryArea = [];
freightForm.pricingMethod = '2'
dialog.visible = true;
dialog.title = "添加物流管理";
}
/** 修改按钮操作 */
const handleUpdate = async (row) => {
reset();
const _id = row?.id || ids.value[0]
const res = await getLogistics(_id);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = "修改物流管理";
}
/** 提交按钮 */
const submitForm = () => {
logisticsFormRef.value?.validate(async (valid) => {
if (valid) {
buttonLoading.value = true;
if (form.value.id) {
await updateLogistics(form.value).finally(() => buttonLoading.value = false);
} else {
const [provinceId, cityId, districtId] = freightForm.deliveryArea;
form.value.deliveryProvince = provinceId;
form.value.deliveryCity = cityId;
form.value.deliveryArea = districtId;
form.value.pricingMethod = 1;
await addLogistics(form.value).finally(() => buttonLoading.value = false);
}
ElMessage.success("操作成功");
dialog.visible = false;
await getList();
}
});
}
/** 删除按钮操作 */
const handleDelete = async (row) => {
const _ids = row?.id || ids.value;
try {
await ElMessageBox.confirm('是否确认删除物流管理编号为"' + _ids + '"的数据项?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
await delLogistics(_ids);
ElMessage.success("删除成功");
await getList();
} catch (error) {
console.log('取消删除')
}
}
onMounted(() => {
getList();
// loadData(); //
});
</script>
<style scoped>
.freight-template-container {
padding: 20px;
}
.region-edit-container {
max-height: 400px;
overflow-y: auto;
}
.region-edit-container .el-checkbox {
display: block;
margin: 10px 0;
}
.dialog-footer {
text-align: right;
}
.mb8 {
margin-bottom: 8px;
}
.p-2 {
padding: 8px;
}
.mb-10 {
margin-bottom: 10px;
}
</style>

View File

@ -0,0 +1,560 @@
<template>
<div class="register-page">
<div class="register-container">
<div class="register-form">
<!-- 标题 -->
<div class="form-header">
<h2>与书同行</h2>
<!-- <div class="language-switch">
<span>A</span>
</div> -->
</div>
<!-- 表单 -->
<el-form ref="formRef" :model="formData" :rules="rules" label-width="0">
<!-- 用户名 -->
<el-form-item prop="username">
<el-input
v-model="formData.username"
placeholder="用户名"
size="large"
:prefix-icon="User"
disabled
/>
</el-form-item>
<!-- 手机号 -->
<el-form-item prop="phoneNumber">
<el-input
v-model="formData.phoneNumber"
placeholder="手机号"
size="large"
:prefix-icon="Phone"
/>
</el-form-item>
<!-- 密码 -->
<el-form-item prop="password">
<el-input
v-model="formData.password"
type="password"
placeholder="密码"
size="large"
:prefix-icon="Lock"
show-password
/>
</el-form-item>
<!-- 确认密码 -->
<el-form-item prop="confirmPassword">
<el-input
v-model="formData.confirmPassword"
type="password"
placeholder="确认密码"
size="large"
:prefix-icon="Lock"
show-password
/>
</el-form-item>
<!-- 邀请码 -->
<el-form-item prop="inviteCode">
<el-input
v-model="formData.inviteCode"
placeholder="邀请码(非必填)"
size="large"
:prefix-icon="Message"
/>
</el-form-item>
<!-- 验证码 -->
<el-form-item v-if="captchaEnabled" prop="code">
<div class="captcha-row">
<el-input
v-model="formData.code"
placeholder="验证码"
size="large"
:prefix-icon="Key"
/>
<div class="captcha-image" @click="getCaptcha">
<img v-if="captchaImg" :src="captchaImg" alt="验证码" />
</div>
</div>
</el-form-item>
<!-- 注册按钮 -->
<el-form-item>
<el-button
type="primary"
size="large"
class="register-btn"
@click="handleRegister"
:loading="loading"
>
注册
</el-button>
</el-form-item>
</el-form>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { User, Phone, Lock, Message, Key } from '@element-plus/icons-vue'
import { getApiUrl } from '@/config/api'
const route = useRoute()
const loading = ref(false)
const formRef = ref()
const captchaImg = ref('')
const captchaEnabled = ref(true)
// URL
const urlParams = ref({
pddMallId: '',
pddMallName: '',
type: '',
accessToken: '',
skuSpec: ''
})
//
const formData = ref({
username: '',
phoneNumber: '',
password: '',
confirmPassword: '',
inviteCode: '',
code: '',
uuid: '',
clientId: 'e5cd7e4891bf95d1d19206ce24a7b32e',
grantType: 'password',
tenantId: '000000',
userType: 'sys_user'
})
//
const rules = {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 2, max: 20, message: '用户名长度在2到20个字符', trigger: 'blur' }
],
phoneNumber: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 5, max: 20, message: '密码长度在5到20个字符', trigger: 'blur' }
],
confirmPassword: [
{ required: true, message: '请确认密码', trigger: 'blur' },
{
validator: (rule, value, callback) => {
if (value !== formData.value.password) {
callback(new Error('两次输入密码不一致'))
} else {
callback()
}
},
trigger: 'blur'
}
],
code: [
{
required: true,
message: '请输入验证码',
trigger: 'blur',
validator: (rule, value, callback) => {
//
if (!captchaEnabled.value) {
callback()
return
}
//
if (!value) {
callback(new Error('请输入验证码'))
} else {
callback()
}
}
}
]
}
// URL
const getUrlParams = () => {
const query = route.query
urlParams.value = {
pddMallId: query.pddMallId || '',
pddMallName: decodeURIComponent(query.pddMallName || ''),
type: query.type || '',
accessToken: query.accessToken || '',
skuSpec: decodeURIComponent(query.skuSpec || '')
}
// "ppd" + pddMallId
if (urlParams.value.pddMallId) {
formData.value.username = 'pdd' + urlParams.value.pddMallId
}
}
//
const getCaptcha = async () => {
try {
const response = await fetch(getApiUrl('/auth/code'), {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
})
if (response.ok) {
//
const contentType = response.headers.get('content-type')
if (contentType && contentType.includes('application/json')) {
const result = await response.json()
if (result.code === 200) {
const { data } = result
//
captchaEnabled.value = data.captchaEnabled === undefined ? true : data.captchaEnabled
if (captchaEnabled.value) {
// UUID
formData.value.uuid = data.uuid
captchaImg.value = 'data:image/gif;base64,' + data.img
} else {
//
formData.value.uuid = ''
captchaImg.value = ''
//
}
} else {
//
let errorMessage = result.msg || '获取验证码失败'
//
if (result.msg === 'Captcha error') {
errorMessage = '验证码生成失败,请重试'
} else if (result.msg === 'Captcha invalid') {
errorMessage = '验证码服务异常,请重试'
} else if (result.msg && (
result.msg.includes('Captcha') ||
result.msg.includes('captcha')
)) {
errorMessage = '验证码服务异常,请重试'
}
ElMessage.error(errorMessage)
}
} else {
// JSON
const text = await response.text()
ElMessage.error('服务器响应格式错误请检查API接口')
}
} else {
// HTTP200-299
ElMessage.error(`获取验证码失败 (${response.status}),请重试`)
}
} catch (error) {
if (error.name === 'SyntaxError' && error.message.includes('JSON')) {
ElMessage.error('服务器响应格式错误请检查API接口')
} else {
ElMessage.error('网络错误,获取验证码失败')
}
}
}
//
const handleRegister = async () => {
try {
//
const valid = await formRef.value.validate()
if (!valid) return
loading.value = true
//
const registerData = {
username: formData.value.username,
password: formData.value.password,
phoneNumber: formData.value.phoneNumber,
inviteCode: formData.value.inviteCode,
clientId: formData.value.clientId,
grantType: formData.value.grantType,
tenantId: formData.value.tenantId,
userType: formData.value.userType,
//
pddMallId: urlParams.value.pddMallId,
pddMallName: urlParams.value.pddMallName,
pddType: urlParams.value.type,
accessToken: urlParams.value.accessToken,
skuSpec: urlParams.value.skuSpec
}
//
if (captchaEnabled.value) {
registerData.code = formData.value.code
registerData.uuid = formData.value.uuid
}
// API
const response = await fetch(getApiUrl('/auth/register'), {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(registerData)
})
const result = await response.json()
if (result.code === 200) {
//
showSuccessDialog()
//
// router.push('/login')
} else {
//
let errorMessage = result.msg || '注册失败,请重试'
//
if (result.msg === 'Captcha error') {
errorMessage = '验证码错误,请重新输入'
} else if (result.msg === 'Captcha invalid') {
errorMessage = '验证码无效,请重新输入'
} else if (result.msg && (
result.msg.includes('Captcha') ||
result.msg.includes('captcha') ||
result.msg.includes('验证码')
)) {
errorMessage = '验证码错误,请重新输入'
}
ElMessage.error(errorMessage)
//
if (captchaEnabled.value && result.msg && (
result.msg === 'Captcha error' ||
result.msg === 'Captcha invalid' ||
result.msg.includes('Captcha') ||
result.msg.includes('captcha') ||
result.msg.includes('验证码')
)) {
//
getCaptcha()
//
formData.value.code = ''
}
}
} catch (error) {
console.error('注册失败:', error)
ElMessage.error('注册失败,请重试')
} finally {
loading.value = false
}
}
//
const showSuccessDialog = () => {
const loginUrl = 'https://erp.buzhiyushu.cn/'
ElMessageBox({
title: '注册成功!',
message: `
<div style="text-align: center;">
<p style="margin-bottom: 15px; color: #67C23A; font-size: 16px;">🎉 恭喜您注册成功</p>
<p style="margin-bottom: 15px; color: #606266;">请复制以下链接进行登录</p>
<div style="background: #f5f7fa; padding: 10px; border-radius: 4px; margin-bottom: 15px; word-break: break-all;">
${loginUrl}
</div>
</div>
`,
dangerouslyUseHTMLString: true,
showCancelButton: true,
confirmButtonText: '复制链接',
cancelButtonText: '关闭',
confirmButtonClass: 'el-button--primary',
cancelButtonClass: 'el-button--default',
center: true
}).then(async () => {
//
try {
await navigator.clipboard.writeText(loginUrl)
ElMessage.success('链接已复制到剪贴板!')
} catch (err) {
// 使
try {
//
const textArea = document.createElement('textarea')
textArea.value = loginUrl
document.body.appendChild(textArea)
textArea.select()
document.execCommand('copy')
document.body.removeChild(textArea)
ElMessage.success('链接已复制到剪贴板!')
} catch (fallbackErr) {
ElMessage.error('复制失败,请手动复制链接')
}
}
}).catch(() => {
// ESC
console.log('用户关闭了弹窗')
})
}
// URL
onMounted(() => {
getUrlParams()
getCaptcha()
})
</script>
<style scoped>
.register-page {
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.register-container {
width: 100%;
max-width: 400px;
}
.register-form {
background: white;
border-radius: 12px;
padding: 40px 30px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
}
.form-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
}
.form-header h2 {
font-size: 24px;
color: #333;
margin: 0;
font-weight: 500;
}
.language-switch {
width: 32px;
height: 32px;
background: #f0f0f0;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.language-switch span {
font-size: 14px;
color: #666;
font-weight: bold;
}
.el-form-item {
margin-bottom: 20px;
}
.el-input {
border-radius: 8px;
}
:deep(.el-input__wrapper) {
border-radius: 8px;
box-shadow: 0 0 0 1px #dcdfe6;
border: none;
}
:deep(.el-input__wrapper:hover) {
box-shadow: 0 0 0 1px #c0c4cc;
}
:deep(.el-input__wrapper.is-focus) {
box-shadow: 0 0 0 1px #409eff;
}
.captcha-row {
display: flex;
gap: 10px;
align-items: center;
}
.captcha-row .el-input {
flex: 1;
}
.captcha-image {
width: 100px;
height: 40px;
border: 1px solid #dcdfe6;
border-radius: 6px;
overflow: hidden;
cursor: pointer;
}
.captcha-image img {
width: 100%;
height: 100%;
display: block;
object-fit: cover;
}
.register-btn {
width: 100%;
height: 48px;
font-size: 16px;
border-radius: 8px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
}
.register-btn:hover {
background: linear-gradient(135deg, #5a6fd8 0%, #6a4190 100%);
}
.login-link {
text-align: center;
margin-top: 20px;
}
.login-link span {
color: #666;
font-size: 14px;
cursor: pointer;
}
.login-link span:hover {
color: #409eff;
}
/* 移动端适配 */
@media (max-width: 480px) {
.register-form {
padding: 30px 20px;
margin: 10px;
}
.form-header h2 {
font-size: 20px;
}
}
</style>