#
zhou zhou
8 天以前 da05dcffeabcb3c460e45666a4804f4ea5a4145a
#
1个文件已删除
7个文件已添加
6个文件已修改
1898 ■■■■■ 已修改文件
.gemini/skills/wms-dev-standards/SKILL.md 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.trae/skills/nvue/SKILL.md 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
common/request.js 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
config/request.js 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
locale/en.json 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
locale/zh-Hans.json 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
main.js 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages.json 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/home/api.js 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/home/home.vue 579 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/index/index.vue 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/login/api.js 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/login/index.nvue 559 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/login/login.vue 438 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.gemini/skills/wms-dev-standards/SKILL.md
New file
@@ -0,0 +1,62 @@
---
name: wms-dev-standards
description: Guidance for developing the WMS uniapp project. Use this when writing new API requests or developing UI components to ensure consistency with uView UI and existing API request patterns.
---
# WMS Project Development Standards
This skill outlines the core development standards and patterns to follow when contributing to the WMS uniapp project.
## API Request Pattern
When adding or modifying API requests, always follow the established pattern in `config/api.js`.
### Core Rules
1. **HTTP Client:** Always use `uni.$u.http` for requests.
2. **Export Format:** Export each API call as a named arrow function.
3. **GET Parameters:** When sending parameters in a `GET` request, they **must** be wrapped in a configuration object with a `params` key.
### Examples
**Correct GET Request (with parameters):**
```javascript
import { http } from '@/config/api.js' // or standard uniapp import
const http = uni.$u.http
// 用户登录 - Wrap parameters in { params }
export const login = (params) => http.get('/login.action', { params })
```
**Correct POST Request:**
```javascript
// POST requests pass data directly as the second argument
export const submitData = (data) => http.post('/submit.action', data)
```
## UI Component Standards
The project relies exclusively on the **uView UI** library for its components.
### Core Rules
1. **Use uView Components:** Always prefer uView components (e.g., `<u-button>`, `<u-input>`, `<u-form>`) over native components (`<button>`, `<input>`) or writing custom CSS when a uView equivalent exists.
2. **Consult Available Components:** The project already has `uview-ui` installed. Check the existing component library capabilities before writing custom layouts.
3. **Styling:** Follow the existing project styling classes and `uni.scss` variables where appropriate.
### Examples
**Correct UI implementation:**
```vue
<template>
  <view class="container">
    <u--form>
      <u-form-item label="Username">
        <u--input v-model="username" placeholder="Enter your username"></u--input>
      </u-form-item>
      <u-button type="primary" @click="handleLogin">Login</u-button>
    </u--form>
  </view>
</template>
```
.trae/skills/nvue/SKILL.md
New file
@@ -0,0 +1,63 @@
---
name: "nvue"
description: "Expert assistant for uni-app nvue (native rendering) development. Invoke when working with .nvue files, native rendering, or performance optimization in uni-app."
---
# Uni-app nvue Development Skill
You are an expert in uni-app nvue (native vue) development. nvue uses a native rendering engine based on Weex, distinct from the WebView-based Vue pages.
## Core Principles
1.  **Environment**: Assumes latest HBuilderX version.
2.  **Native Rendering**: nvue maps to native components. It is NOT a webview.
3.  **Strict Layout**: ONLY Flexbox is supported.
4.  **Text Rendering**: All text MUST be wrapped in `<text>` components.
5.  **Compilation Mode**: In `uni-app` mode (default), styles from `App.vue` are compiled into every `.nvue` file.
## When to Use nvue
Recommend nvue for:
-   **High Performance Lists**: `list`, `recycle-list`, `waterfall`.
-   **Complex Interactions**: `BindingX` for high-performance gestures/animations.
-   **Complex Layouts**: Left/right draggable lists, sticky headers + swiper.
-   **Native Component Coverage**: Overcoming z-index issues with `map`, `video`, `live-pusher`.
-   **Fast Startup**: nvue pages load faster than WebView pages.
## CSS Limitations & Rules
-   **Layout**: `display: flex` is default and only option. No `grid`, `block`, `inline-block`.
-   **Box Model**: Defaults to `border-box`.
-   **Text**:
    -   Must use `<text>Content</text>`.
    -   Only `<text>` supports `font-size`, `color`, `lines` (truncation).
    -   `<text>` cannot contain other components.
-   **Styling**:
    -   **Supported Shorthands**: `border`, `border-top` (etc), `border-radius`, `flex-flow`, `background`.
    -   **Supported Selectors**: Class selectors, descendant, child, adjacent sibling, and general sibling selectors.
    -   **Class Binding**: Only array syntax `:class="['a', 'b']"` is supported. Object syntax is NOT supported.
    -   **Units**: No `%` (mostly). Use `px` (logic pixels) or `rpx`.
    -   **Background**: `background-image` is NOT supported in CSS. Use `<image>` component with `position: absolute`.
    -   **Transparency**: Android components are transparent by default. Set `background-color` to avoid ghosting.
    -   **Overflow**: Android only supports `hidden`. iOS supports `hidden` and `visible`.
## API Usage
-   **DOM Module**:
    -   `dom.addRule('fontFace', { fontFamily: '...', src: "url('...')" })`
    -   `dom.scrollToElement(ref, { offset: 0 })`
    -   `dom.getComponentRect(ref, callback)` (Use ref `'viewport'` for screen dims).
-   **Native Plugins**:
    -   `const plugin = uni.requireNativePlugin('PluginName')`
    -   **BindingX**: Built-in for high-performance expression binding.
    -   **Animation**: `uni.requireNativePlugin('animation')`.
-   **Transition**: Supports `width`, `height`, `backgroundColor`, `opacity`, `transform`.
## Event Handling
-   **Events**: `click`, `longpress`, `appear` (view becomes visible), `disappear`.
    -   `appear`/`disappear` provide `direction` (up/down).
