+23
-16
@@ -4,21 +4,28 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// 用户相关
|
// 用户相关
|
||||||
export const user = {
|
|
||||||
login: (data) => post('/api/user/login', data),
|
export const api = {
|
||||||
logout: () => post('/api/user/logout'),
|
/**
|
||||||
info: () => get('/api/user/info')
|
* GET 请求(一般不需要认证)
|
||||||
|
*/
|
||||||
|
get(path) {
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST JSON
|
||||||
|
*/
|
||||||
|
post(path, data = {},callback) {
|
||||||
|
console.log("post")
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST FormData(文件上传)
|
||||||
|
*/
|
||||||
|
upload(path, file) {
|
||||||
|
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// 订单相关
|
export default api
|
||||||
export const order = {
|
|
||||||
list: (data) => get('/api/order/list', data),
|
|
||||||
detail: (id) => get('/api/order/detail', { id }),
|
|
||||||
create: (data) => post('/api/order/create', data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 消息相关
|
|
||||||
export const message = {
|
|
||||||
list: (data) => get('/api/message/list', data),
|
|
||||||
read: (id) => post('/api/message/read', { id })
|
|
||||||
}
|
|
||||||
+1
-47
@@ -1,50 +1,4 @@
|
|||||||
import { useConfigStore } from '@/stores/config'
|
import { useConfigStore } from '@/stores/config'
|
||||||
|
|
||||||
/**
|
const useConfig=useConfigStore()
|
||||||
* 基础请求封装
|
|
||||||
* @param {string} path - 接口路径
|
|
||||||
* @param {object} data - 请求参数
|
|
||||||
* @param {string} method - 请求方法
|
|
||||||
*/
|
|
||||||
export const request = (path, data = {}, method = 'GET') => {
|
|
||||||
const config = useConfigStore()
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
uni.request({
|
|
||||||
url: config.apiBaseUrl + path,
|
|
||||||
data,
|
|
||||||
method,
|
|
||||||
header: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
success: (res) => {
|
|
||||||
if (res.statusCode === 200) {
|
|
||||||
resolve(res.data)
|
|
||||||
} else {
|
|
||||||
uni.showToast({
|
|
||||||
title: '请求失败',
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
reject(res)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fail: (err) => {
|
|
||||||
uni.showToast({
|
|
||||||
title: '网络错误',
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
reject(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GET 请求
|
|
||||||
*/
|
|
||||||
export const get = (path, data) => request(path, data, 'GET')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* POST 请求
|
|
||||||
*/
|
|
||||||
export const post = (path, data) => request(path, data, 'POST')
|
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import api from ".";
|
||||||
|
|
||||||
|
export const userApi = {
|
||||||
|
|
||||||
|
login(username, password, remember) {
|
||||||
|
return api.post('/users/login', { username, password, remember })
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
<template>
|
||||||
|
<view v-if="visible" class="my-toast" :class="typeClass">
|
||||||
|
<text>{{ message }}</text>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
|
||||||
|
const visible = ref(false)
|
||||||
|
const message = ref('')
|
||||||
|
const type = ref('info') // info | success | error | warning
|
||||||
|
|
||||||
|
const typeClass = computed(() => `toast-${type.value}`)
|
||||||
|
|
||||||
|
// 暴露方法给外部调用
|
||||||
|
function show(msg, t = 'info', duration = 2000) {
|
||||||
|
message.value = msg
|
||||||
|
type.value = t
|
||||||
|
visible.value = true
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
visible.value = false
|
||||||
|
}, duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 快捷方法
|
||||||
|
function success(msg, duration = 2000) { show(msg, 'success', duration) }
|
||||||
|
function error(msg, duration = 2000) { show(msg, 'error', duration) }
|
||||||
|
function warning(msg, duration = 2000) { show(msg, 'warning', duration) }
|
||||||
|
function info(msg, duration = 2000) { show(msg, 'info', duration) }
|
||||||
|
|
||||||
|
defineExpose({ show, success, error, warning, info })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 固定顶部居中 */
|
||||||
|
.my-toast {
|
||||||
|
position: fixed;
|
||||||
|
top: 120rpx;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
padding: 20rpx 40rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
z-index: 9999;
|
||||||
|
text-align: center;
|
||||||
|
max-width: 600rpx;
|
||||||
|
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.15);
|
||||||
|
word-break: break-all;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 四种类型 */
|
||||||
|
.toast-info {
|
||||||
|
background-color: #007AFF;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-success {
|
||||||
|
background-color: #34C759;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-error {
|
||||||
|
background-color: #FF3B30;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-warning {
|
||||||
|
background-color: #FF9500;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
+4
-1
@@ -41,7 +41,10 @@
|
|||||||
"navigationBarTextStyle": "black",
|
"navigationBarTextStyle": "black",
|
||||||
"navigationBarTitleText": "uni-app",
|
"navigationBarTitleText": "uni-app",
|
||||||
"navigationBarBackgroundColor": "#F8F8F8",
|
"navigationBarBackgroundColor": "#F8F8F8",
|
||||||
"backgroundColor": "#F8F8F8"
|
"backgroundColor": "#F8F8F8",
|
||||||
|
"usingComponents": {
|
||||||
|
"my-toast": "/components/my-toast/my-toast"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"tabBar": {
|
"tabBar": {
|
||||||
"color": "#7A7E83",
|
"color": "#7A7E83",
|
||||||
|
|||||||
+39
-1
@@ -6,14 +6,21 @@
|
|||||||
<button class="submit-btn" @click="handleLogin">登录</button>
|
<button class="submit-btn" @click="handleLogin">登录</button>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
<my-toast ref="toast" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
import { useConfigStore } from '../../stores/config'
|
||||||
|
import { userApi } from '../../api/user'
|
||||||
|
|
||||||
|
const useConfig =useConfigStore()
|
||||||
|
|
||||||
const username = ref('')
|
const username = ref('')
|
||||||
const password = ref('')
|
const password = ref('')
|
||||||
|
|
||||||
|
const toast = ref(null)
|
||||||
|
|
||||||
const handleLogin = () => {
|
const handleLogin = () => {
|
||||||
if (!username.value || !password.value) {
|
if (!username.value || !password.value) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
@@ -23,7 +30,38 @@ const handleLogin = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// TODO: 调用登录接口
|
// TODO: 调用登录接口
|
||||||
console.log('登录信息:', username.value, password.value)
|
console.log('登录信息:', username.value, password.value,useConfig.getApiBaseUrl())
|
||||||
|
//userApi.login(username.value,password.value,true)
|
||||||
|
uni.request({
|
||||||
|
url: useConfig.getApiBaseUrl()+"/users/login",
|
||||||
|
method: 'POST',
|
||||||
|
timeout:1000,
|
||||||
|
header: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
data:{
|
||||||
|
"username": username.value,
|
||||||
|
"password": password.value,
|
||||||
|
"remember": true
|
||||||
|
},
|
||||||
|
success: (res) => {
|
||||||
|
console.log('成功', res)
|
||||||
|
switch(res.statusCode){
|
||||||
|
case 200:
|
||||||
|
toast.value.success('成功')
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
toast.value.error('服务异常')
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.log('失败', err)
|
||||||
|
toast.value.error('网异常')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
+194
-12
@@ -1,23 +1,205 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="container">
|
<!-- 页面根容器,灰色背景 -->
|
||||||
<text class="placeholder">设置</text>
|
<view class="page">
|
||||||
</view>
|
|
||||||
|
<!-- ===== 分组:账号设置 ===== -->
|
||||||
|
<view class="group">
|
||||||
|
<!-- 分组标题 -->
|
||||||
|
<text class="group-title">base url</text>
|
||||||
|
<!-- 列表容器(白色卡片) -->
|
||||||
|
<view class="group-body">
|
||||||
|
<!-- 列表项:点击跳转或弹窗编辑 -->
|
||||||
|
<view class="cell" @tap="onEditApiUrl">
|
||||||
|
<text class="cell-value">{{ useConfig.getApiBaseUrl() || '未设置' }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="btn-wrapper">
|
||||||
|
<button v-if="!isTesting" class="save-btn" @tap="onTest">测试连接</button>
|
||||||
|
<button v-if="isTesting" class="save-btn" disabled>正在测试...</button>
|
||||||
|
</view>
|
||||||
|
<view v-if="isTested&&!isTesting" :class="isTestok ? 'result success' : 'result error'">
|
||||||
|
{{ isTestok ? '✅ 连接成功' : '❌ 连接失败' }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- ====================== 这里是居中的按钮 ====================== -->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { ref,onMounted } from 'vue'
|
||||||
|
import { isUrl } from '@/utils/index.js'
|
||||||
|
import { useConfigStore } from '../../stores/config'
|
||||||
|
|
||||||
|
const useConfig=useConfigStore()
|
||||||
|
|
||||||
|
// ---- 响应式状态 ----
|
||||||
|
const isTested=ref(false)//是否点了测试
|
||||||
|
const isTesting=ref(false)//是否正在测试
|
||||||
|
const isTestok=ref(false)//测试是否通过
|
||||||
|
|
||||||
|
// ---- 事件处理 ----
|
||||||
|
|
||||||
|
function onTest(){
|
||||||
|
//console.log("test")
|
||||||
|
isTested.value=true
|
||||||
|
isTesting.value=true
|
||||||
|
uni.request({
|
||||||
|
url: useConfig.getApiBaseUrl(),
|
||||||
|
method: 'GET',
|
||||||
|
timeout:1000,
|
||||||
|
success: (res) => {
|
||||||
|
//console.log('成功', res.data)
|
||||||
|
if(res.data.return.isOpsApiRoot==true){
|
||||||
|
isTestok.value=true
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
//console.log('失败', err)
|
||||||
|
isTestok.value=false
|
||||||
|
},
|
||||||
|
complete() {
|
||||||
|
// 这个回调不管成功失败**一定**会执行
|
||||||
|
//console.log('请求完成')
|
||||||
|
isTesting.value=false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点击 API URL → 弹出输入框(示例用 uni.showModal 简单实现)
|
||||||
|
function onEditApiUrl() {
|
||||||
|
// 实际可跳转子页面或弹出自定义弹窗
|
||||||
|
uni.showModal({
|
||||||
|
title: 'API Base URL',
|
||||||
|
editable: true,
|
||||||
|
placeholderText: 'https://example.com',
|
||||||
|
content: useConfig.getApiBaseUrl(),
|
||||||
|
success(res) {
|
||||||
|
if (res.confirm) {
|
||||||
|
console.log(res)
|
||||||
|
if (isUrl(res.content)){
|
||||||
|
useConfig.setApiBaseUrl(res.content)
|
||||||
|
console.log(res.content,":ok")
|
||||||
|
|
||||||
|
}else{
|
||||||
|
console.log(res.content,":not a url")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onMounted(()=>{
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.container {
|
/* 页面底色 */
|
||||||
flex: 1;
|
.page {
|
||||||
display: flex;
|
min-height: 100vh;
|
||||||
justify-content: center;
|
background-color: #f2f2f7; /* iOS 系统级灰色 */
|
||||||
align-items: center;
|
padding-top: 20rpx;
|
||||||
background-color: #f5f5f5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.placeholder {
|
/* 分组间距 */
|
||||||
font-size: 32rpx;
|
.group {
|
||||||
color: #999;
|
margin-bottom: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 分组标题:小号灰色,左侧缩进 */
|
||||||
|
.group-title {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #8e8e93;
|
||||||
|
padding: 0 32rpx 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 白色卡片容器 */
|
||||||
|
.group-body {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
margin: 0 24rpx;
|
||||||
|
overflow: hidden; /* 保证圆角裁剪子项 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 每一行 cell */
|
||||||
|
.cell {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 32rpx;
|
||||||
|
height: 96rpx;
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 点击高亮(uni-app 用 hover-class 实现,这里仅演示结构) */
|
||||||
|
.cell:active {
|
||||||
|
background-color: #f2f2f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 左侧标签 */
|
||||||
|
.cell-label {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #1c1c1e;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 右侧容器(值 + 箭头) */
|
||||||
|
.cell-right {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 右侧当前值 */
|
||||||
|
.cell-value {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #8e8e93;
|
||||||
|
max-width: 300rpx; /* 防止超长文本撑破布局 */
|
||||||
|
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 右箭头 */
|
||||||
|
.cell-arrow {
|
||||||
|
font-size: 36rpx;
|
||||||
|
color: #c7c7cc;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* switch 开关右对齐 */
|
||||||
|
.cell-switch {
|
||||||
|
transform: scale(0.85); /* 微调大小 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 分割线:左侧缩进,不跨满 */
|
||||||
|
.divider {
|
||||||
|
height: 1rpx;
|
||||||
|
background-color: #e5e5ea;
|
||||||
|
margin-left: 32rpx; /* 左缩进,右边贴边,iOS 风格 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 危险操作行(退出登录) */
|
||||||
|
.cell-danger {
|
||||||
|
justify-content: center; /* 文字居中 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell-label-danger {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #ff3b30; /* iOS 红色 */
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
|
||||||
const goToLogin = () => {
|
const goToLogin = () => {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: '/pages/login/login'
|
url: '/pages/login/login'
|
||||||
|
|||||||
+12
-1
@@ -3,7 +3,7 @@ import { ref } from 'vue'
|
|||||||
|
|
||||||
export const useConfigStore = defineStore('config', () => {
|
export const useConfigStore = defineStore('config', () => {
|
||||||
// API 配置
|
// API 配置
|
||||||
const apiBaseUrl = ref('http://192.168.13.105')
|
const apiBaseUrl = ref('')
|
||||||
|
|
||||||
// 应用配置
|
// 应用配置
|
||||||
const appName = ref('OPS')
|
const appName = ref('OPS')
|
||||||
@@ -15,8 +15,18 @@ export const useConfigStore = defineStore('config', () => {
|
|||||||
// 设置 API 地址
|
// 设置 API 地址
|
||||||
const setApiBaseUrl = (url) => {
|
const setApiBaseUrl = (url) => {
|
||||||
apiBaseUrl.value = url
|
apiBaseUrl.value = url
|
||||||
|
uni.setStorageSync('baseUrl', url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getApiBaseUrl=()=>{
|
||||||
|
if(apiBaseUrl.value==='')
|
||||||
|
{
|
||||||
|
apiBaseUrl.value=uni.getStorageSync('baseUrl')
|
||||||
|
}
|
||||||
|
return apiBaseUrl.value
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// 设置主题
|
// 设置主题
|
||||||
const setTheme = (newTheme) => {
|
const setTheme = (newTheme) => {
|
||||||
theme.value = newTheme
|
theme.value = newTheme
|
||||||
@@ -28,6 +38,7 @@ export const useConfigStore = defineStore('config', () => {
|
|||||||
version,
|
version,
|
||||||
theme,
|
theme,
|
||||||
setApiBaseUrl,
|
setApiBaseUrl,
|
||||||
|
getApiBaseUrl,
|
||||||
setTheme
|
setTheme
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* 检查字符串是否是合法的 URL
|
||||||
|
* @param {string} str
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export function isUrl(str) {
|
||||||
|
if (!str || typeof str !== 'string') return false
|
||||||
|
|
||||||
|
// 必须以 http:// 或 https:// 开头(接口地址必须带)
|
||||||
|
const reg = /^https?:\/\/.+/i
|
||||||
|
return reg.test(str.trim())
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user