添加个人中心及按角色展示内容

This commit is contained in:
2026-03-11 16:12:00 +08:00
parent 4df88ea502
commit 2056f69c3e
45 changed files with 1108 additions and 23 deletions

26
add_overlay_css.py Normal file
View File

@@ -0,0 +1,26 @@
import codecs
file_path = r'd:\\骅锋\\mall\\layouts\\admin\\components\\AdminHeader.uvue'
with codecs.open(file_path, 'r', 'utf-8') as f:
text = f.read()
overlay_css = r'''
.user-dropdown-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
background-color: transparent;
}
'''
if '.user-dropdown-overlay' not in text:
text = text.replace('</style>', overlay_css + '\n</style>')
text = text.replace('z-index: 99;', 'z-index: 1001;')
text = text.replace('z-index: 90;', 'z-index: 1001;')
text = text.replace('z-index: 150;', 'z-index: 1001;')
text = text.replace('.user-dropdown {', '.user-dropdown {\n z-index: 1002;')
with codecs.open(file_path, 'w', 'utf-8') as f:
f.write(text)

9
add_toast.py Normal file
View File

@@ -0,0 +1,9 @@
import codecs
file_path = r'd:\\骅锋\\mall\\layouts\\admin\\components\\AdminHeader.uvue'
with codecs.open(file_path, 'r', 'utf-8') as f:
text = f.read()
text = text.replace('function goToUserCenter() {', 'function goToUserCenter() {\n uni.showToast({ title: "尝试打开个人中心...", icon: "none", duration: 2000 });\n console.log([AdminHeader] goToUserCenter called);')
with codecs.open(file_path, 'w', 'utf-8') as f:
f.write(text)

8
fix3.py Normal file
View File

@@ -0,0 +1,8 @@
import codecs
file_path = r'd:\\骅锋\\mall\\pages.json'
with codecs.open(file_path, 'r', 'utf-8') as f:
text = f.read()
text = text.replace('custom\"\n }\n }\n },', 'custom\"\n }\n },')
with codecs.open(file_path, 'w', 'utf-8') as f:
f.write(text)

11
fix_css2.py Normal file
View File

@@ -0,0 +1,11 @@
import codecs
import re
file_path = r'd:\\骅锋\\mall\\layouts\\admin\\components\\AdminHeader.uvue'
with codecs.open(file_path, 'r', 'utf-8') as f:
text = f.read()
text = re.sub(r'(\.header\s*\{)', r'\1\n position: relative;\n z-index: 150;', text)
text = re.sub(r'(\.header-right\s*\{)', r'\1\n position: relative;\n z-index: 100;\n pointer-events: auto;', text)
with codecs.open(file_path, 'w', 'utf-8') as f:
f.write(text)

8
fix_final.py Normal file
View File

@@ -0,0 +1,8 @@
import codecs
file_path = r'd:\\骅锋\\mall\\pages.json'
with codecs.open(file_path, 'r', 'utf-8') as f:
text = f.read()
text = text.replace('custom\"\n },{', 'custom\"\n }\n },\n {')
with codecs.open(file_path, 'w', 'utf-8') as f:
f.write(text)

10
fix_header_css.py Normal file
View File

@@ -0,0 +1,10 @@
import codecs
file_path = r'd:\\骅锋\\mall\\layouts\\admin\\components\\AdminHeader.uvue'
with codecs.open(file_path, 'r', 'utf-8') as f:
text = f.read()
text = text.replace('.header-right{ \n display:flex;', '.header-right{ \n position: relative;\n z-index: 99;\n pointer-events: auto;\n display:flex;')
text = text.replace('.header{\n height: 56px;', '.header{\n position: relative;\n z-index: 90;\n height: 56px;')
with codecs.open(file_path, 'w', 'utf-8') as f:
f.write(text)

BIN
fix_pages.py Normal file

Binary file not shown.

View File