-   **Event Penetration (iOS)**: `<view eventPenetrationEnabled="true">` allows clicks to pass through to underlying layers.
-   **User Interaction (iOS)**: `<view userInteractionEnabled="false">` disables all interaction.
## Code Generation Guidelines
1.  Always verify if the file is `.nvue`.
2.  If user asks for CSS that is invalid in nvue (e.g., `background-image`, `display: grid`), STOP and explain the limitation, then provide the nvue-compatible workaround.
3.  Use `<list>` or `<waterfall>` for long lists instead of `<scroll-view>` for better performance.
4.  Ensure all text nodes are strictly inside `<text>`.
5.  Prefer `BindingX` for complex touch interactions.
common/request.js
File was deleted
config/request.js
New file
@@ -0,0 +1,86 @@
// 此vm参数为页面的实例,可以通过它引用vuex中的变量
module.exports = (vm) => {
    // 初始化请求配置
    uni.$u.http.setConfig((config) => {
        /* config 为默认全局配置*/
        // 动态设置 baseURL
        let settings = uni.getStorageSync('app_settings');
        if (!settings) {
            settings = {
                ip: '127.0.0.1',
                port: '8080',
                project: 'jshdasrs'
            };
            // uni.setStorageSync('app_settings', settings);
        }
        config.baseURL = `http://${settings.ip}:${settings.port}/${settings.project}`;
        config.header = {
            'content-type': 'application/json'
        };
        return config;
    })
    // 请求拦截
    uni.$u.http.interceptors.request.use((config) => { // 可使用async await 做异步操作
        // 初始化请求拦截器时,会执行此方法,此时data为undefined,赋予默认{}
        config.data = config.data || {}
        // 提示加载框逻辑(根据 custom.hideLoading 决定)
        const hideLoading = config.custom?.hideLoading;
        if (hideLoading === false || hideLoading === undefined) {
            uni.showLoading({
                title: '请稍候...',
                mask: true
            });
        }
        // 根据custom参数中配置的是否需要token,添加对应的请求头
        // 默认或显式 auth 为 true 时添加 token
        if (config?.custom?.auth !== false) {
            const token = uni.getStorageSync('token');
            if (token) {
                config.header.token = token;
            }
        }
        return config
    }, config => { // 可使用async await 做异步操作
        return Promise.reject(config)
    })
    // 响应拦截
    uni.$u.http.interceptors.response.use((response) => {
        /* 对响应成功做点什么 可使用async await 做异步操作*/
        const hideLoading = response.config?.custom?.hideLoading;
        if (hideLoading === false || hideLoading === undefined) {
            uni.hideLoading();
        }
        const data = response.data
        // 自定义参数
        const custom = response.config?.custom || {}
        if (data.code !== 200) {
            // 如果没有显式定义custom的toast参数为false的话,默认对报错进行toast弹出提示
            if (custom.toast !== false) {
                uni.$u.toast(data.msg || data.message || '请求失败')
            }
            // 如果需要catch返回,则进行reject
            if (custom?.catch) {
                return Promise.reject(data)
            } else {
                // 否则返回一个pending中的promise,请求不会进入catch中
                return new Promise(() => {})
            }
        }
        // 根据示例,返回业务数据
        return data === undefined ? {} : data
    }, (response) => {
        // 对响应错误做点什么 (statusCode !== 200)
        const hideLoading = response.config?.custom?.hideLoading;
        if (hideLoading === false || hideLoading === undefined) {
            uni.hideLoading();
        }
        return Promise.reject(response)
    })
}
locale/en.json
@@ -41,5 +41,19 @@
  },
  "other":{
      
  },
  "settings": {
      "title": "Settings",
      "ip": "IP Address",
      "port": "Port",
      "project": "Project Name",
      "saved": "Settings Saved",
      "authTitle": "Authentication",
      "inputAuthPwd": "Enter Admin Password",
      "authError": "Incorrect Password"
  },
  "common": {
      "confirm": "Confirm",
      "cancel": "Cancel"
  }
}
locale/zh-Hans.json
@@ -43,7 +43,19 @@
    "other":{
        "asnNo":"ASN单号"
        
    },
    "settings": {
        "title": "设置",
        "ip": "IP地址",
        "port": "端口",
        "project": "项目名",
        "saved": "设置已保存",
        "authTitle": "身份验证",
        "inputAuthPwd": "请输入管理员密码",
        "authError": "密码错误"
    },
    "common": {
        "confirm": "确认",
        "cancel": "取消"
    }
}
main.js
@@ -11,25 +11,33 @@
import './uni.promisify.adaptor'
Vue.config.productionTip = false
App.mpType = 'app'
import VueI18n from 'vue-i18n'
Vue.use(VueI18n)
import uView from '@/uni_modules/uview-ui'
Vue.use(uView)
const i18n = new VueI18n(i18nConfig)
const app = new Vue({
    ...App,
    i18n
})
// 引入请求封装,将app参数传递到配置中
require('./config/request.js')(app)
app.$mount()
// #endif
import uView from '@/uni_modules/uview-ui'
Vue.use(uView)
// #ifdef VUE3
import {
    createSSRApp
} from 'vue'
import uView from '@/uni_modules/uview-ui'
export function createApp() {
    const app = createSSRApp(App)
    app.use(uView)
    return {
        app
    }
pages.json
@@ -1,6 +1,5 @@
{
    "pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
        {
            "path": "pages/login/login",
            "style": {
@@ -9,10 +8,11 @@
            }
        },
        {
            "path": "pages/index/index",
            "path": "pages/home/home",
            "style": {
                "navigationBarTitleText": "首页",
                "navigationStyle": "custom"
            }
        },
        {
pages/home/api.js
New file
@@ -0,0 +1,4 @@
const http = uni.$u.http
// 获取动态菜单
export const getAuthMenu = (data, config = {}) => http.post('/menu/pda/auth', data, config)
pages/home/home.vue
New file
@@ -0,0 +1,579 @@
<template>
    <view class="page-container">
        <!-- 头部导航 -->
        <u-navbar
            title="Zoneyung 工作台"
            :fixed="true"
            :placeholder="true"
            bgColor="#ffffff"
            titleStyle="font-weight: 600; color: #303133; font-size: 32rpx;"
            leftIcon=""
        ></u-navbar>
        <!-- 欢迎信息 / 用户信息 -->
        <view class="welcome-box">
            <view class="welcome-info">
                <text class="greeting">欢迎回来</text>
                <text class="date-text">中扬立库 WMS 仓储管理系统</text>
            </view>
            <view class="welcome-avatar">
                <!-- 可替换为真实头像 -->
                <u-avatar
                    icon="account-fill"
                    fontSize="24"
                    randomBgColor
                    size="45"
                ></u-avatar>
            </view>
        </view>
        <!-- 功能菜单 -->
        <view class="menu-section">
            <!-- 分类模式 -->
            <template v-if="useCategoryMode">
                <view
                    class="category-panel"
                    v-for="(category, catIndex) in categorizedMenus"
                    :key="category.key"
                >
                    <view
                        class="category-header"
                        @click="toggleCategory(category.key)"
                    >
                        <view class="category-title-wrapper">
                            <view class="title-indicator"></view>
                            <text class="category-title">
                                {{ category.name }}
                            </text>
                        </view>
                        <view class="category-action">
                            <uni-icons
                                :type="
                                    expandedCategories.includes(category.key)
                                        ? 'up'
                                        : 'down'
                                "
                                size="16"
                                color="#909399"
                            ></uni-icons>
                        </view>
                    </view>
                    <view
                        class="category-body"
                        v-show="expandedCategories.includes(category.key)"
                    >
                        <u-grid
                            :col="4"
                            :border="false"
                        >
                            <u-grid-item
                                v-for="(item, index) in category.items"
                                :key="index"
                                @click="navigateTo(item)"
                                customStyle="padding-top: 15px; padding-bottom: 15px;"
                            >
                                <view
                                    class="grid-icon-box"
                                    :style="{
                                        backgroundColor: getBgColor(item.color)
                                    }"
                                >
                                    <uni-icons
                                        :type="getIconType(item)"
                                        size="24"
                                        color="#ffffff"
                                    ></uni-icons>
                                </view>
                                <text class="grid-text">{{ item.title }}</text>
                            </u-grid-item>
                        </u-grid>
                    </view>
                </view>
                <!-- 未分类菜单 -->
                <view
                    class="category-panel"
                    v-if="uncategorizedMenus.length > 0"
                >
                    <view class="category-header">
                        <view class="category-title-wrapper">
                            <view class="title-indicator"></view>
                            <text class="category-title">其他操作</text>
                        </view>
                    </view>
                    <view class="category-body">
                        <u-grid
                            :col="4"
                            :border="false"
                        >
                            <u-grid-item
                                v-for="(item, index) in uncategorizedMenus"
                                :key="index"
                                @click="navigateTo(item)"
                                customStyle="padding-top: 15px; padding-bottom: 15px;"
                            >
                                <view
                                    class="grid-icon-box"
                                    :style="{
                                        backgroundColor: getBgColor(item.color)
                                    }"
                                >
                                    <uni-icons
                                        :type="getIconType(item)"
                                        size="24"
                                        color="#ffffff"
                                    ></uni-icons>
                                </view>
                                <text class="grid-text">{{ item.title }}</text>
                            </u-grid-item>
                        </u-grid>
                    </view>
                </view>
            </template>
            <!-- 平铺模式 -->
            <template v-else>
                <view class="category-panel">
                    <view class="category-body">
                        <u-grid
                            :col="4"
                            :border="false"
                        >
                            <u-grid-item
                                v-for="(item, index) in elements"
                                :key="index"
                                @click="navigateTo(item)"
                                customStyle="padding-top: 15px; padding-bottom: 15px;"
                            >
                                <view
                                    class="grid-icon-box"
                                    :style="{
                                        backgroundColor: getBgColor(item.color)
                                    }"
                                >
                                    <uni-icons
                                        :type="getIconType(item)"
                                        size="24"
                                        color="#ffffff"
                                    ></uni-icons>
                                </view>
                                <text class="grid-text">{{ item.title }}</text>
                            </u-grid-item>
                        </u-grid>
                    </view>
                </view>
            </template>
        </view>
        <!-- 底部版权 -->
        <view class="footer">
            <text class="footer-text">
                copyright © 2022 浙江中扬立库有限公司 all rights reserved.
            </text>
        </view>
    </view>
