| New file |
| | |
| | | --- |
| | | 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> |
| | | ``` |
| New file |
| | |
| | | --- |
| | | 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. |
| New file |
| | |
| | | // 此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) |
| | | }) |
| | | } |
| | |
| | | }, |
| | | "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" |
| | | } |
| | | } |
| | |
| | | "other":{ |
| | | "asnNo":"ASN单号" |
| | | |
| | | }, |
| | | "settings": { |
| | | "title": "设置", |
| | | "ip": "IP地址", |
| | | "port": "端口", |
| | | "project": "项目名", |
| | | "saved": "设置已保存", |
| | | "authTitle": "身份验证", |
| | | "inputAuthPwd": "请输入管理员密码", |
| | | "authError": "密码错误" |
| | | }, |
| | | "common": { |
| | | "confirm": "确认", |
| | | "cancel": "取消" |
| | | } |
| | | |
| | | |
| | | } |
| | |
| | | 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": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages |
| | | |
| | | { |
| | | "path": "pages/login/login", |
| | | "style": { |
| | |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/index/index", |
| | | "path": "pages/home/home", |
| | | "style": { |
| | | "navigationBarTitleText": "首页", |
| | | "navigationStyle": "custom" |
| | | |
| | | } |
| | | }, |
| | | { |
| New file |
| | |
| | | const http = uni.$u.http |
| | | |
| | | // 获取动态菜单 |
| | | export const getAuthMenu = (data, config = {}) => http.post('/menu/pda/auth', data, config) |
| New file |
| | |
| | | <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> |
| | |
| | | <template> |
| | | <div> |
| | | <uni-nav-bar :right-text="userName" title="首页" /> |
| | | <uni-nav-bar |
| | | :right-text="userName" |
| | | title="首页" |
| | | /> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | data() { |
| | | return { |
| | | userName: '123' |
| | | }; |
| | | } |
| | | }, |
| | | onLoad(obj) { |
| | | console.log(obj.name) |
| | |
| | | } |
| | | </script> |
| | | |
| | | <style> |
| | | |
| | | </style> |
| | | <style></style> |
| New file |
| | |
| | | const http = uni.$u.http |
| | | |
| | | // 用户登录 |
| | | export const login = (params) => http.get('/login.action', { params }) |
| | | |
| | | // 这里可以继续添加其他接口 |
| | | // export const getInfo = (data) => http.get('/user/info', data) |
| New file |
| | |
| | | <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> |
| | |
| | | <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 { |
| | |
| | | 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' |
| | | }, |
| | |
| | | ] |
| | | }, |
| | | 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') |
| | |
| | | } |
| | | }, |
| | | 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) |
| | | }, |
| | |
| | | // 显示/隐藏密码 |
| | | changePassword() { |
| | | this.passwordIcon = !this.showPassword ? 'eye-off' : 'eye' |
| | | this.showPassword = !this.showPassword; |
| | | this.showPassword = !this.showPassword |
| | | }, |
| | | localChange() { |
| | | console.log(this.local) |
| | |
| | | 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') |
| | | }, |
| | | |
| | | // 语言选择改变 |
| | |
| | | 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> |
| | |
| | | } |
| | | |
| | | .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 { |
| | |
| | | flex: 1; |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | align-items: center; |
| | | |
| | | align-items: flex-end; |
| | | } |
| | | |
| | | .logoView image { |
| | |
| | | |
| | | .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 { |
| | |
| | | padding: 8px 13px; |
| | | flex-direction: row; |
| | | flex-wrap: nowrap; |
| | | background-color: #FFFFFF; |
| | | background-color: #ffffff; |
| | | border-radius: 10px; |
| | | height: 45px; |
| | | align-items: center; |
| | |
| | | font-size: 15px; |
| | | padding: 0px; |
| | | flex: 1; |
| | | background-color: #FFFFFF; |
| | | |
| | | background-color: #ffffff; |
| | | } |
| | | |
| | | .uni-icon { |
| | |
| | | } |
| | | |
| | | .uni-eye-active { |
| | | color: #007AFF; |
| | | color: #007aff; |
| | | } |
| | | |
| | | .eye-icon { |
| | |
| | | } |
| | | |
| | | .check { |
| | | |
| | | height: 100%; |
| | | display: flex; |
| | | flex-direction: row; |
| | | font-size: 18px; |
| | | color: #606266; |
| | | justify-content: flex-start; |
| | | align-items: center; |
| | | |
| | | } |
| | | |
| | | /* 语言选择下拉菜单 */ |
| | |
| | | |
| | | .selected-language { |
| | | display: flex; |
| | | flex-direction: row; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 15rpx 20rpx; |
| | |
| | | |
| | | .language-option { |
| | | display: flex; |
| | | flex-direction: row; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 15rpx 20rpx; |
| | |
| | | .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> |