@@ -63,8 +63,8 @@
<!-- 内容展示区 (内部路由渲染) -->
<view class="content-scroll">
<view class="content-inner" :class="{ 'is-mobile': isMobile }">
<slot></slot>
<component :is="currentComponent" v-if="!isPageLoading && currentComponent != null"></component>
<slot v-if="hasAccess"></slot>
<component :is="currentComponent" v-if="hasAccess && !isPageLoading && currentComponent != null"></component>
<AdminPageLoading v-if="isPageLoading"></AdminPageLoading>
</view>
<AdminFooter></AdminFooter>
@@ -116,6 +116,7 @@ import {
import type { TabItem } from '@/layouts/admin/store/adminNavStore.uts'
import { getComponent } from '@/layouts/admin/router/adminComponentMap.uts'
import { hasAdminModuleAccess } from '@/layouts/admin/utils/role.uts'
const props = defineProps({
currentPage: {
@@ -131,6 +132,12 @@ const SUB_W = 200
// 页面加载状态
const isPageLoading = ref(false)
const hasAccess = computed<boolean>(() => {
return hasAdminModuleAccess(activeTopMenuId.value)
})
const hasNotification = ref<boolean>(false)
/**
@@ -258,6 +265,8 @@ const breadcrumb = computed<Array<{id: string, title: string}>>(() => {
// 当前渲染的组件
const currentComponent = computed<any>(() => {
const route = findRouteById(activeRouteId.value)
if (!route) return null
return getComponent(route.componentKey)

View File

@@ -25,24 +25,74 @@
<view class="header-right">
<view class="hbtn" @click="$emit('search')"><text>🔍</text></view>
<view v-if="!isMobile" class="hbtn" @click="$emit('refresh')"><text>⟳</text></view>
<view class="hbtn" @click="$emit('notify')">
<view class="hbtn" @click="$emit('notify')">
<text>🔔</text>
<view class="dot" v-if="hasNotification"></view>
</view>
<!-- 用户个人中心 / 下拉菜单 -->
<view class="user-profile-container" @click="goToUserCenter">
<text class="user-name">crmeb demo</text>
<text class="user-arrow">▼</text>
<view class="user-dropdown" v-if="showUserMenu">
<view class="dropdown-item" @click.stop="goToUserCenter">个人中心</view>
<view class="dropdown-item" @click.stop="handleLogout">退出登录</view>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { computed } from 'vue'
import {
toggleSubSider,
showSubSider,
layoutMode,
import { ref, computed } from 'vue'
import {
toggleSubSider,
showSubSider,
layoutMode,
isOverlayVisible,
isMobileMenuOpen
isMobileMenuOpen,
openRoute
} from '@/layouts/admin/store/adminNavStore.uts'
const showUserMenu = ref(false)
function closeUserMenu() {
showUserMenu.value = false
}
function toggleUserMenu() {
showUserMenu.value = !showUserMenu.value
}
function goToUserCenter() {
uni.showToast({ title: "尝试打开个人中心...", icon: "none", duration: 2000 });
uni.showToast({ title: "尝试打开个人中心...", icon: "none", duration: 2000 });
console.log("[AdminHeader] goToUserCenter called");
console.log("[AdminHeader] goToUserCenter called");
showUserMenu.value = false
openRoute('home_user_center')
}
function handleLogout() {
showUserMenu.value = false
uni.showModal({
title: '提示',
content: '确定要退出登录吗?',
success: (res) => {
if (res.confirm) {
uni.removeStorageSync('adminRole')
uni.removeStorageSync('token')
uni.reLaunch({
url: '/pages/user/login'
})
}
}
})
}
const props = defineProps<{
breadcrumb: Array<{id: string, title: string}>
hasNotification: boolean
@@ -82,6 +132,10 @@ const currentTitle = computed((): string => {
<style>
.header{
position: relative;
z-index: 1001;
position: relative;
z-index: 1001;
height: 56px;
background:#fff;
border-bottom: 1px solid #eef2f7;
@@ -142,6 +196,12 @@ const currentTitle = computed((): string => {
display: flex !important;
}
.header-right {
position: relative;
z-index: 100;
pointer-events: auto;
position: relative;
z-index: 100;
pointer-events: auto;
gap: 5px;
}
}
@@ -151,7 +211,13 @@ const currentTitle = computed((): string => {
margin: 0 8px;
}
.header-right{
.header-right{
position: relative;
z-index: 100;
pointer-events: auto;
position: relative;
z-index: 100;
pointer-events: auto;
display:flex;
flex-direction:row;
align-items:center;
@@ -176,4 +242,64 @@ const currentTitle = computed((): string => {
top: 6px;
right: 6px;
}
.user-profile-container {
position: relative;
display: flex;
flex-direction: row;
align-items: center;
margin-left: 10px;
cursor: pointer;
}
.user-name {
font-size: 14px;
color: #333;
}
.user-arrow {
font-size: 12px;
color: #666;
margin-left: 4px;
}
.user-dropdown {
z-index: 1002;
z-index: 1002;
position: absolute;
top: 40px;
right: 0;
width: 120px;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 2px 12px rgba(0,0,0,0.1);
z-index: 1000;
display: flex;
flex-direction: column;
}
.dropdown-item {
height: 40px;
line-height: 40px;
text-align: center;
font-size: 14px;
color: #333;
cursor: pointer;
}
.dropdown-item:hover {
background-color: #f5f7fa;
color: #1890ff;
}
.user-dropdown-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
background-color: transparent;
}
</style>

View File

@@ -16,6 +16,7 @@ import PlaceholderPage from '@/layouts/admin/components/PlaceholderPage.uvue'
// 导入首页(内部组件,不包含 AdminLayout
import HomeIndex from '@/layouts/admin/pages/HomeIndex.uvue'
import UserCenter from '@/pages/mall/admin/userCenter/index.uvue'
// --- 用户模块 ---
import UserStatistic from '@/pages/mall/admin/user/statistics/index.uvue'
@@ -179,6 +180,7 @@ import MaintainSysInfo from '@/pages/mall/admin/maintain/sys/info.uvue'
export const componentMap: Map<string, any> = new Map([
// 首页
['HomeIndex', HomeIndex],
['UserCenter', UserCenter],
// 用户模块
['UserStatistic', UserStatistic],

View File

@@ -29,6 +29,8 @@ export type RouteRecord = {
/**
* 菜单分组类型
*/
import { hasAdminModuleAccess } from '@/layouts/admin/utils/role.uts'
export type MenuGroup = {
id: string
title: string
@@ -232,8 +234,17 @@ export const routes: RouteRecord[] = [
},
// ========== 用户模块 ==========
{
id: 'user_statistic',
// ========== 个人中心 ==========
{
id: 'home_user_center',
title: '个人中心',
path: '/pages/mall/admin/userCenter/index',
componentKey: 'UserCenter',
order: 100
},
{
id: 'user_statistic',
title: '用户统计',
path: '/pages/mall/admin/user/statistics/index',
componentKey: 'UserStatistic',
@@ -1175,7 +1186,10 @@ export const routes: RouteRecord[] = [
* 获取所有一级菜单
*/
export function getTopMenus(): TopMenu[] {
return topMenus.sort((a, b) => a.order - b.order)
// 基于 role 的模块过滤
return topMenus
.filter(m => hasAdminModuleAccess(m.id))
.sort((a, b) => a.order - b.order)
}
/**

View File

@@ -12,6 +12,7 @@ import {
getTopMenus
} from '@/layouts/admin/router/adminRoutes.uts'
import { addView, activeFullPath, visitedViews } from './tagsViewStore.uts'
import { hasAdminModuleAccess } from '@/layouts/admin/utils/role.uts'
/**
* 标签页类型
@@ -77,14 +78,34 @@ export const isOverlayVisible = ref<boolean>(false)
* @param addTab 是否添加到标签页
*/
export function openRoute(routeId: string, addTab: boolean = true): void {
const route = findRouteById(routeId)
if (!route) {
console.warn(`[AdminNav] Route not found: ${routeId}`)
return
}
// 基于 role 的页面访问拦截
// route.parentId 对应上方 topMenus 的 id。这里校验是否有权限
const moduleId = route.parentId ? route.parentId : route.id.split('_')[0]
if (!hasAdminModuleAccess(moduleId)) {
uni.showToast({
title: '您没有权限访问该模块',
icon: 'none'
})
console.warn(`[AdminNav] Access denied for role to module: ${moduleId}`)
// 回退到首页
if (routeId !== 'home_index') {
openRoute('home_index', addTab)
}
return
}
// 更新当前路由
activeRouteId.value = routeId
// 更新一级菜单选中态
if (route.parentId) {

View File

@@ -0,0 +1,51 @@
// Admin role-based access control
export function getCurrentAdminRole(): string {
// 从本地存储获取当前 role。为不影响全局优先读 admin_role默认降级到读全局 role/userInfo再默认
// 根据实际系统中的 token/userInfo 结构可灵活调整
const roleType = uni.getStorageSync('admin_role') as string
if (roleType && typeof roleType === 'string' && roleType.length > 0) {
return roleType
}
// 检查是否有关联 merchant_id 或者是存了全局 role
const merchantId = uni.getStorageSync('merchant_id')
if (merchantId) return 'merchant'
const globalRole = uni.getStorageSync('role') as string
if (globalRole && typeof globalRole === 'string' && globalRole.length > 0) {
return globalRole
}
return 'admin' // 默认返回 admin 作为兜底(兼容原有全部显示逻辑)
}
// 获取不同 role 允许访问的顶级模块 ID (主侧边栏的顶层菜单 id)
export function getVisibleTopMenuIds(role: string): string[] {
if (role === 'admin') {
return ['home', 'user', 'order', 'product', 'marketing', 'distribution', 'kefu', 'finance', 'cms', 'decoration', 'app', 'setting', 'maintain']
}
if (role === 'merchant') {
// merchant: 只能看到 主页、订单、商品、营销、财务
return ['home', 'order', 'product', 'marketing', 'finance']
}
// 其他 role: 安全兜底
return ['home']
}
// 判断是否有权限访问某模块或路由
export function hasAdminModuleAccess(moduleId: string | undefined): boolean {
if (!moduleId) return true // 没有指定的通常认为是公共的,比如一些通用组件
const role = getCurrentAdminRole()
if (role === 'admin') return true
// 对于 merchant 角色,允许其访问的顶级 menu id 及其所属路由
if (role === 'merchant') {
const allowed = ['home', 'order', 'product', 'marketing', 'finance']
return allowed.includes(moduleId)
}
return false
}

20
make_simple.py Normal file
View File

@@ -0,0 +1,20 @@
import codecs
content = '''<template>
<view class="user-center-container">
<text style="font-size: 24px; color: red;">个人中心 userCenter loaded!</text>
</view>
</template>
<script setup lang="uts">
console.log('[UserCenter] Component Setup Executed!')
</script>
<style scoped>
.user-center-container {
padding: 50px;
background-color: #fff;
min-height: 500px;
}
</style>'''
with codecs.open(r'd:\\骅锋\\mall\\pages\\mall\\admin\\userCenter\\index.uvue', 'w', 'utf-8') as f:
f.write(content)

View File

@@ -14,6 +14,13 @@
"navigationStyle": "custom"
}
},
{
"path": "pages/mall/admin/userCenter/index",
"style": {
"navigationBarTitleText": "个人中心",
"navigationStyle": "custom"
}
},
{
"path": "pages/user/boot",
"style": {

View File

@@ -0,0 +1,195 @@
<template>
<view class="user-center-container">
<view class="page-header">
<text class="page-title">个人中心</text>
</view>
<view class="form-container">
<view class="form-item">
<text class="form-label">头像</text>
<view class="form-content avatar-uploader">
<!-- 默认使用一个占位头像或 Logo -->
<image class="avatar-img" src="/static/logo.png" mode="aspectFill"></image>
</view>
</view>
<view class="form-item">
<text class="form-label">账号</text>
<view class="form-content">
<input class="uni-input disabled" value="demo" disabled placeholder="请输入账号" />
</view>
</view>
<view class="form-item">
<text class="form-label"><text class="required">*</text>姓名</text>
<view class="form-content">
<input class="uni-input" v-model="formData.name" placeholder="请输入姓名" />
</view>
</view>
<view class="form-item">
<text class="form-label">原始密码</text>
<view class="form-content">
<input class="uni-input" type="password" v-model="formData.oldPassword" placeholder="请输入原始密码" />
</view>
</view>
<view class="form-item">
<text class="form-label">新密码</text>
<view class="form-content">
<input class="uni-input" type="password" v-model="formData.newPassword" placeholder="请输入新密码" />
</view>
</view>
<view class="form-item">
<text class="form-label">确认密码</text>
<view class="form-content">
<input class="uni-input" type="password" v-model="formData.confirmPassword" placeholder="请再次输入新密码" />
</view>
</view>
<view class="form-actions">
<button class="submit-btn" type="primary" @click="onSubmit">保存修改</button>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { reactive } from 'vue'
const formData = reactive({
name: 'demo',
oldPassword: '',
newPassword: '',
confirmPassword: ''
})
const onSubmit = () => {
if (formData.newPassword && formData.newPassword !== formData.confirmPassword) {
uni.showToast({ title: '两次输入的新密码不一致', icon: 'none' })
return
}
if (!formData.name) {
uni.showToast({ title: '姓名不能为空', icon: 'none' })
return
}
uni.showLoading({ title: '保存中...' })
setTimeout(() => {
uni.hideLoading()
uni.showToast({ title: '保存成功(演示)', icon: 'success' })
// 清空密码框
formData.oldPassword = ''
formData.newPassword = ''
formData.confirmPassword = ''
}, 500)
}
</script>
<style scoped>
.user-center-container {
padding: 20px;
background-color: #f5f7f9;
min-height: 100%;
}
.page-header {
margin-bottom: 20px;
}
.page-title {
font-size: 16px;
font-weight: bold;
color: #333;
}
.form-container {
background-color: #fff;
border-radius: 4px;
padding: 30px 20px;
max-width: 800px;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
}
.form-item {
display: flex;
flex-direction: row;
align-items: center;
margin-bottom: 24px;
}
.form-label {
width: 100px;
text-align: right;
margin-right: 16px;
font-size: 14px;
color: #606266;
flex-shrink: 0;
}
.required {
color: #f5222d;
margin-right: 4px;
}
.form-content {
flex: 1;
max-width: 400px;
}
.uni-input {
width: 100%;
height: 36px;
line-height: 36px;
padding: 0 12px;
border: 1px solid #dcdfe6;
border-radius: 4px;
font-size: 14px;
color: #333;
box-sizing: border-box;
}
.uni-input:focus {
border-color: #1890ff;
outline: none;
}
.disabled {
background-color: #f5f7fa;
color: #c0c4cc;
cursor: not-allowed;
}
.avatar-uploader {
display: flex;
justify-content: flex-start;
}
.avatar-img {
width: 80px;
height: 80px;
border-radius: 4px;
background-color: #f0f2f5;
border: 1px dashed #d9d9d9;
}
.form-actions {
margin-top: 40px;
padding-left: 116px;
}
.submit-btn {
background-color: #1890ff;
color: #fff;
font-size: 14px;
height: 36px;
line-height: 36px;
width: 120px;
border-radius: 4px;
border: none;
}
.submit-btn:active {
background-color: #096dd9;
}
</style>

View File

@@ -1,10 +1,100 @@
signIn result:
AkSupaSignInResult {access_token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4Y…HNlfQ.5BvQq26lJUF23AEglMvA7EzJZvYN_hrp1Q2JA6o0s-w', refresh_token: 'ehhxnwgvgeyk', expires_at: 1773136334, user: UTSJSONObject2, token_type: 'bearer', …}
login.uvue:175 🔍 开始校验商家端角色 -> UID: 8bdf11be-2838-4d96-8552-0949cde076d4, Email: test19@163.com
login.uvue:216 ❌ 查询角色过程异常: TypeError: res.getData is not a function
at login.uvue:180:23
at Generator.next (<anonymous>)
login.uvue:435 登录错误: Error: 商家身份校验失败,请联系管理员检查用户数据
at login.uvue:221:9
at Generator.next (<anonymous>)。
打印这些东西
GET http://localhost:5173/layouts/admin/AdminLayout.uvue?t=1773216297014&import net::ERR_ABORTED 500 (Internal Server Error)
main.uts:16 [Vue warn]: Unhandled error during execution of async component loader
at <AsyncComponentWrapper>
at <PageBody>
at <Page>
at <Anonymous>
at <KeepAlive>
at <RouterView>
at <Layout>
at <App>
warnHandler @ uni-h5.es.js:19975
callWithErrorHandling @ vue.runtime.esm.js:1381
warn$1 @ vue.runtime.esm.js:1207
logError @ vue.runtime.esm.js:1438
errorHandler @ uni-h5.es.js:19600
callWithErrorHandling @ vue.runtime.esm.js:1381
handleError @ vue.runtime.esm.js:1421
onError @ vue.runtime.esm.js:3724
(anonymous) @ vue.runtime.esm.js:3767
Promise.catch
setup @ vue.runtime.esm.js:3766
callWithErrorHandling @ vue.runtime.esm.js:1381
setupStatefulComponent @ vue.runtime.esm.js:8985
setupComponent @ vue.runtime.esm.js:8946
mountComponent @ vue.runtime.esm.js:7262
processComponent @ vue.runtime.esm.js:7228
patch @ vue.runtime.esm.js:6694
mountChildren @ vue.runtime.esm.js:6942
processFragment @ vue.runtime.esm.js:7158
patch @ vue.runtime.esm.js:6668
mountChildren @ vue.runtime.esm.js:6942
processFragment @ vue.runtime.esm.js:7158
patch @ vue.runtime.esm.js:6668
mountChildren @ vue.runtime.esm.js:6942
mountElement @ vue.runtime.esm.js:6849
processElement @ vue.runtime.esm.js:6814
patch @ vue.runtime.esm.js:6682
mountChildren @ vue.runtime.esm.js:6942
mountElement @ vue.runtime.esm.js:6849
processElement @ vue.runtime.esm.js:6814
patch @ vue.runtime.esm.js:6682
mountChildren @ vue.runtime.esm.js:6942
processFragment @ vue.runtime.esm.js:7158
patch @ vue.runtime.esm.js:6668
componentUpdateFn @ vue.runtime.esm.js:7372
run @ vue.runtime.esm.js:153
instance.update @ vue.runtime.esm.js:7497
setupRenderEffect @ vue.runtime.esm.js:7507
mountComponent @ vue.runtime.esm.js:7274
processComponent @ vue.runtime.esm.js:7228
patch @ vue.runtime.esm.js:6694
mountChildren @ vue.runtime.esm.js:6942
mountElement @ vue.runtime.esm.js:6849
processElement @ vue.runtime.esm.js:6814
patch @ vue.runtime.esm.js:6682
componentUpdateFn @ vue.runtime.esm.js:7372
run @ vue.runtime.esm.js:153
instance.update @ vue.runtime.esm.js:7497
setupRenderEffect @ vue.runtime.esm.js:7507
mountComponent @ vue.runtime.esm.js:7274
processComponent @ vue.runtime.esm.js:7228
patch @ vue.runtime.esm.js:6694
componentUpdateFn @ vue.runtime.esm.js:7372
run @ vue.runtime.esm.js:153
instance.update @ vue.runtime.esm.js:7497
setupRenderEffect @ vue.runtime.esm.js:7507
mountComponent @ vue.runtime.esm.js:7274
processComponent @ vue.runtime.esm.js:7228
patch @ vue.runtime.esm.js:6694
componentUpdateFn @ vue.runtime.esm.js:7453
run @ vue.runtime.esm.js:153
instance.update @ vue.runtime.esm.js:7497
updateComponent @ vue.runtime.esm.js:7305
processComponent @ vue.runtime.esm.js:7239
patch @ vue.runtime.esm.js:6694
componentUpdateFn @ vue.runtime.esm.js:7453
run @ vue.runtime.esm.js:153
instance.update @ vue.runtime.esm.js:7497
callWithErrorHandling @ vue.runtime.esm.js:1381
flushJobs @ vue.runtime.esm.js:1585
Promise.then
queueFlush @ vue.runtime.esm.js:1494
queueJob @ vue.runtime.esm.js:1488
scheduler @ vue.runtime.esm.js:3179
resetScheduling @ vue.runtime.esm.js:236
triggerEffects @ vue.runtime.esm.js:280
triggerRefValue @ vue.runtime.esm.js:1033
set value @ vue.runtime.esm.js:1078
finalizeNavigation @ vue-router.mjs?v=ed041164:2474
(anonymous) @ vue-router.mjs?v=ed041164:2384
Promise.then
pushWithRedirect @ vue-router.mjs?v=ed041164:2352
push @ vue-router.mjs?v=ed041164:2278
install @ vue-router.mjs?v=ed041164:2631
use @ vue.runtime.esm.js:5190
initRouter @ uni-h5.es.js:19886
install @ uni-h5.es.js:19955
use @ vue.runtime.esm.js:5190
(anonymous) @ main.uts:16
main.uts:16 TypeError: Failed to fetch dynamically imported module: http://localhost:5173/pages/mall/admin/homePage/index.uvue?t=1773216297014&import

42
rebuild_html.py Normal file
View File

@@ -0,0 +1,42 @@
import codecs
file_path = r'd:\\骅锋\\mall\\layouts\\admin\\components\\AdminHeader.uvue'
with codecs.open(file_path, 'r', 'utf-8') as f:
text = f.read()
# I will replace the user-profile-container entirely with a solid implementation, including the overlay.
old_html = r''' <!-- 用户个人中心 / 下拉菜单 -->
<view class="user-profile-container" @click="goToUserCenter">
<text class="user-name">crmeb demo</text>
<text class="user-arrow">▼</text>
<view class="user-dropdown" v-if="showUserMenu">
<view class="dropdown-item" @click.stop="goToUserCenter">个人中心</view>
<view class="dropdown-item" @click.stop="handleLogout">退出登录</view>
</view>
</view>'''
new_html = r''' <!-- 点击外部收起菜单的遮罩层 -->
<view class="user-dropdown-overlay" v-if="showUserMenu" @click="closeUserMenu"></view>
<!-- 用户个人中心 / 下拉菜单 -->
<view class="user-profile-container" @click="toggleUserMenu">
<text class="user-name">crmeb demo</text>
<text class="user-arrow">▼</text>
<view class="user-dropdown" v-if="showUserMenu">
<view class="dropdown-item" @click.stop="goToUserCenter">个人中心</view>
<view class="dropdown-item" @click.stop="handleLogout">退出登录</view>
</view>
</view>'''
text = text.replace(old_html, new_html)
# Add closeUserMenu to script if doesn't exist
if 'function closeUserMenu' not in text:
text = text.replace('function toggleUserMenu() {', 'function closeUserMenu() {\n showUserMenu.value = false\n}\n\nfunction toggleUserMenu() {')
# Adjust goToUserCenter to remove toast, just keep logic
text = text.replace('uni.showToast({ title: "尝试打开个人中心...", icon: "none", duration: 2000 });\n console.log([AdminHeader] goToUserCenter called);\n console.log([AdminHeader] goToUserCenter called);', '')
with codecs.open(file_path, 'w', 'utf-8') as f:
f.write(text)

8
remove_broken_log.py Normal file
View File

@@ -0,0 +1,8 @@
import codecs
file_path = r'd:\\骅锋\\mall\\layouts\\admin\\AdminLayout.uvue'
with codecs.open(file_path, 'r', 'utf-8') as f:
text = f.read()
text = text.replace('console.log([AdminLayout] Computing component for activeRouteId: );', '')
with codecs.open(file_path, 'w', 'utf-8') as f:
f.write(text)

10
remove_broken_log2.py Normal file
View File

@@ -0,0 +1,10 @@
import codecs
file_path = r'd:\\骅锋\\mall\\layouts\\admin\\store\\adminNavStore.uts'
with codecs.open(file_path, 'r', 'utf-8') as f:
text = f.read()
text = text.replace('console.log([AdminNavStore] openRoute called with: );', '')
text = text.replace('console.log([AdminNavStore] activeRouteId set to: );', '')
with codecs.open(file_path, 'w', 'utf-8') as f:
f.write(text)

10
remove_broken_log3.py Normal file
View File

@@ -0,0 +1,10 @@
import codecs
file_path = r'd:\\骅锋\\mall\\layouts\\admin\\components\\AdminHeader.uvue'
with codecs.open(file_path, 'r', 'utf-8') as f:
text = f.read()
text = text.replace('console.log([AdminHeader] goToUserCenter called)', 'console.log([AdminHeader] goToUserCenter called)')
text = text.replace('console.log([AdminHeader] goToUserCenter called);', '')
with codecs.open(file_path, 'w', 'utf-8') as f:
f.write(text)

8
remove_broken_log4.py Normal file
View File

@@ -0,0 +1,8 @@
import codecs
file_path = r'd:\\骅锋\\mall\\layouts\\admin\\AdminLayout.uvue'
with codecs.open(file_path, 'r', 'utf-8') as f:
text = f.read()
text = text.replace('console.log([AdminLayout] Computing hasAccess for: );', '')
with codecs.open(file_path, 'w', 'utf-8') as f:
f.write(text)

25
replace_user.py Normal file
View File

@@ -0,0 +1,25 @@
import codecs
content = '''<template>
<view class="user-center-container">
<text style="font-size: 24px; color: red;">userCenter loaded!</text>
</view>
</template>
<script setup lang="uts">
console.log('[UserCenter] Component Setup Executed!')
</script>
<style scoped>
.user-center-container {
padding: 50px;
background-color: #fff;
min-height: 500px;
}
</style>
'''
with codecs.open(r'd:\\骅锋\\mall\\pages\\mall\\admin\\userCenter\\index.uvue', 'w', 'utf-8') as f:
f.write(content)
print("userCenter replaced.")

203
restore_content.py Normal file
View File

@@ -0,0 +1,203 @@
import codecs
content = '''<template>
<view class="user-center-container">
<view class="page-header">
<text class="page-title">个人中心</text>
</view>
<view class="form-container">
<view class="form-item">
<text class="form-label">头像</text>
<view class="form-content avatar-uploader">
<!-- 默认使用一个占位头像或 Logo -->
<image class="avatar-img" src="/static/logo.png" mode="aspectFill"></image>
</view>
</view>
<view class="form-item">
<text class="form-label">账号</text>
<view class="form-content">
<input class="uni-input disabled" value="crmeb demo" disabled placeholder="请输入账号" />
</view>
</view>
<view class="form-item">
<text class="form-label"><text class="required">*</text>姓名</text>
<view class="form-content">
<input class="uni-input" v-model="formData.name" placeholder="请输入姓名" />
</view>
</view>
<view class="form-item">
<text class="form-label">原始密码</text>
<view class="form-content">
<input class="uni-input" type="password" v-model="formData.oldPassword" placeholder="请输入原始密码" />
</view>
</view>
<view class="form-item">
<text class="form-label">新密码</text>
<view class="form-content">
<input class="uni-input" type="password" v-model="formData.newPassword" placeholder="请输入新密码" />
</view>
</view>
<view class="form-item">
<text class="form-label">确认密码</text>
<view class="form-content">
<input class="uni-input" type="password" v-model="formData.confirmPassword" placeholder="请再次输入新密码" />
</view>
</view>
<view class="form-actions">
<button class="submit-btn" type="primary" @click="onSubmit">保存修改</button>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { reactive } from 'vue'
const formData = reactive({
name: 'crmeb demo',
oldPassword: '',
newPassword: '',
confirmPassword: ''
})
const onSubmit = () => {
if (formData.newPassword && formData.newPassword !== formData.confirmPassword) {
uni.showToast({ title: '两次输入的新密码不一致', icon: 'none' })
return
}
if (!formData.name) {
uni.showToast({ title: '姓名不能为空', icon: 'none' })
return
}
uni.showLoading({ title: '保存中...' })
setTimeout(() => {
uni.hideLoading()
uni.showToast({ title: '保存成功(演示)', icon: 'success' })
// 清空密码框
formData.oldPassword = ''
formData.newPassword = ''
formData.confirmPassword = ''
}, 500)
}
</script>
<style scoped>
.user-center-container {
padding: 20px;
background-color: #f5f7f9;
min-height: 100%;
}
.page-header {
margin-bottom: 20px;
}
.page-title {
font-size: 16px;
font-weight: bold;
color: #333;
}
.form-container {
background-color: #fff;
border-radius: 4px;
padding: 30px 20px;
max-width: 800px;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
}
.form-item {
display: flex;
flex-direction: row;
align-items: center;
margin-bottom: 24px;
}
.form-label {
width: 100px;
text-align: right;
margin-right: 16px;
font-size: 14px;
color: #606266;
flex-shrink: 0;
}
.required {
color: #f5222d;
margin-right: 4px;
}
.form-content {
flex: 1;
max-width: 400px;
}
.uni-input {
width: 100%;
height: 36px;
line-height: 36px;
padding: 0 12px;
border: 1px solid #dcdfe6;
border-radius: 4px;
font-size: 14px;
color: #333;
box-sizing: border-box;
}
.uni-input:focus {
border-color: #1890ff;
outline: none;
}
.disabled {
background-color: #f5f7fa;
color: #c0c4cc;
cursor: not-allowed;
}
.avatar-uploader {
display: flex;
justify-content: flex-start;
}
.avatar-img {
width: 80px;
height: 80px;
border-radius: 4px;
background-color: #f0f2f5;
border: 1px dashed #d9d9d9;
}
.form-actions {
margin-top: 40px;
padding-left: 116px;
}
.submit-btn {
background-color: #1890ff;
color: #fff;
font-size: 14px;
height: 36px;
line-height: 36px;
width: 120px;
border-radius: 4px;
border: none;
}
.submit-btn:active {
background-color: #096dd9;
}
</style>
'''
with codecs.open(r'd:\\骅锋\\mall\\pages\\mall\\admin\\userCenter\\index.uvue', 'w', 'utf-8') as f:
f.write(content)
print("个人中心内容重置完成!")

4
script4.py Normal file
View File

@@ -0,0 +1,4 @@
import codecs
for line in codecs.open(r'd:\骅锋\mall\layouts\admin\store\adminNavStore.uts', 'r', 'utf-8'):
if 'findRouteById' in line:
print(line.strip())

4
script5.py Normal file
View File

@@ -0,0 +1,4 @@
import codecs
for line in codecs.open(r'd:\骅锋\mall\layouts\admin\router\adminRoutes.uts', 'r', 'utf-8'):
if 'function findRouteById' in line:
print('Found!')

4
script6.py Normal file
View File

@@ -0,0 +1,4 @@
import codecs
text = codecs.open(r'd:\骅锋\mall\layouts\admin\router\adminRoutes.uts', 'r', 'utf-8').read()
start = text.find('function findRouteById')
print(text[start:start+400])

6
script7.py Normal file
View File

@@ -0,0 +1,6 @@
import codecs
text = codecs.open(r'd:\骅锋\mall\layouts\admin\router\adminRoutes.uts', 'r', 'utf-8').read()
start = text.find('const routes')
if start == -1:
start = text.find('const adminRoutes')
print(text[start:start+400])

5
script8.py Normal file
View File

@@ -0,0 +1,5 @@
import codecs
text = codecs.open(r'd:\骅锋\mall\layouts\admin\AdminLayout.uvue', 'r', 'utf-8').read()
for line in text.split('\n'):
if 'activeRouteId' in line and 'import' in line:
print(line)

5
script9.py Normal file
View File

@@ -0,0 +1,5 @@
import codecs
text = codecs.open(r'd:\骅锋\mall\layouts\admin\AdminLayout.uvue', 'r', 'utf-8').read()
for line in text.split('\n'):
if 'activeRouteId' in line:
print(line.strip())

10
simplify.py Normal file
View File

@@ -0,0 +1,10 @@
import codecs
file_path = r'd:\\骅锋\\mall\\layouts\\admin\\components\\AdminHeader.uvue'
with codecs.open(file_path, 'r', 'utf-8') as f:
text = f.read()
text = text.replace('@click="toggleUserMenu"', '@click="goToUserCenter"')
text = text.replace('function goToUserCenter() {', 'function goToUserCenter() {\n console.log("[AdminHeader] goToUserCenter called");')
with codecs.open(file_path, 'w', 'utf-8') as f:
f.write(text)

10
simplify2.py Normal file
View File

@@ -0,0 +1,10 @@
import codecs
file_path = r'd:\\骅锋\\mall\\layouts\\admin\\store\\adminNavStore.uts'
with codecs.open(file_path, 'r', 'utf-8') as f:
text = f.read()
text = text.replace('function openRoute(routeId: string, addTab: boolean = true): void {', 'function openRoute(routeId: string, addTab: boolean = true): void {\n console.log([AdminNavStore] openRoute called with: );')
text = text.replace('activeRouteId.value = routeId', 'activeRouteId.value = routeId\n console.log([AdminNavStore] activeRouteId set to: );')
with codecs.open(file_path, 'w', 'utf-8') as f:
f.write(text)

8
simplify3.py Normal file
View File

@@ -0,0 +1,8 @@
import codecs
file_path = r'd:\\骅锋\\mall\\layouts\\admin\\AdminLayout.uvue'
with codecs.open(file_path, 'r', 'utf-8') as f:
text = f.read()
text = text.replace('const currentComponent = computed<any>(() => {', 'const currentComponent = computed<any>(() => {\n console.log([AdminLayout] Computing component for activeRouteId: );')
with codecs.open(file_path, 'w', 'utf-8') as f:
f.write(text)

9
simplify4.py Normal file
View File

@@ -0,0 +1,9 @@
import codecs
file_path = r'd:\\骅锋\\mall\\layouts\\admin\\components\\AdminHeader.uvue'
with codecs.open(file_path, 'r', 'utf-8') as f:
text = f.read()
text = text.replace('.user-profile-container {\n position: relative;', '.user-profile-container {\n position: relative;\n z-index: 100;\n pointer-events: auto;')
with codecs.open(file_path, 'w', 'utf-8') as f:
f.write(text)

10
simplify5.py Normal file
View File

@@ -0,0 +1,10 @@
import codecs
file_path = r'd:\\骅锋\\mall\\layouts\\admin\\store\\adminNavStore.uts'
with codecs.open(file_path, 'r', 'utf-8') as f:
text = f.read()
text = text.replace('console.log([AdminNavStore] openRoute called with: );', 'console.log([AdminNavStore] openRoute called with: );')
text = text.replace('console.log([AdminNavStore] activeRouteId set to: );', 'console.log([AdminNavStore] activeRouteId set to: );')
with codecs.open(file_path, 'w', 'utf-8') as f:
f.write(text)

9
simplify6.py Normal file
View File

@@ -0,0 +1,9 @@
import codecs
file_path = r'd:\\骅锋\\mall\\layouts\\admin\\AdminLayout.uvue'
with codecs.open(file_path, 'r', 'utf-8') as f:
text = f.read()
text = text.replace('console.log([AdminLayout] Computing component for activeRouteId: );', 'console.log([AdminLayout] Computing component for activeRouteId: );')
with codecs.open(file_path, 'w', 'utf-8') as f:
f.write(text)

9
simplify7.py Normal file
View File

@@ -0,0 +1,9 @@
import codecs
file_path = r'd:\\骅锋\\mall\\layouts\\admin\\AdminLayout.uvue'
with codecs.open(file_path, 'r', 'utf-8') as f:
text = f.read()
text = text.replace('console.log([AdminLayout] Computing component for activeRouteId: )', 'console.log([AdminLayout] Computing component for activeRouteId: )')
text = text.replace('console.log([AdminHeader] goToUserCenter called)', 'console.log([AdminHeader] goToUserCenter called)')
with codecs.open(file_path, 'w', 'utf-8') as f:
f.write(text)

10
simplify8.py Normal file
View File

@@ -0,0 +1,10 @@
import codecs
file_path = r'd:\\骅锋\\mall\\layouts\\admin\\store\\adminNavStore.uts'
with codecs.open(file_path, 'r', 'utf-8') as f:
text = f.read()
text = text.replace('console.log([AdminNavStore] openRoute called with: );', 'console.log([AdminNavStore] openRoute called with: );')
text = text.replace('console.log([AdminNavStore] activeRouteId set to: );', 'console.log([AdminNavStore] activeRouteId set to: );')
with codecs.open(file_path, 'w', 'utf-8') as f:
f.write(text)

9
simplify9.py Normal file
View File

@@ -0,0 +1,9 @@
import codecs
file_path = r'd:\\骅锋\\mall\\layouts\\admin\\AdminLayout.uvue'
with codecs.open(file_path, 'r', 'utf-8') as f:
text = f.read()
text = text.replace('const hasAccess = computed<boolean>(() => {', 'const hasAccess = computed<boolean>(() => {\n console.log([AdminLayout] Computing hasAccess for: );')
with codecs.open(file_path, 'w', 'utf-8') as f:
f.write(text)

BIN
temp.py Normal file

Binary file not shown.

5
test_html.py Normal file
View File

@@ -0,0 +1,5 @@
import codecs
with codecs.open(r'd:\\骅锋\\mall\\layouts\\admin\\components\\AdminHeader.uvue', 'r', 'utf-8') as f:
text = f.read()
start = text.find('user-profile-container')
print(text[start-20:start+600])

8
test_main.py Normal file
View File

@@ -0,0 +1,8 @@
import codecs
with codecs.open(r'd:\\骅锋\\mall\\layouts\\admin\\AdminLayout.uvue', 'r', 'utf-8') as f:
text = f.read()
import re
match = re.search(r'<view\s+class=[\'"]main-content[\s\S]*?<\/view>', text)
if match:
print(match.group(0)[:500])

6
test_main2.py Normal file
View File

@@ -0,0 +1,6 @@
import codecs
with codecs.open(r'd:\\骅锋\\mall\\layouts\\admin\\AdminLayout.uvue', 'r', 'utf-8') as f:
text = f.read()
start = text.find('class="main-content"')
print(text[start-50:start+300])

25
test_replace_header.py Normal file
View File

@@ -0,0 +1,25 @@
import codecs
import re
with open('layouts/admin/components/AdminHeader.uvue', 'r', encoding='utf-8') as f:
text = f.read()
new_html = r""" <view class="hbtn" @click="$emit('notify')">\n <text>🔔</text>\n <view class="dot" v-if="hasNotification"></view>\n </view>\n \n <!-- 用户个人中心 / 下拉菜单 -->\n <view class="user-profile-container" @click="toggleUserMenu">\n <text class="user-name">crmeb demo</text>\n <text class="user-arrow">▼</text>\n \n <view class="user-dropdown" v-if="showUserMenu">\n <view class="dropdown-item" @click.stop="goToUserCenter">个人中心</view>\n <view class="dropdown-item" @click.stop="handleLogout">退出登录</view>\n </view>\n </view>"""
new_html = new_html.replace(r'\n', '\n')
text = re.sub(r'<view class=\"hbtn\" @click=\"\$emit\(\'notify\'\)\">[\s\S]*?<\/view>\s*<\/view>', new_html + '\n </view>', text)
new_script = r"""import { ref, computed } from 'vue'\nimport {\n toggleSubSider,\n showSubSider,\n layoutMode,\n isOverlayVisible,\n isMobileMenuOpen,\n openRoute\n} from '@/layouts/admin/store/adminNavStore.uts'\n\nconst showUserMenu = ref(false)\n\nfunction toggleUserMenu() {\n showUserMenu.value = !showUserMenu.value\n}\n\nfunction goToUserCenter() {\n showUserMenu.value = false\n openRoute('demo_user_center')\n}\n\nfunction handleLogout() {\n showUserMenu.value = false\n uni.showModal({\n title: '提示',\n content: '确定要退出登录吗?',\n success: (res) => {\n if (res.confirm) {\n uni.reLaunch({\n url: '/pages/index/index'\n })\n }\n }\n })\n}"""
new_script = new_script.replace(r'\n', '\n')
text = re.sub(r'import \{ computed \} from \'vue\'[\s\S]*?\} from \'@/layouts/admin/store/adminNavStore\.uts\'', new_script, text)
new_style = r""".dot{\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background:#ff4d4f;\n position:absolute;\n top: 6px;\n right: 6px;\n}\n\n.user-profile-container {\n position: relative;\n display: flex;\n flex-direction: row;\n align-items: center;\n margin-left: 10px;\n cursor: pointer;\n}\n\n.user-name {\n font-size: 14px;\n color: #333;\n}\n\n.user-arrow {\n font-size: 12px;\n color: #666;\n margin-left: 4px;\n}\n\n.user-dropdown {\n position: absolute;\n top: 40px;\n right: 0;\n width: 120px;\n background-color: #fff;\n border-radius: 4px;\n box-shadow: 0 2px 12px rgba(0,0,0,0.1);\n z-index: 1000;\n display: flex;\n flex-direction: column;\n}\n\n.dropdown-item {\n height: 40px;\n line-height: 40px;\n text-align: center;\n font-size: 14px;\n color: #333;\n cursor: pointer;\n}\n\n.dropdown-item:hover {\n background-color: #f5f7fa;\n color: #1890ff;\n}"""
new_style = new_style.replace(r'\n', '\n')
text = re.sub(r'\.dot\{[\s\S]*?\}', new_style, text)
with open('layouts/admin/components/AdminHeader.uvue', 'w', encoding='utf-8') as f:
f.write(text)

16
test_replace_map.py Normal file
View File

@@ -0,0 +1,16 @@
import codecs
with open('layouts/admin/router/adminComponentMap.uts', 'r', encoding='utf-8') as f:
text = f.read()
text = text.replace(
"import HomeIndex from '@/pages/mall/admin/homePage/index.uvue'",
"import HomeIndex from '@/pages/mall/admin/homePage/index.uvue'\nimport UserCenter from '@/pages/mall/admin/userCenter/index.uvue'"
)
text = text.replace(
"['HomeIndex', HomeIndex],",
"['HomeIndex', HomeIndex],\n ['UserCenter', UserCenter],"
)
with open('layouts/admin/router/adminComponentMap.uts', 'w', encoding='utf-8') as f:
f.write(text)