</template>
<script>
import { getAuthMenu } from './api.js'
export default {
    data() {
        return {
            baseUrl: '',
            token: '',
            icon: '',
            elements: [],
            // 是否使用分类模式(true: 上下层折叠菜单模式, false: 平铺菜单模式)
            useCategoryMode: true,
            // 默认菜单数据(用于后端无返回时)
            elements2: [
                {
                    title: '组托',
                    name: 'pakin',
                    color: 'cyan',
                    cuIcon: 'pullup',
                    url: '/pakin/pakin'
                },
                {
                    title: '订单入库',
                    name: 'orderPakin',
                    color: 'purple',
                    cuIcon: '',
                    url: '/order/orderList'
                },
                {
                    title: 'AGV容器入库',
                    name: 'agv_start',
                    color: 'blue',
                    cuIcon: '',
                    url: '/AGV/agv_start'
                },
                {
                    title: 'AGV容器回库',
                    name: 'agv_back',
                    color: 'orange',
                    cuIcon: '',
                    url: '/AGV/agv_back'
                },
                {
                    title: '库存查询',
                    name: 'stockQuery',
                    color: 'green',
                    cuIcon: '',
                    url: '/stock/stockQuery'
                },
                {
                    title: '退出登录',
                    name: 'logOut',
                    color: 'grey',
                    cuIcon: 'exit',
                    url: '/login/logOut'
                }
            ],
            colorList: [
                'cyan',
                'purple',
                'blue',
                'pink',
                'orange',
                'green',
                'mauve',
                'brown',
                'olive',
                'red',
                'yellow',
                'grey'
            ],
            // 图标映射
            iconMap: {
                pakin: 'upload',
                orderPakin: 'list',
                stockQuery: 'search',
                logOut: 'redo',
                orderPutOn: 'top',
                orderPutDown: 'bottom',
                restock: 'refresh',
                stockCheck: 'checkbox',
                agv_start: 'navigate',
                agv_back: 'refresh'
            },
            // 菜单分类配置(仅分类模式使用)
            // key: 分类唯一标识
            // name: 分类显示名称
            // color: 分类图标颜色
            // icon: 分类图标
            // menuNames: 该分类包含的菜单name列表
            categoryConfig: [
                {
                    key: 'pakin_category',
                    name: '入库管理',
                    color: 'cyan',
                    icon: 'upload',
                    menuNames: ['pakin', 'orderPakin']
                },
                {
                    key: 'agv_category',
                    name: 'AGV管理',
                    color: 'blue',
                    icon: 'navigate',
                    menuNames: ['agv_start', 'agv_back']
                },
                {
                    key: 'stock_category',
                    name: '库存管理',
                    color: 'green',
                    icon: 'search',
                    menuNames: ['stockQuery']
                }
            ],
            // 不参与分类的菜单项(如退出登录,始终单独显示)
            excludeFromCategory: ['logOut'],
            // 当前展开的分类列表
            expandedCategories: [
                'pakin_category',
                'agv_category',
                'stock_category'
            ]
        }
    },
    computed: {
        // 分类后的菜单数据(过滤掉空分类)
        categorizedMenus() {
            return this.categoryConfig
                .map((category) => {
                    const items = this.elements.filter((item) =>
                        category.menuNames.includes(item.name)
                    )
                    return {
                        ...category,
                        items: items
                    }
                })
                .filter((category) => category.items.length > 0)
        },
        // 未分类的菜单(不在任何分类中或在排除列表中)
        uncategorizedMenus() {
            const allCategorizedNames = this.categoryConfig.reduce(
                (acc, c) => acc.concat(c.menuNames || []),
                []
            )
            return this.elements.filter(
                (item) =>
                    !allCategorizedNames.includes(item.name) ||
                    this.excludeFromCategory.includes(item.name)
            )
        }
    },
    onShow() {
        this.baseUrl = uni.getStorageSync('baseUrl')
        this.token = uni.getStorageSync('token')
        this.getAuth()
    },
    methods: {
        // 切换分类展开/收起
        toggleCategory(categoryKey) {
            const index = this.expandedCategories.indexOf(categoryKey)
            if (index > -1) {
                this.expandedCategories.splice(index, 1)
            } else {
                this.expandedCategories.push(categoryKey)
            }
        },
        // 获取图标类型
        getIconType(item) {
            return this.iconMap[item.name] || 'circle'
        },
        // 颜色映射到图片上的企业色
        getBgColor(colorStr) {
            const map = {
                cyan: '#00ced1',
                purple: '#8a2be2',
                blue: '#409eff',
                pink: '#ff49db',
                orange: '#e6a23c',
                green: '#67c23a',
                mauve: '#b070cc',
                brown: '#874d28',
                olive: '#8e9e30',
                red: '#f56c6c',
                yellow: '#e6a23c',
                grey: '#909399'
            }
            return map[colorStr] || '#409eff'
        },
        // 跳转页面
        navigateTo(item) {
            if (item.name === 'logOut') {
                uni.reLaunch({
                    url: '/pages/login/login'
                })
                return
            }
            uni.navigateTo({
                url: '/pages' + item.url
            })
        },
        async getAuth() {
            try {
                // custom.catch=true ensures errors throw into the catch block
                // custom.toast=false disables the interceptor's default toast so we can handle it manually
                const res = await getAuthMenu(
                    {},
                    { custom: { catch: true, toast: false } }
                )
                this.elements = []
                if (
                    res.data == undefined ||
                    res.data == null ||
                    res.data === ''
                ) {
                    this.elements = this.elements2
                    return
                }
                for (let i = 0; i < res.data.length; i++) {
                    this.getIcon(res.data[i].title)
                    this.elements.unshift({
                        title: res.data[i].name,
                        name: res.data[i].title,
                        color: this.colorList[i % this.colorList.length],
                        cuIcon: this.icon,
                        url: res.data[i].action
                    })
                }
                this.elements.push({
                    title: '退出登录',
                    name: 'logOut',
                    color: 'grey',
                    cuIcon: 'exit',
                    url: '/login/logOut'
                })
            } catch (err) {
                console.log('getAuth error:', err)
                this.elements = this.elements2 // Fallback to default menus
                const errCode = err.code || err.statusCode
                const errMsg =
                    err.msg ||
                    err.message ||
                    (err.data && (err.data.msg || err.data.message))
                if (errCode === 403) {
                    uni.showToast({
                        title: errMsg || '无权限或登录过期',
                        icon: 'none',
                        position: 'top'
                    })
                    setTimeout(() => {
                        uni.reLaunch({
                            url: '../login/login'
                        })
                    }, 1000)
                } else {
                    if (errMsg) {
                        uni.showToast({
                            title: errMsg,
                            icon: 'none',
                            position: 'top'
                        })
                    }
                }
            }
        },
        getIcon(e) {
            const ways = ['pakin', 'orderPakin', 'orderPutOn']
            if (ways.includes(e)) {
                this.icon = 'pullup'
            }
        }
    }
}
</script>
<style>
page {
    background: #f0f2f5; /* 类似图片中浅灰白的工作区背景 */
}
.page-container {
    min-height: 100vh;
    background-color: #f0f2f5;
}
/* 欢迎区域 */
.welcome-box {
    background-color: #ffffff;
    padding: 40rpx 40rpx;
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 24rpx;
    border-bottom: 1px solid #ebeef5; /* 轻微边框 */
}
.welcome-info {
    display: flex;
    flex-direction: column;
}
.greeting {
    font-size: 36rpx;
    color: #303133;
    font-weight: 600;
    margin-bottom: 8rpx;
}
.date-text {
    font-size: 26rpx;
    color: #909399;
}
/* 菜单区域 */
.menu-section {
    padding: 0 24rpx;
}
/* 分类面板风格:洁白,轻量阴影/边框 */
.category-panel {
    background-color: #ffffff;
    border-radius: 12rpx;
    margin-bottom: 24rpx;
    overflow: hidden;
    box-shadow: 0 2rpx 12rpx 0 rgba(0, 0, 0, 0.05); /* Element UI 常用浅阴影 */
}
.category-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 30rpx 24rpx;
    border-bottom: 1px solid #ebeef5;
    background-color: #fafafa; /* 轻微背景区分 */
}
.category-title-wrapper {
    display: flex;
    align-items: center;
}
/* 标题左侧垂直蓝色指示线 */
.title-indicator {
    width: 6rpx;
    height: 30rpx;
    background-color: #409eff;
    border-radius: 4rpx;
    margin-right: 16rpx;
}
.category-title {
    font-size: 30rpx;
    color: #303133;
    font-weight: 600;
}
.category-action {
    display: flex;
    align-items: center;
}
.category-body {
    padding: 10rpx 0;
}
/* 菜单九宫格项 */
.grid-icon-box {
    width: 88rpx;
    height: 88rpx;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    margin-bottom: 16rpx;
    transition: transform 0.2s;
}
.grid-icon-box:active {
    transform: scale(0.9);
}
.grid-text {
    font-size: 26rpx;
    color: #606266;
    margin-top: 10rpx;
}
/* 底部 */
.footer {
    padding: 40rpx 0;
    text-align: center;
}
.footer-text {
    font-size: 24rpx;
    color: #c0c4cc;
}
</style>
pages/index/index.vue
@@ -1,6 +1,9 @@
<template>
    <div>
        <uni-nav-bar :right-text="userName" title="首页" />
        <uni-nav-bar
            :right-text="userName"
            title="首页"
        />
    </div>
