添加个人中心及按角色展示内容
This commit is contained in:
26
add_overlay_css.py
Normal file
26
add_overlay_css.py
Normal 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
9
add_toast.py
Normal 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
8
fix3.py
Normal 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
11
fix_css2.py
Normal 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
8
fix_final.py
Normal 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
10
fix_header_css.py
Normal 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
BIN
fix_pages.py
Normal file
Binary file not shown.
@@ -63,8 +63,8 @@
|
|||||||
<!-- 内容展示区 (内部路由渲染) -->
|
<!-- 内容展示区 (内部路由渲染) -->
|
||||||
<view class="content-scroll">
|
<view class="content-scroll">
|
||||||
<view class="content-inner" :class="{ 'is-mobile': isMobile }">
|
<view class="content-inner" :class="{ 'is-mobile': isMobile }">
|
||||||
<slot></slot>
|
<slot v-if="hasAccess"></slot>
|
||||||
<component :is="currentComponent" v-if="!isPageLoading && currentComponent != null"></component>
|
<component :is="currentComponent" v-if="hasAccess && !isPageLoading && currentComponent != null"></component>
|
||||||
<AdminPageLoading v-if="isPageLoading"></AdminPageLoading>
|
<AdminPageLoading v-if="isPageLoading"></AdminPageLoading>
|
||||||
</view>
|
</view>
|
||||||
<AdminFooter></AdminFooter>
|
<AdminFooter></AdminFooter>
|
||||||
@@ -116,6 +116,7 @@ import {
|
|||||||
import type { TabItem } from '@/layouts/admin/store/adminNavStore.uts'
|
import type { TabItem } from '@/layouts/admin/store/adminNavStore.uts'
|
||||||
|
|
||||||
import { getComponent } from '@/layouts/admin/router/adminComponentMap.uts'
|
import { getComponent } from '@/layouts/admin/router/adminComponentMap.uts'
|
||||||
|
import { hasAdminModuleAccess } from '@/layouts/admin/utils/role.uts'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
currentPage: {
|
currentPage: {
|
||||||
@@ -131,6 +132,12 @@ const SUB_W = 200
|
|||||||
// 页面加载状态
|
// 页面加载状态
|
||||||
const isPageLoading = ref(false)
|
const isPageLoading = ref(false)
|
||||||
|
|
||||||
|
const hasAccess = computed<boolean>(() => {
|
||||||
|
|
||||||
|
|
||||||
|
return hasAdminModuleAccess(activeTopMenuId.value)
|
||||||
|
})
|
||||||
|
|
||||||
const hasNotification = ref<boolean>(false)
|
const hasNotification = ref<boolean>(false)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -258,6 +265,8 @@ const breadcrumb = computed<Array<{id: string, title: string}>>(() => {
|
|||||||
|
|
||||||
// 当前渲染的组件
|
// 当前渲染的组件
|
||||||
const currentComponent = computed<any>(() => {
|
const currentComponent = computed<any>(() => {
|
||||||
|
|
||||||
|
|
||||||
const route = findRouteById(activeRouteId.value)
|
const route = findRouteById(activeRouteId.value)
|
||||||
if (!route) return null
|
if (!route) return null
|
||||||
return getComponent(route.componentKey)
|
return getComponent(route.componentKey)
|
||||||
|
|||||||
@@ -29,20 +29,70 @@
|
|||||||
<text>🔔</text>
|
<text>🔔</text>
|
||||||
<view class="dot" v-if="hasNotification"></view>
|
<view class="dot" v-if="hasNotification"></view>
|
||||||
</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>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="uts">
|
<script setup lang="uts">
|
||||||
import { computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import {
|
import {
|
||||||
toggleSubSider,
|
toggleSubSider,
|
||||||
showSubSider,
|
showSubSider,
|
||||||
layoutMode,
|
layoutMode,
|
||||||
isOverlayVisible,
|
isOverlayVisible,
|
||||||
isMobileMenuOpen
|
isMobileMenuOpen,
|
||||||
|
openRoute
|
||||||
} from '@/layouts/admin/store/adminNavStore.uts'
|
} 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<{
|
const props = defineProps<{
|
||||||
breadcrumb: Array<{id: string, title: string}>
|
breadcrumb: Array<{id: string, title: string}>
|
||||||
hasNotification: boolean
|
hasNotification: boolean
|
||||||
@@ -82,6 +132,10 @@ const currentTitle = computed((): string => {
|
|||||||
|
|
||||||
<style>
|
<style>
|
||||||
.header{
|
.header{
|
||||||
|
position: relative;
|
||||||
|
z-index: 1001;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1001;
|
||||||
height: 56px;
|
height: 56px;
|
||||||
background:#fff;
|
background:#fff;
|
||||||
border-bottom: 1px solid #eef2f7;
|
border-bottom: 1px solid #eef2f7;
|
||||||
@@ -142,6 +196,12 @@ const currentTitle = computed((): string => {
|
|||||||
display: flex !important;
|
display: flex !important;
|
||||||
}
|
}
|
||||||
.header-right {
|
.header-right {
|
||||||
|
position: relative;
|
||||||
|
z-index: 100;
|
||||||
|
pointer-events: auto;
|
||||||
|
position: relative;
|
||||||
|
z-index: 100;
|
||||||
|
pointer-events: auto;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,6 +212,12 @@ const currentTitle = computed((): string => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.header-right{
|
.header-right{
|
||||||
|
position: relative;
|
||||||
|
z-index: 100;
|
||||||
|
pointer-events: auto;
|
||||||
|
position: relative;
|
||||||
|
z-index: 100;
|
||||||
|
pointer-events: auto;
|
||||||
display:flex;
|
display:flex;
|
||||||
flex-direction:row;
|
flex-direction:row;
|
||||||
align-items:center;
|
align-items:center;
|
||||||
@@ -176,4 +242,64 @@ const currentTitle = computed((): string => {
|
|||||||
top: 6px;
|
top: 6px;
|
||||||
right: 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>
|
</style>
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import PlaceholderPage from '@/layouts/admin/components/PlaceholderPage.uvue'
|
|||||||
|
|
||||||
// 导入首页(内部组件,不包含 AdminLayout)
|
// 导入首页(内部组件,不包含 AdminLayout)
|
||||||
import HomeIndex from '@/layouts/admin/pages/HomeIndex.uvue'
|
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'
|
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([
|
export const componentMap: Map<string, any> = new Map([
|
||||||
// 首页
|
// 首页
|
||||||
['HomeIndex', HomeIndex],
|
['HomeIndex', HomeIndex],
|
||||||
|
['UserCenter', UserCenter],
|
||||||
|
|
||||||
// 用户模块
|
// 用户模块
|
||||||
['UserStatistic', UserStatistic],
|
['UserStatistic', UserStatistic],
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ export type RouteRecord = {
|
|||||||
/**
|
/**
|
||||||
* 菜单分组类型
|
* 菜单分组类型
|
||||||
*/
|
*/
|
||||||
|
import { hasAdminModuleAccess } from '@/layouts/admin/utils/role.uts'
|
||||||
|
|
||||||
export type MenuGroup = {
|
export type MenuGroup = {
|
||||||
id: string
|
id: string
|
||||||
title: string
|
title: string
|
||||||
@@ -232,6 +234,15 @@ export const routes: RouteRecord[] = [
|
|||||||
},
|
},
|
||||||
|
|
||||||
// ========== 用户模块 ==========
|
// ========== 用户模块 ==========
|
||||||
|
// ========== 个人中心 ==========
|
||||||
|
{
|
||||||
|
id: 'home_user_center',
|
||||||
|
title: '个人中心',
|
||||||
|
path: '/pages/mall/admin/userCenter/index',
|
||||||
|
componentKey: 'UserCenter',
|
||||||
|
order: 100
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
id: 'user_statistic',
|
id: 'user_statistic',
|
||||||
title: '用户统计',
|
title: '用户统计',
|
||||||
@@ -1175,7 +1186,10 @@ export const routes: RouteRecord[] = [
|
|||||||
* 获取所有一级菜单
|
* 获取所有一级菜单
|
||||||
*/
|
*/
|
||||||
export function getTopMenus(): TopMenu[] {
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
getTopMenus
|
getTopMenus
|
||||||
} from '@/layouts/admin/router/adminRoutes.uts'
|
} from '@/layouts/admin/router/adminRoutes.uts'
|
||||||
import { addView, activeFullPath, visitedViews } from './tagsViewStore.uts'
|
import { addView, activeFullPath, visitedViews } from './tagsViewStore.uts'
|
||||||
|
import { hasAdminModuleAccess } from '@/layouts/admin/utils/role.uts'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 标签页类型
|
* 标签页类型
|
||||||
@@ -77,15 +78,35 @@ export const isOverlayVisible = ref<boolean>(false)
|
|||||||
* @param addTab 是否添加到标签页
|
* @param addTab 是否添加到标签页
|
||||||
*/
|
*/
|
||||||
export function openRoute(routeId: string, addTab: boolean = true): void {
|
export function openRoute(routeId: string, addTab: boolean = true): void {
|
||||||
|
|
||||||
|
|
||||||
const route = findRouteById(routeId)
|
const route = findRouteById(routeId)
|
||||||
if (!route) {
|
if (!route) {
|
||||||
console.warn(`[AdminNav] Route not found: ${routeId}`)
|
console.warn(`[AdminNav] Route not found: ${routeId}`)
|
||||||
return
|
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
|
activeRouteId.value = routeId
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 更新一级菜单选中态
|
// 更新一级菜单选中态
|
||||||
if (route.parentId) {
|
if (route.parentId) {
|
||||||
activeTopMenuId.value = route.parentId
|
activeTopMenuId.value = route.parentId
|
||||||
|
|||||||
51
layouts/admin/utils/role.uts
Normal file
51
layouts/admin/utils/role.uts
Normal 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
20
make_simple.py
Normal 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)
|
||||||
@@ -14,6 +14,13 @@
|
|||||||
"navigationStyle": "custom"
|
"navigationStyle": "custom"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/mall/admin/userCenter/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "个人中心",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/user/boot",
|
"path": "pages/user/boot",
|
||||||
"style": {
|
"style": {
|
||||||
|
|||||||
195
pages/mall/admin/userCenter/index.uvue
Normal file
195
pages/mall/admin/userCenter/index.uvue
Normal 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>
|
||||||
@@ -1,10 +1,100 @@
|
|||||||
signIn result:
|
GET http://localhost:5173/layouts/admin/AdminLayout.uvue?t=1773216297014&import net::ERR_ABORTED 500 (Internal Server Error)
|
||||||
AkSupaSignInResult {access_token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4Y…HNlfQ.5BvQq26lJUF23AEglMvA7EzJZvYN_hrp1Q2JA6o0s-w', refresh_token: 'ehhxnwgvgeyk', expires_at: 1773136334, user: UTSJSONObject2, token_type: 'bearer', …}
|
main.uts:16 [Vue warn]: Unhandled error during execution of async component loader
|
||||||
login.uvue:175 🔍 开始校验商家端角色 -> UID: 8bdf11be-2838-4d96-8552-0949cde076d4, Email: test19@163.com
|
at <AsyncComponentWrapper>
|
||||||
login.uvue:216 ❌ 查询角色过程异常: TypeError: res.getData is not a function
|
at <PageBody>
|
||||||
at login.uvue:180:23
|
at <Page>
|
||||||
at Generator.next (<anonymous>)
|
at <Anonymous>
|
||||||
login.uvue:435 登录错误: Error: 商家身份校验失败,请联系管理员检查用户数据
|
at <KeepAlive>
|
||||||
at login.uvue:221:9
|
at <RouterView>
|
||||||
at Generator.next (<anonymous>)。
|
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
42
rebuild_html.py
Normal 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
8
remove_broken_log.py
Normal 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
10
remove_broken_log2.py
Normal 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
10
remove_broken_log3.py
Normal 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
8
remove_broken_log4.py
Normal 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
25
replace_user.py
Normal 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
203
restore_content.py
Normal 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
4
script4.py
Normal 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
4
script5.py
Normal 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
4
script6.py
Normal 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
6
script7.py
Normal 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
5
script8.py
Normal 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
5
script9.py
Normal 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
10
simplify.py
Normal 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
10
simplify2.py
Normal 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
8
simplify3.py
Normal 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
9
simplify4.py
Normal 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
10
simplify5.py
Normal 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
9
simplify6.py
Normal 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
9
simplify7.py
Normal 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
10
simplify8.py
Normal 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
9
simplify9.py
Normal 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)
|
||||||
5
test_html.py
Normal file
5
test_html.py
Normal 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
8
test_main.py
Normal 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
6
test_main2.py
Normal 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
25
test_replace_header.py
Normal 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
16
test_replace_map.py
Normal 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)
|
||||||
Reference in New Issue
Block a user