</template>
@@ -9,7 +12,7 @@
        data() {
            return {
                userName: '123'
            };
        }
        },
        onLoad(obj) {
            console.log(obj.name)
@@ -19,6 +22,4 @@
    }
</script>
<style>
</style>
<style></style>
pages/login/api.js
New file
@@ -0,0 +1,7 @@
const http = uni.$u.http
// 用户登录
export const login = (params) => http.get('/login.action', { params })
// 这里可以继续添加其他接口
// export const getInfo = (data) => http.get('/user/info', data)
pages/login/index.nvue
New file
@@ -0,0 +1,559 @@
<template>
    <view class="bodyView">
        <view>
            <uni-nav-bar :fixed="true" :statusBar="true" :title="$t('page.login')" right-icon="gear" @clickRight="openSettings"></uni-nav-bar>
        </view>
        <!-- <image class="bgImage" src="/static/img/login_backg.png" mode="aspectFill"></image> -->
        <view class="topView">
            <image src="/static/img/login_top.png" mode="aspectFill">
            </image>
        </view>
        <view class="logoView">
            <image src="/static/img/newLogo.png" mode="aspectFit">
            </image>
        </view>
        <view class="bottomView">
            <view class="itemView" style="margin-bottom: 20px;">
                <text class="helloText">{{$t('index.hello')}}</text>
                <text class="introText">{{$t('index.intro')}}</text>
            </view>
            <view class="itemView">
                <text class="textType3">{{$t('login.user')}}:</text>
                <u-input clearable class="" v-model="user.userName" focus :placeholder="$t('login.inputUser')" />
            </view>
            <view class="itemView">
                <text class="textType3">{{$t('login.pwd')}}:</text>
                <u-input :password="showPassword" v-model="user.password" :placeholder="$t('login.inputPwd')"
                    suffixIcon="map-fill" suffixIconStyle="color: #909399">
                    <template slot="suffix">
                        <u-icon :name="pwdIcon" @click="changePassword"></u-icon>
                    </template>
                </u-input>
            </view>
            <view class="langAndRemView">
                <view class="check">
                    <view>
                        <text>{{$t('login.remPwd')}}</text>
                    </view>
                    <view>
                        <u-switch space="2" size="20" v-model="remberPassword" activeColor="#f9ae3d"
                            inactiveColor="rgb(230, 230, 230)">
                        </u-switch>
                        <!-- <switch :checked='remberPassword' color="#FFCC33" style="transform:scale(0.7)"
                            @change="remberChange" /> -->
                    </view>
                </view>
                <view class="langView">
                    <!-- 语言选择下拉菜单 -->
                    <view class="language-dropdown">
                        <view class="selected-language" @click="toggleLanguageDropdown">
                            <text>{{getCurrentLanguageText()}}</text>
                            <u-icon name="list" size="14" color="#707070"></u-icon>
                        </view>
                        <view class="language-options" v-if="showLanguageDropdown">
                            <view class="language-option" v-for="(item, index) in locales" :key="index"
                                @click="onLocaleChange(item)">
                                <text>{{item.text}}</text>
                                <u-icon name="checkbox-mark" color="#007AFF"
                                    v-if="item.code == applicationLocale"></u-icon>
                            </view>
                        </view>
                    </view>
                </view>
            </view>
            <view class="itemView">
                <u-button class="loadingButton" @click="onLogin()" :loading="loading">{{btnText}}</u-button>
            </view>
        </view>
        <u-popup :show="showAuth" @close="showAuth = false" mode="center" :round="14" :customStyle="{width: '500rpx'}">
            <view class="settings-popup">
                <view class="settings-title">
                    <text class="settings-title-text">{{$t('settings.authTitle') || '身份验证'}}</text>
                </view>
                <view class="settings-item">
                    <u-input v-model="authPassword" type="password" border="surround" :placeholder="$t('settings.inputAuthPwd') || '请输入管理员密码'" />
                </view>
                <view class="settings-buttons">
                    <u-button size="medium" @click="showAuth = false">{{$t('common.cancel') || '取消'}}</u-button>
                    <view style="width: 20px;"></view>
                    <u-button size="medium" type="primary" @click="checkAuth">{{$t('common.confirm') || '确认'}}</u-button>
                </view>
            </view>
        </u-popup>
        <u-popup :show="showSettings" @close="showSettings = false" mode="center" :round="14" :closeOnClickOverlay="false" :customStyle="{width: '600rpx'}">
            <view class="settings-popup">
                <view class="settings-title">
                    <text class="settings-title-text">{{$t('settings.title') || '设置'}}</text>
                </view>
                <view class="settings-item">
                    <text class="settings-label">{{$t('settings.ip') || 'IP地址'}}:</text>
                    <u-input v-model="settings.ip" border="surround" />
                </view>
                <view class="settings-item">
                    <text class="settings-label">{{$t('settings.port') || '端口'}}:</text>
                    <u-input v-model="settings.port" border="surround" />
                </view>
                <view class="settings-item">
                    <text class="settings-label">{{$t('settings.project') || '项目名'}}:</text>
                    <u-input v-model="settings.project" border="surround" />
                </view>
                <view class="settings-buttons">
                    <u-button size="medium" @click="showSettings = false">{{$t('common.cancel') || '取消'}}</u-button>
                    <view style="width: 20px;"></view>
                    <u-button size="medium" type="primary" @click="saveSettings">{{$t('common.confirm') || '确认'}}</u-button>
                </view>
            </view>
        </u-popup>
        <u-toast ref="uToast"></u-toast>
    </view>
</template>
<script>
    import md5 from '../../static/js/md5.js'
    import { login } from './api.js'
    export default {
        data() {
            return {
                showPassword: true,
                loading: false,
                showLanguageDropdown: false,
                loginButton: 'login.login',
                systemLocale: '',
                applicationLocale: '',
                remberPassword: true,
                user: {
                    userName: '',
                    password: '',
                },
                passwordIcon: 'eye-off',
                showSettings: false,
                settings: {
                    ip: '',
                    port: '',
                    project: ''
                },
                showAuth: false,
                authPassword: ''
            }
        },
        computed: {
            locales() {
                return [{
                        text: this.$t('locale.auto'),
                        code: 'auto'
                    }, {
                        text: this.$t('locale.en'),
                        code: 'en'
                    },
                    {
                        text: this.$t('locale.zh-hans'),
                        code: 'zh-Hans'
                    },
                    {
                        text: this.$t('locale.zh-hant'),
                        code: 'zh-Hant'
                    },
                    {
                        text: this.$t('locale.ja'),
                        code: 'ja'
                    }
                ]
            },
            btnText() {
                return this.$t(this.loginButton);
            },
            pwdIcon() {
                return this.passwordIcon;
            }
        },
        onLoad() {
            let systemInfo = uni.getSystemInfoSync();
            this.systemLocale = systemInfo.language;
            this.applicationLocale = uni.getLocale();
            this.isAndroid = systemInfo.platform.toLowerCase() === 'android';
            uni.onLocaleChange((e) => {
                this.applicationLocale = e.locale;
            })
            this.user = uni.getStorageSync('user')
            if (!this.user) {
                this.user = {
                    userName: '',
                    password: ''
                }
            }
        },
        methods: {
            openSettings() {
                this.showAuth = true;
                this.authPassword = '';
            },
            checkAuth() {
                // Default password: admin or 123456. Ideally from config.
                if (this.authPassword === 'admin' || this.authPassword === '123456') {
                    this.showAuth = false;
                    this.loadSettings();
                } else {
                    this.$refs.uToast.show({
                        type: 'error',
                        message: this.$t('settings.authError') || '密码错误',
                    });
                }
            },
            loadSettings() {
                this.showSettings = true;
                let settings = uni.getStorageSync('app_settings');
                if (!settings) {
                    settings = {
                        ip: '127.0.0.1',
                        port: '8080',
                        project: 'jshdasrs'
                    };
                }
                this.settings = settings;
            },
            saveSettings() {
                uni.setStorageSync('app_settings', this.settings);
                this.showSettings = false;
                this.$refs.uToast.show({
                    type: 'success',
                    message: this.$t('settings.saved') || '设置已保存',
                });
            },
                        async onLogin() {
                            try {
                                const res = await login({
                                    username: this.user.userName,
                                    password: md5.hex_md5(this.user.password)
                                }, {
                                    custom: {
                                        catch: true
                                    }
                                })
                                this.loading = true;
                                this.loginButton = 'login.loging';
                                uni.setStorageSync('token', res.data.accessToken);
                                uni.setStorageSync('userData', res.data.username);
                                if (this.remberPassword) {
                                    uni.setStorageSync('user', this.user);
                                } else {
                                    uni.removeStorageSync('user');
                                }
                                this.goHome()
                            } catch (e) {
                                // 拦截器已处理 toast
                            }
                        },
            goHome() {
                setTimeout(() => {
                    this.$refs.uToast.show({
                        type: 'success',
                        message: "登录成功",
                        position: 'top'
                    });
                    setTimeout(() => {
                        uni.$u.route({
                            url: 'pages/index/index',
                            params: {
                                name: 'lisa'
                            }
                        })
                    }, 300)
                }, 700)
            },
            remberChange(e) {
                this.remberPassword = !this.remberPassword
            },
            // 显示/隐藏密码
            changePassword() {
                this.passwordIcon = !this.showPassword ? 'eye-off' : 'eye'
                this.showPassword = !this.showPassword;
            },
            localChange() {
                console.log(this.local)
                if (this.isAndroid) {
                    uni.showModal({
                        content: this.$t('index.language-change-confirm'),
                        success: (res) => {
                            if (res.confirm) {
                                uni.setLocale(this.local.value);
                            }
                        }
                    })
                } else {
                    uni.setLocale(this.local.value);
                    this.$i18n.locale = this.local.value;
                }
            },
            // 切换语言下拉菜单显示状态
            toggleLanguageDropdown() {
                this.showLanguageDropdown = !this.showLanguageDropdown;
            },
            // 获取当前选择的语言文本
            getCurrentLanguageText() {
                const currentLocale = this.locales.find(item => item.code === this.applicationLocale);
                return currentLocale ? currentLocale.text : this.$t('locale.auto');
            },
            // 语言选择改变
            onLocaleChange(e) {
                if (this.isAndroid) {
                    uni.showModal({
                        content: this.$t('index.language-change-confirm'),
                        success: (res) => {
                            if (res.confirm) {
                                uni.setLocale(e.code);
                                this.showLanguageDropdown = false;
                            }
                        }
                    })
                } else {
                    uni.setLocale(e.code);
                    this.$i18n.locale = e.code;
                    this.showLanguageDropdown = false;
                }
            },
        }
    }
</script>
<style lang="scss" scoped>
    .helloText {
        font-family: a2;
        font-size: 20px;
        margin-top: 10px;
        margin-bottom: 5px;
    }
    .introText {
        font-family: a3;
        font-size: 15px;
        color: #ababab;
    }
    .textType3 {
        font-family: a4;
    }
    .bodyView {
        flex: 1;
        flex-direction: column;
        width: 750rpx;
    }
    .bgImage {
        position: fixed;
        top: 0;
        left: 0;
        bottom: 0;
        right: 0;
    }
    .topView {
        flex: 7;
    }
    .topView image {
        width: 100%;
    }
    .logoView {
        flex: 1;
        display: flex;
        justify-content: flex-end;
        align-items: flex-end;
    }
    .logoView image {
        width: 33%;
        height: 50px;
        margin-right: 20px;
    }
    .bottomView {
        flex: 15;
        display: flex;
        flex-direction: column;
        justify-content: flex-start;
        align-items: center;
    }
    .itemView {
        width: 90%;
        margin-bottom: 5px;
    }
    .langAndRemView {
        width: 90%;
        display: flex;
        flex-direction: row;
        justify-content: space-between;
        align-items: center;
        margin-bottom: 5px;
    }
    .langView {
        width: 30%;
    }
    .textImage {
        width: 60%;
        height: 42px;
        object-fit: cover;
        margin-top: 20px;
        margin-bottom: 30px;
    }
    .input-wrapper {
        /* #ifndef APP-NVUE */
        display: flex;
        /* #endif */
        padding: 8px 13px;
        flex-direction: row;
        flex-wrap: nowrap;
        background-color: #FFFFFF;
        border-radius: 10px;
        height: 45px;
        align-items: center;
        margin-top: 5px;
    }
    .uni-input {
        height: 28px;
        line-height: 28px;
        font-size: 15px;
        padding: 0px;
        flex: 1;
        background-color: #FFFFFF;
    }
    .uni-icon {
        font-family: uniicons;
        font-size: 24px;
        font-weight: normal;
        font-style: normal;
        width: 24px;
        height: 24px;
        line-height: 24px;
        color: #999999;
    }
    .uni-eye-active {
        color: #007AFF;
    }
    .eye-icon {
        width: 20px;
        height: 13px;
        margin-left: 5px;
    }
    .loadingButton {
        background-color: #ffda1e;
        font-family: a1;
    }
    .check {
        height: 100%;
        display: flex;
        flex-direction: row;
        font-size: 18px;
        color: #606266;
        justify-content: flex-start;
        align-items: center;
    }
    /* 语言选择下拉菜单 */
    .language-dropdown {
        position: relative;
        margin-bottom: 20rpx;
        z-index: 10;
    }
    .selected-language {
        display: flex;
        flex-direction: row;
        justify-content: space-between;
        align-items: center;
        padding: 15rpx 20rpx;
        background-color: #f8f8f8;
        border-radius: 8rpx;
        border: 1px solid #e0e0e0;
    }
    .language-options {
        position: absolute;
        bottom: 100%;
        left: 0;
        right: 0;
        background-color: #ffffff;
        border-radius: 8rpx;
        box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
        border: 1px solid #e0e0e0;
        margin-bottom: 5rpx;
    }
    .language-option {
        display: flex;
        flex-direction: row;
        justify-content: space-between;
        align-items: center;
        padding: 15rpx 20rpx;
        border-bottom: 1px solid #f0f0f0;
    }
    .language-option:last-child {
        border-bottom: none;
    }
    .language-option:active {
        background-color: #f5f5f5;
    }
    .settings-popup {
        padding: 20px;
        background-color: #ffffff;
        border-radius: 10px;
        display: flex;
        flex-direction: column;
    }
    .settings-title {
        margin-bottom: 20px;
        align-items: center;
        justify-content: center;
    }
    .settings-title-text {
        font-size: 18px;
        font-weight: bold;
    }
    .settings-item {
        margin-bottom: 15px;
    }
    .settings-label {
        margin-bottom: 5px;
        font-size: 14px;
        color: #606266;
    }
    .settings-buttons {
        flex-direction: row;
        justify-content: center;
        margin-top: 10px;
    }
</style>
pages/login/login.vue
@@ -1,78 +1,231 @@
<template>
    <view class="bodyView">
        <view>
            <u-navbar
                :fixed="true"
                :title="$t('page.login')"
                rightIcon="setting"
                left-icon=""
                :placeholder="true"
                @rightClick="openSettings"
            ></u-navbar>
        </view>
        <view class="topView">
            <image src="/static/img/login_top.png" mode="aspectFill">
            </image>
            <u--image
                width="100%"
                src="/static/img/login_top.png"
                mode="aspectFill"
            ></u--image>
        </view>
        <view class="logoView">
            <image src="/static/img/newLogo.png" mode="aspectFit">
            </image>
            <image
                src="/static/img/newLogo.png"
                mode="aspectFit"
            ></image>
        </view>
        <view class="bottomView">
            <view class="itemView" style="margin-bottom: 40px;">
            <view
                class="itemView"
                style="margin-bottom: 20px"
            >
                <view class="helloText">{{$t('index.hello')}}</view>
                <view class="introText">{{$t('index.intro')}}</view>
            </view>
            <view class="itemView">
                <view class="textType3">{{$t('login.user')}}:</view>
                <u-input clearable class="" v-model="user.userName" focus :placeholder="$t('login.inputUser')" />
                <text class="textType3">{{ $t('login.user') }}:</text>
                <u-input
                    clearable
                    class=""
                    v-model="user.userName"
                    focus
                    :placeholder="$t('login.inputUser')"
                />
            </view>
            <view class="itemView">
                <view class="textType3">{{$t('login.pwd')}}:</view>
                <text class="textType3">{{ $t('login.pwd') }}:</text>
                <u-input :password="showPassword" v-model="user.password" :placeholder="$t('login.inputPwd')"
                    suffixIcon="map-fill" suffixIconStyle="color: #909399">
                <u-input
                    :password="showPassword"
                    v-model="user.password"
                    :placeholder="$t('login.inputPwd')"
                    suffixIcon="map-fill"
                    suffixIconStyle="color: #909399"
                >
                    <template slot="suffix">
                        <u-icon :name="pwdIcon" @click="changePassword"></u-icon>
                        <u-icon
                            :name="pwdIcon"
                            @click="changePassword"
                        ></u-icon>
                    </template>
                </u-input>
            </view>
            <view class="langAndRemView">
                <view class="check">
                    <view>
                        <view>{{$t('login.remPwd')}}</view>
                        <text>{{ $t('login.remPwd') }}</text>
                    </view>
                    <view>
                        <u-switch space="2" size="20" v-model="remberPassword" activeColor="#f9ae3d"
                            inactiveColor="rgb(230, 230, 230)">
                        </u-switch>
                        <!-- <switch :checked='remberPassword' color="#FFCC33" style="transform:scale(0.7)"
                            @change="remberChange" /> -->
                        <u-switch
                            space="2"
                            size="20"
                            v-model="remberPassword"
                            activeColor="#f9ae3d"
                            inactiveColor="rgb(230, 230, 230)"
                        ></u-switch>
                    </view>
                </view>
                <view class="langView">
                    <!-- 语言选择下拉菜单 -->
                    <view class="language-dropdown">
                        <view class="selected-language" @click="toggleLanguageDropdown">
                        <view
                            class="selected-language"
                            @click="toggleLanguageDropdown"
                        >
                            <text>{{getCurrentLanguageText()}}</text>
                            <u-icon name="list" size="14" color="#707070"></u-icon>
                            <u-icon
                                name="list"
                                size="14"
                                color="#707070"
                            ></u-icon>
                        </view>
                        <view class="language-options" v-if="showLanguageDropdown">
                            <view class="language-option" v-for="(item, index) in locales" :key="index"
                                @click="onLocaleChange(item)">
                        <view
                            class="language-options"
                            v-if="showLanguageDropdown"
                        >
                            <view
                                class="language-option"
                                v-for="(item, index) in locales"
                                :key="index"
                                @click="onLocaleChange(item)"
                            >
                                <text>{{item.text}}</text>
                                <u-icon name="checkbox-mark" color="#007AFF"
                                    v-if="item.code == applicationLocale"></u-icon>
                                <u-icon
                                    name="checkbox-mark"
                                    color="#007AFF"
                                    v-if="item.code == applicationLocale"
                                ></u-icon>
                            </view>
                        </view>
                    </view>
                </view>
            </view>
            <view class="itemView">
                <u-button class="loadingButton" @click="onLogin()" :loading="loading">{{btnText}}</u-button>
                <u-button
                    class="loadingButton"
                    @click="onLogin()"
                    :loading="loading"
                >
                    {{ btnText }}
                </u-button>
            </view>
        </view>
        <u-popup
            :show="showAuth"
            @close="showAuth = false"
            mode="center"
            :round="14"
            :customStyle="{ width: '500rpx' }"
        >
            <view class="settings-popup">
                <view class="settings-title">
                    <text class="settings-title-text">
                        {{ $t('settings.authTitle') || '身份验证' }}
                    </text>
                </view>
                <view class="settings-item">
                    <u-input
                        v-model="authPassword"
                        type="password"
                        border="surround"
                        :placeholder="
                            $t('settings.inputAuthPwd') || '请输入管理员密码'
                        "
                    />
                </view>
                <view class="settings-buttons">
                    <u-button
                        size="medium"
                        @click="showAuth = false"
                    >
                        {{ $t('common.cancel') || '取消' }}
                    </u-button>
                    <view style="width: 20px"></view>
                    <u-button
                        size="medium"
                        type="primary"
                        @click="checkAuth"
                    >
                        {{ $t('common.confirm') || '确认' }}
                    </u-button>
                </view>
            </view>
        </u-popup>
        <u-popup
            :show="showSettings"
            @close="showSettings = false"
            mode="center"
            :round="14"
            :closeOnClickOverlay="false"
            :customStyle="{ width: '600rpx' }"
        >
            <view class="settings-popup">
                <view class="settings-title">
                    <text class="settings-title-text">
                        {{ $t('settings.title') || '设置' }}
                    </text>
                </view>
                <view class="settings-item">
                    <text class="settings-label">
                        {{ $t('settings.ip') || 'IP地址' }}:
                    </text>
                    <u-input
                        v-model="settings.ip"
                        border="surround"
                    />
                </view>
                <view class="settings-item">
                    <text class="settings-label">
                        {{ $t('settings.port') || '端口' }}:
                    </text>
                    <u-input
                        v-model="settings.port"
                        border="surround"
                    />
                </view>
                <view class="settings-item">
                    <text class="settings-label">
                        {{ $t('settings.project') || '项目名' }}:
                    </text>
                    <u-input
                        v-model="settings.project"
                        border="surround"
                    />
                </view>
                <view class="settings-buttons">
                    <u-button
                        size="medium"
                        @click="showSettings = false"
                    >
                        {{ $t('common.cancel') || '取消' }}
                    </u-button>
                    <view style="width: 20px"></view>
                    <u-button
                        size="medium"
                        type="primary"
                        @click="saveSettings"
                    >
                        {{ $t('common.confirm') || '确认' }}
                    </u-button>
                </view>
            </view>
        </u-popup>
        <u-toast ref="uToast"></u-toast>
    </view>
</template>
<script>
    import md5 from '../../static/js/md5.js'
    import {
        request
    } from '../../common/request.js'
import { login } from './api.js'
    export default {
        data() {
            return {
@@ -85,17 +238,27 @@
                remberPassword: true,
                user: {
                    userName: '',
                    password: '',
                password: ''
                },
                passwordIcon: 'eye-off'
            passwordIcon: 'eye-off',
            showSettings: false,
            settings: {
                ip: '',
                port: '',
                project: ''
            },
            showAuth: false,
            authPassword: ''
            }
        },
        computed: {
            locales() {
                return [{
            return [
                {
                        text: this.$t('locale.auto'),
                        code: 'auto'
                    }, {
                },
                {
                        text: this.$t('locale.en'),
                        code: 'en'
                    },
@@ -114,19 +277,19 @@
                ]
            },
            btnText() {
                return this.$t(this.loginButton);
            return this.$t(this.loginButton)
            },
            pwdIcon() {
                return this.passwordIcon;
            return this.passwordIcon
            }
        },
        onLoad() {
            let systemInfo = uni.getSystemInfoSync();
            this.systemLocale = systemInfo.language;
            this.applicationLocale = uni.getLocale();
            this.isAndroid = systemInfo.platform.toLowerCase() === 'android';
        let systemInfo = uni.getSystemInfoSync()
        this.systemLocale = systemInfo.language
        this.applicationLocale = uni.getLocale()
        this.isAndroid = systemInfo.platform.toLowerCase() === 'android'
            uni.onLocaleChange((e) => {
                this.applicationLocale = e.locale;
            this.applicationLocale = e.locale
            })
            this.user = uni.getStorageSync('user')
@@ -138,53 +301,85 @@
            }
        },
        methods: {
            async onLogin() {
                const {
                    code,
                    data,
                    msg
                } = await request('/login.action', {
                    username: this.user.userName,
                    password: md5.hex_md5(this.user.password),
                }, 'GET')
                if (code === 200) {
                    this.loading = true;
                    this.loginButton = 'login.loging';
                    uni.setStorageSync('token', data.accessToken);
                    uni.setStorageSync('userData', data.username);
                    if (this.remberPassword) {
                        uni.setStorageSync('user', this.user);
                    } else {
                        uni.removeStorageSync('user');
                    }
                    this.goHome()
        openSettings() {
            this.showAuth = true
            this.authPassword = ''
        },
        checkAuth() {
            // Default password: admin or 123456. Ideally from config.
            if (
                this.authPassword === 'admin' ||
                this.authPassword === '123456'
            ) {
                this.showAuth = false
                this.loadSettings()
                } else {
                    this.$refs.uToast.show({
                        type: 'error',
                        message: "请检查接口连接",
                        position: 'top'
                    });
                    message: this.$t('settings.authError') || '密码错误'
                })
                }
        },
        loadSettings() {
            this.showSettings = true
            let settings = uni.getStorageSync('app_settings')
            if (!settings) {
                settings = {
                    ip: '127.0.0.1',
                    port: '8080',
                    project: 'jshdasrs'
                }
            }
            this.settings = settings
        },
        saveSettings() {
            uni.setStorageSync('app_settings', this.settings)
            console.log(this.settings)
            this.showSettings = false
            this.$refs.uToast.show({
                type: 'success',
                message: this.$t('settings.saved') || '设置已保存'
            })
        },
        async onLogin() {
            try {
                const res = await login(
                    {
                        username: this.user.userName,
                        password: md5.hex_md5(this.user.password)
                    },
                    { custom: { catch: true } }
                )
                this.loading = true
                this.loginButton = 'login.loging'
                uni.setStorageSync('token', res.data.accessToken)
                uni.setStorageSync('userData', res.data.username)
                if (this.remberPassword) {
                    uni.setStorageSync('user', this.user)
                } else {
                    uni.removeStorageSync('user')
                }
                this.goHome()
            } catch (e) {
                // 拦截器已处理 toast
            }
            },
            goHome() {
                setTimeout(() => {
                    this.$refs.uToast.show({
                        type: 'success',
                        message: "登录成功",
                    message: '登录成功',
                        position: 'top'
                    });
                })
                    setTimeout(() => {
                        uni.$u.route({
                            url: 'pages/index/index',
                        type: 'reLaunch',
                        url: 'pages/home/home',
                            params: {
                                name: 'lisa'
                            }
                        })
                    }, 300)
                }, 700)
            },
@@ -194,7 +389,7 @@
            // 显示/隐藏密码
            changePassword() {
                this.passwordIcon = !this.showPassword ? 'eye-off' : 'eye'
                this.showPassword = !this.showPassword;
            this.showPassword = !this.showPassword
            },
            localChange() {
                console.log(this.local)
@@ -203,24 +398,26 @@
                        content: this.$t('index.language-change-confirm'),
                        success: (res) => {
                            if (res.confirm) {
                                uni.setLocale(this.local.value);
                            uni.setLocale(this.local.value)
                            }
                        }
                    })
                } else {
                    uni.setLocale(this.local.value);
                    this.$i18n.locale = this.local.value;
                uni.setLocale(this.local.value)
                this.$i18n.locale = this.local.value
                }
            },
            // 切换语言下拉菜单显示状态
            toggleLanguageDropdown() {
                this.showLanguageDropdown = !this.showLanguageDropdown;
            this.showLanguageDropdown = !this.showLanguageDropdown
            },
            // 获取当前选择的语言文本
            getCurrentLanguageText() {
                const currentLocale = this.locales.find(item => item.code === this.applicationLocale);
                return currentLocale ? currentLocale.text : this.$t('locale.auto');
            const currentLocale = this.locales.find(
                (item) => item.code === this.applicationLocale
            )
            return currentLocale ? currentLocale.text : this.$t('locale.auto')
            },
            // 语言选择改变
@@ -230,17 +427,17 @@
                        content: this.$t('index.language-change-confirm'),
                        success: (res) => {
                            if (res.confirm) {
                                uni.setLocale(e.code);
                                this.showLanguageDropdown = false;
                            uni.setLocale(e.code)
                            this.showLanguageDropdown = false
                            }
                        }
                    })
                } else {
                    uni.setLocale(e.code);
                    this.$i18n.locale = e.code;
                    this.showLanguageDropdown = false;
                uni.setLocale(e.code)
                this.$i18n.locale = e.code
                this.showLanguageDropdown = false
                }
            },
        }
        }
    }
</script>
@@ -264,14 +461,17 @@
    }
    .bodyView {
        display: flex;
    flex: 1;
        flex-direction: column;
        background-image: url("/static/img/login_backg.png");
        background-repeat: no-repeat;
        background-size: cover;
        background-position: center;
        height: 100vh;
        width: 100%;
    width: 750rpx;
}
.bgImage {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    }
    .topView {
@@ -286,8 +486,7 @@
        flex: 1;
        display: flex;
        justify-content: flex-end;
        align-items: center;
    align-items: flex-end;
    }
    .logoView image {
@@ -306,15 +505,16 @@
    .itemView {
        width: 90%;
        height: 50px;
        margin-bottom: 30px;
    margin-bottom: 5px;
    }
    .langAndRemView {
        width: 90%;
        display: flex;
    flex-direction: row;
        justify-content: space-between;
        align-items: center;
    margin-bottom: 5px;
    }
    .langView {
@@ -336,7 +536,7 @@
        padding: 8px 13px;
        flex-direction: row;
        flex-wrap: nowrap;
        background-color: #FFFFFF;
    background-color: #ffffff;
        border-radius: 10px;
        height: 45px;
        align-items: center;
@@ -349,8 +549,7 @@
        font-size: 15px;
        padding: 0px;
        flex: 1;
        background-color: #FFFFFF;
    background-color: #ffffff;
    }
    .uni-icon {
@@ -365,7 +564,7 @@
    }
    .uni-eye-active {
        color: #007AFF;
    color: #007aff;
    }
    .eye-icon {
@@ -380,14 +579,13 @@
    }
    .check {
        height: 100%;
        display: flex;
    flex-direction: row;
        font-size: 18px;
        color: #606266;
        justify-content: flex-start;
        align-items: center;
    }
    /* 语言选择下拉菜单 */
@@ -399,6 +597,7 @@
    .selected-language {
        display: flex;
    flex-direction: row;
        justify-content: space-between;
        align-items: center;
        padding: 15rpx 20rpx;
@@ -421,6 +620,7 @@
    .language-option {
        display: flex;
    flex-direction: row;
        justify-content: space-between;
        align-items: center;
        padding: 15rpx 20rpx;
@@ -434,4 +634,40 @@
    .language-option:active {
        background-color: #f5f5f5;
    }
.settings-popup {
    padding: 20px;
    background-color: #ffffff;
    border-radius: 10px;
    display: flex;
    flex-direction: column;
}
.settings-title {
    margin-bottom: 20px;
    align-items: center;
    justify-content: center;
}
.settings-title-text {
    font-size: 18px;
    font-weight: bold;
}
.settings-item {
    margin-bottom: 15px;
}
.settings-label {
    margin-bottom: 5px;
    font-size: 14px;
    color: #606266;
}
.settings-buttons {
    display: flex;
    flex-direction: row;
    justify-content: center;
    margin-top: 10px;
}
</style>