u
This commit is contained in:
1 parent
5569fe9c4f
commit
ca82773f6b
15 files changed
+1167
-492
No files matched your search
@@ -2,52 +2,54 @@
|
||||
import PickerSelect from '../picker-select/index.vue'
|
||||
|
||||
const props = defineProps({
|
||||
type: {
|
||||
type: String as PropType<'radio' | 'multiple'>,
|
||||
default: 'radio',
|
||||
},
|
||||
label: {
|
||||
value: String,
|
||||
default: '',
|
||||
},
|
||||
value: {
|
||||
type: null as unknown as PropType<any | any[]>,
|
||||
default: undefined,
|
||||
},
|
||||
labelField: {
|
||||
type: String,
|
||||
default: 'label',
|
||||
},
|
||||
valueField: {
|
||||
type: String,
|
||||
default: 'value',
|
||||
},
|
||||
viewname: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
searchcondition: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
userid: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
searchType: {
|
||||
type: String as PropType<'default' | 'ex' | 'single'>,
|
||||
default: 'default',
|
||||
required: false,
|
||||
},
|
||||
label: { type: String, default: '' },
|
||||
value: { type: null as unknown as PropType<any>, default: undefined },
|
||||
labelField: { type: String, required: true },
|
||||
valueField: { type: String, required: true },
|
||||
viewname: { type: String, required: true },
|
||||
field1: { type: String, required: true },
|
||||
field2: { type: String, required: true },
|
||||
placeholder: { type: String, default: '请选择' },
|
||||
})
|
||||
|
||||
const emits = defineEmits<{
|
||||
(e: 'update:label', val: string): void
|
||||
(e: 'update:value', val: any): void
|
||||
}>()
|
||||
|
||||
const _visible = ref(false)
|
||||
const _label = ref(props.label)
|
||||
|
||||
// 同步 props.label
|
||||
watch(() => props.label, (val) => {
|
||||
_label.value = val
|
||||
})
|
||||
|
||||
// 选中时只处理 label
|
||||
function handleSelectChange(selectValue: any) {
|
||||
if (!selectValue)
|
||||
return
|
||||
_label.value = selectValue[props.labelField]
|
||||
emits('update:label', _label.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<sar-popout-input
|
||||
v-model="_label"
|
||||
:placeholder="props.placeholder"
|
||||
@click="_visible = true"
|
||||
>
|
||||
<PickerSelect v-model:visible="_visible" />
|
||||
<PickerSelect
|
||||
v-model:visible="_visible"
|
||||
:value="props.value"
|
||||
:viewname="props.viewname"
|
||||
:field1="props.field1"
|
||||
:field2="props.field2"
|
||||
:label-field="props.labelField"
|
||||
:value-field="props.valueField"
|
||||
@update:value="emits('update:value', $event)"
|
||||
@change="handleSelectChange"
|
||||
/>
|
||||
</sar-popout-input>
|
||||
</template>
|
||||
@@ -2,7 +2,7 @@
|
||||
import type { PropType } from 'vue'
|
||||
import { useIndeterminate } from 'sard-uniapp'
|
||||
import { computed, ref } from 'vue'
|
||||
import { getAutocompleteDataApi, getAutocompleteDataExApi, getAutocompleteDataSingleApi } from '@/http/service'
|
||||
import { getAutocompleteDataApi, getAutocompleteDataExApi } from '@/request/api'
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
@@ -19,11 +19,11 @@ const props = defineProps({
|
||||
},
|
||||
labelField: {
|
||||
type: String,
|
||||
default: 'label',
|
||||
required: true,
|
||||
},
|
||||
valueField: {
|
||||
type: String,
|
||||
default: 'value',
|
||||
required: true,
|
||||
},
|
||||
viewname: {
|
||||
type: String,
|
||||
@@ -36,6 +36,14 @@ const props = defineProps({
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
field1: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
field2: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
searchType: {
|
||||
type: String as PropType<'default' | 'ex' | 'single'>,
|
||||
default: 'default',
|
||||
@@ -51,7 +59,7 @@ const _visible = computed({
|
||||
},
|
||||
})
|
||||
|
||||
const _loading = ref(false)
|
||||
const _loading = ref(true)
|
||||
const searchValue = ref('')
|
||||
|
||||
const pagination = ref({
|
||||
@@ -101,13 +109,12 @@ function select(option, toggle) {
|
||||
if (props.type === 'radio') {
|
||||
_visible.value = false
|
||||
|
||||
// 查找对应的 label
|
||||
const selectedOption = options.value.find(
|
||||
o => o.value === option.value,
|
||||
)
|
||||
|
||||
// 触发事件或者更新内部 _label
|
||||
emit('change', { selectedOption })
|
||||
emit('change', selectedOption)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,12 +161,14 @@ function getData() {
|
||||
|
||||
function getAutocompleteData() {
|
||||
_loading.value = true
|
||||
|
||||
getAutocompleteDataApi({
|
||||
q: searchValue.value,
|
||||
page: pagination.value.current,
|
||||
limit: pagination.value.pageSize,
|
||||
viewname: props.viewname,
|
||||
field: props.labelField,
|
||||
field1: props.field1,
|
||||
field2: props.field2,
|
||||
}).then((res) => {
|
||||
pagination.value.total = res.data.total
|
||||
|
||||
@@ -171,12 +180,14 @@ function getAutocompleteData() {
|
||||
|
||||
function getAutocompleteDataEx() {
|
||||
_loading.value = true
|
||||
|
||||
getAutocompleteDataExApi({
|
||||
q: searchValue.value,
|
||||
page: pagination.value.current,
|
||||
limit: pagination.value.pageSize,
|
||||
viewname: props.viewname,
|
||||
field: props.labelField,
|
||||
field1: props.field1,
|
||||
field2: props.field2,
|
||||
userid: props.userid,
|
||||
searchcondition: props.searchcondition,
|
||||
}).then((res) => {
|
||||
@@ -189,29 +200,29 @@ function getAutocompleteDataEx() {
|
||||
}
|
||||
|
||||
function getAutocompleteDataSingle() {
|
||||
_loading.value = true
|
||||
getAutocompleteDataSingleApi({
|
||||
q: searchValue.value,
|
||||
page: pagination.value.current,
|
||||
limit: pagination.value.pageSize,
|
||||
viewname: props.viewname,
|
||||
field: props.labelField,
|
||||
userid: props.userid,
|
||||
}).then((res) => {
|
||||
pagination.value.total = res.data.total
|
||||
// _loading.value = true
|
||||
// getAutocompleteDataSingleApi({
|
||||
// q: searchValue.value,
|
||||
// page: pagination.value.current,
|
||||
// limit: pagination.value.pageSize,
|
||||
// viewname: props.viewname,
|
||||
// field: props.labelField,
|
||||
// userid: props.userid,
|
||||
// }).then((res) => {
|
||||
// pagination.value.total = res.data.total
|
||||
|
||||
setOptions(res.data.rows)
|
||||
}).finally(() => {
|
||||
_loading.value = false
|
||||
})
|
||||
// setOptions(res.data.rows)
|
||||
// }).finally(() => {
|
||||
// _loading.value = false
|
||||
// })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<sar-popout
|
||||
v-model:visible="_visible"
|
||||
title="请选择"
|
||||
:show-footer="false"
|
||||
title="请选择"
|
||||
@after-enter="handleAfterEnter"
|
||||
>
|
||||
<template v-if="props.type === 'multiple'" #title-prepend>
|
||||
@@ -336,33 +347,11 @@ function getAutocompleteDataSingle() {
|
||||
确认
|
||||
</sar-button>
|
||||
</view>
|
||||
|
||||
<view v-if="_loading" class="sar-popout__mask">
|
||||
<sar-loading
|
||||
size="48rpx"
|
||||
text-size="36rpx"
|
||||
color="var(--sar-primary)"
|
||||
text-color="var(--sar-primary)"
|
||||
text="加载中"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
</sar-popout>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.sar-popout__mask {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-top-left-radius: var(--sar-popout-border-top-radius);
|
||||
border-top-right-radius: var(--sar-popout-border-top-radius);
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.sar-checkbox-popout__check-all {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
@@ -376,6 +365,7 @@ function getAutocompleteDataSingle() {
|
||||
color: var(--sar-tertiary-color);
|
||||
font-size: var(--sar-text-sm);
|
||||
}
|
||||
|
||||
.sar-checkbox-popout__container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
import type { CustomRequestOptions } from './interceptor'
|
||||
import { toast } from '@/utils/toast'
|
||||
import { httpInterceptor } from './interceptor'
|
||||
|
||||
export function http<T>(options: CustomRequestOptions): Promise<IResData<T>> {
|
||||
return httpInternal<T>(options)
|
||||
}
|
||||
|
||||
async function httpInternal<T>(options: CustomRequestOptions): Promise<IResData<T>> {
|
||||
try {
|
||||
await httpInterceptor.invoke(options)
|
||||
}
|
||||
catch (e) {
|
||||
console.log('异常')
|
||||
return Promise.reject(e)
|
||||
}
|
||||
|
||||
return new Promise<IResData<T>>((resolve, reject) => {
|
||||
uni.request({
|
||||
...options,
|
||||
dataType: 'json',
|
||||
// #ifndef MP-WEIXIN
|
||||
responseType: 'json',
|
||||
// #endif
|
||||
success(res) {
|
||||
if (res.statusCode >= 200 && res.statusCode < 300) {
|
||||
const resData = res.data as IResData<T>
|
||||
if (resData.code === 200) {
|
||||
try {
|
||||
resData.data = JSON.parse(resData.data as string)
|
||||
}
|
||||
catch {}
|
||||
resolve(resData)
|
||||
}
|
||||
else if (resData.code === 600) {
|
||||
toast.error(resData.message || '当前登录已过期')
|
||||
uni.navigateTo({ url: '/pages-sub/login/index' })
|
||||
reject(res)
|
||||
}
|
||||
else {
|
||||
toast.error(resData.message || '请求错误')
|
||||
reject(res)
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!options.hideErrorToast) {
|
||||
toast.error((res.data as IResData<T>).message || '请求错误')
|
||||
}
|
||||
reject(res)
|
||||
}
|
||||
},
|
||||
fail(err) {
|
||||
toast.error('网络错误,换个网络试试')
|
||||
reject(err)
|
||||
},
|
||||
complete() {
|
||||
console.log('complete')
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* GET 请求
|
||||
* @param url 后台地址
|
||||
* @param query 请求query参数
|
||||
* @returns
|
||||
*/
|
||||
export function httpGet<T>(url: string, query?: Record<string, any>) {
|
||||
return http<T>({
|
||||
url,
|
||||
query,
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* POST 请求
|
||||
* @param url 后台地址
|
||||
* @param data 请求body参数
|
||||
* @param query 请求query参数,post请求也支持query,很多微信接口都需要
|
||||
* @returns
|
||||
*/
|
||||
export function httpPostForm<T>(
|
||||
url: string,
|
||||
data?: Record<string, any>,
|
||||
query?: Record<string, any>,
|
||||
) {
|
||||
return http<T>({
|
||||
url,
|
||||
query,
|
||||
data,
|
||||
method: 'POST',
|
||||
header: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
})
|
||||
}
|
||||
export function httpPostJson<T>(
|
||||
url: string,
|
||||
data?: Record<string, any>,
|
||||
query?: Record<string, any>,
|
||||
) {
|
||||
return http<T>({
|
||||
url,
|
||||
query,
|
||||
data,
|
||||
method: 'POST',
|
||||
header: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
http.get = httpGet
|
||||
http.postForm = httpPostForm
|
||||
http.postJson = httpPostJson
|
||||
@@ -1,85 +0,0 @@
|
||||
import { useUserStore } from '@/store'
|
||||
import { joinUrl } from '@/utils/common'
|
||||
import { getDbConfigs } from '@/utils/request'
|
||||
import { toast } from '@/utils/toast'
|
||||
import { stringifyQuery } from './queryString'
|
||||
|
||||
export type CustomRequestOptions = UniApp.RequestOptions & {
|
||||
query?: Record<string, any>
|
||||
/** 出错时是否隐藏错误提示 */
|
||||
hideErrorToast?: boolean
|
||||
} & IUniUploadFileOptions // 添加uni.uploadFile参数类型
|
||||
|
||||
export const httpInterceptor = {
|
||||
async invoke(options: CustomRequestOptions) {
|
||||
// 1. 处理 query 参数
|
||||
if (options.query) {
|
||||
const queryStr = stringifyQuery(options.query)
|
||||
options.url += options.url.includes('?') ? `&${queryStr}` : `?${queryStr}`
|
||||
}
|
||||
|
||||
// 2. 获取用户信息
|
||||
const userStore = useUserStore()
|
||||
const userInfo = userStore.userInfo as IUserInfo
|
||||
const { token, userid, orgid } = userInfo || {}
|
||||
const isLogin = options.url.includes('login')
|
||||
|
||||
// 3. 获取 db 配置
|
||||
const configMap = await getDbConfigs()
|
||||
if (!configMap) {
|
||||
toast.error('获取机构配置失败,请联系管理人员!')
|
||||
if (!isLogin) {
|
||||
userStore.reset()
|
||||
uni.navigateTo({ url: '/pages-sub/login/index' })
|
||||
}
|
||||
|
||||
return Promise.reject(new Error('机构码无效'))
|
||||
}
|
||||
console.log(configMap)
|
||||
|
||||
// 4. 解析 baseUrl
|
||||
let baseUrl = ''
|
||||
if (isLogin) {
|
||||
const orgidFromData = (options.data as AnyObject)?.orgid || ''
|
||||
baseUrl = configMap[orgidFromData] || ''
|
||||
}
|
||||
else {
|
||||
baseUrl = configMap[orgid || ''] || ''
|
||||
}
|
||||
|
||||
if (!baseUrl) {
|
||||
toast.error('机构码有误,请重新登陆!')
|
||||
if (!isLogin) {
|
||||
userStore.reset()
|
||||
uni.navigateTo({ url: '/pages-sub/login/index' })
|
||||
}
|
||||
return Promise.reject(new Error('机构码无效'))
|
||||
}
|
||||
|
||||
// 5. 设置请求头
|
||||
if (!isLogin) {
|
||||
options.header = {
|
||||
...options.header,
|
||||
Authorization: `Bearer ${token}`,
|
||||
userid: encodeURIComponent(userid),
|
||||
orgid,
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 拼接完整 URL、设置超时与平台信息
|
||||
options.url = joinUrl(baseUrl, options.url)
|
||||
options.timeout = 20000
|
||||
options.header = {
|
||||
...options.header,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export const requestInterceptor = {
|
||||
install() {
|
||||
// 拦截 request 请求
|
||||
// uni.addInterceptor('request', httpInterceptor)
|
||||
// 拦截 uploadFile 文件上传
|
||||
// uni.addInterceptor('uploadFile', httpInterceptor)
|
||||
},
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/**
|
||||
* 将对象序列化为URL查询字符串,用于替代第三方的 qs 库,节省宝贵的体积
|
||||
* 支持基本类型值和数组,不支持嵌套对象
|
||||
* @param obj 要序列化的对象
|
||||
* @returns 序列化后的查询字符串
|
||||
*/
|
||||
export function stringifyQuery(obj: Record<string, any>): string {
|
||||
if (!obj || typeof obj !== 'object' || Array.isArray(obj))
|
||||
return ''
|
||||
|
||||
return Object.entries(obj)
|
||||
.filter(([_, value]) => value !== undefined && value !== null)
|
||||
.map(([key, value]) => {
|
||||
// 对键进行编码
|
||||
const encodedKey = encodeURIComponent(key)
|
||||
|
||||
// 处理数组类型
|
||||
if (Array.isArray(value)) {
|
||||
return value
|
||||
.filter(item => item !== undefined && item !== null)
|
||||
.map(item => `${encodedKey}=${encodeURIComponent(item)}`)
|
||||
.join('&')
|
||||
}
|
||||
|
||||
// 处理基本类型
|
||||
return `${encodedKey}=${encodeURIComponent(value)}`
|
||||
})
|
||||
.join('&')
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import { httpPostForm } from './index'
|
||||
|
||||
interface getAutocompleteDataParams {
|
||||
q: string
|
||||
page: number
|
||||
limit: number
|
||||
viewname: string
|
||||
field: string
|
||||
}
|
||||
|
||||
export function getAutocompleteDataApi(params: getAutocompleteDataParams) {
|
||||
return httpPostForm<any>('/getAutocompleteData1.jsp', params)
|
||||
}
|
||||
|
||||
interface getAutocompleteDataExParams {
|
||||
q: string
|
||||
page: number
|
||||
limit: number
|
||||
viewname: string
|
||||
field: string
|
||||
userid?: string
|
||||
searchcondition: string
|
||||
}
|
||||
export function getAutocompleteDataExApi(params: getAutocompleteDataExParams) {
|
||||
return httpPostForm<any>('/getAutocompleteDataEx1.jsp', params)
|
||||
}
|
||||
|
||||
interface getAutocompleteDataSingleParams {
|
||||
q: string
|
||||
page: number
|
||||
limit: number
|
||||
viewname: string
|
||||
field: string
|
||||
userid?: string
|
||||
}
|
||||
|
||||
export function getAutocompleteDataSingleApi(params: getAutocompleteDataSingleParams) {
|
||||
return httpPostForm<any>('/getAutocompleteDataSingle.jsp', params)
|
||||
}
|
||||
Whitespace-only changes.
@@ -8,17 +8,25 @@ export const navigateToInterceptor = {
|
||||
invoke(args: UniApp.NavigateToOptions) {
|
||||
const userStore = useUserStore()
|
||||
|
||||
console.log('switch')
|
||||
|
||||
// 白名单直接放行
|
||||
if (whiteList.some(path => args.url.startsWith(path))) {
|
||||
return args
|
||||
}
|
||||
console.log('放行')
|
||||
|
||||
// 调用 requireLogin,如果未登录,会自动提示并跳转
|
||||
const ok = userStore.requireLogin()
|
||||
|
||||
console.log(`验证结果:${ok}`)
|
||||
|
||||
if (!ok) {
|
||||
return false // 阻止路由跳转
|
||||
}
|
||||
|
||||
console.log('已登录放行')
|
||||
|
||||
return args // 已登录则放行
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2,15 +2,18 @@
|
||||
import type { FieldValidateError, FormExpose, FormRules } from 'sard-uniapp'
|
||||
import { getWindowInfo, toast } from 'sard-uniapp'
|
||||
import { reactive, ref } from 'vue'
|
||||
import { loginOrgApi } from '@/request/api'
|
||||
import { useUserStore } from '@/store'
|
||||
|
||||
const useStore = useUserStore()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const startOffset = ref(getWindowInfo().statusBarHeight + 60)
|
||||
|
||||
const loginLoading = ref(false)
|
||||
|
||||
const formModel = reactive({
|
||||
orgid: '',
|
||||
userid: '',
|
||||
orgid: userStore.userInfo.orgid,
|
||||
userid: userStore.userInfo.userid,
|
||||
password: '',
|
||||
})
|
||||
|
||||
@@ -31,7 +34,35 @@ function onSubmit() {
|
||||
formRef.value
|
||||
?.validate()
|
||||
.then(() => {
|
||||
loginLoading.value = true
|
||||
loginOrgApi({
|
||||
orgid: formModel.orgid,
|
||||
userid: formModel.userid,
|
||||
password: formModel.password,
|
||||
}).then((res) => {
|
||||
if (res.data) {
|
||||
userStore.userInfo.orgid = formModel.orgid
|
||||
userStore.userInfo.userid = formModel.userid
|
||||
userStore.userInfo.token = res.data
|
||||
|
||||
uni.showToast({
|
||||
title: '登录成功',
|
||||
icon: 'success',
|
||||
})
|
||||
|
||||
uni.switchTab({
|
||||
url: '/pages/home/index',
|
||||
})
|
||||
}
|
||||
else {
|
||||
uni.showToast({
|
||||
title: '登录失败,请稍后再试',
|
||||
icon:'error'
|
||||
})
|
||||
}
|
||||
}).finally(() => {
|
||||
loginLoading.value = false
|
||||
})
|
||||
})
|
||||
.catch((error: FieldValidateError[]) => {
|
||||
toast.fail(error[0].message)
|
||||
@@ -119,7 +150,7 @@ function onSubmit() {
|
||||
</view>
|
||||
|
||||
<view class="footer">
|
||||
<sar-button loading-type="clock" @click="onSubmit">
|
||||
<sar-button loading-type="clock" :loading="loginLoading" @click="onSubmit">
|
||||
登录
|
||||
</sar-button>
|
||||
</view>
|
||||
|
||||
@@ -1,39 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const visible = ref(false)
|
||||
function show() {
|
||||
visible.value = !visible.value
|
||||
}
|
||||
|
||||
function toLogin() {
|
||||
uni.navigateTo({
|
||||
url: '/pages-sub/login/index',
|
||||
})
|
||||
}
|
||||
const label = ref('004')
|
||||
const value = ref('004')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<picker-select
|
||||
v-model:visible="visible"
|
||||
/>
|
||||
<sar-list card>
|
||||
<sar-list-item hover arrow title="底部划出" @click="show()" />
|
||||
</sar-list>
|
||||
<sar-form label-width="240rpx">
|
||||
<sar-form-item label="Activity name">
|
||||
<sar-form-item label="label">
|
||||
<picker-select-input
|
||||
v-model:label="label"
|
||||
v-model:value="value"
|
||||
viewname="b_user"
|
||||
label-field="b_name"
|
||||
value-field="b_id"
|
||||
search-type="ex"
|
||||
field1="b_name"
|
||||
field2="b_id"
|
||||
/>
|
||||
</sar-form-item>
|
||||
<sar-form-item label="label">
|
||||
<picker-select-input
|
||||
v-model:label="label"
|
||||
v-model:value="value"
|
||||
viewname="b_con_type"
|
||||
label-field="b_name"
|
||||
value-field="b_name"
|
||||
field1="b_name"
|
||||
field2="b_id"
|
||||
/>
|
||||
</sar-form-item>
|
||||
</sar-form>
|
||||
|
||||
<sar-button @click="toLogin">
|
||||
登录
|
||||
</sar-button>
|
||||
<!-- 调试输出 -->
|
||||
<view class="p-4">
|
||||
<view>当前 label: {{ label }}</view>
|
||||
<view>当前 value: {{ value }}</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import request from './index'
|
||||
|
||||
interface LoginOrgParams {
|
||||
orgid: string
|
||||
userid: string
|
||||
password: string
|
||||
}
|
||||
|
||||
export function loginOrgApi(params: LoginOrgParams) {
|
||||
return request.postForm('/login_org.jsp', params)
|
||||
}
|
||||
|
||||
interface GetAutocompleteDataParams {
|
||||
q: string
|
||||
page: number
|
||||
limit: number
|
||||
viewname: string
|
||||
field1: string
|
||||
field2: string
|
||||
userid?: string
|
||||
}
|
||||
|
||||
export function getAutocompleteDataApi(params: GetAutocompleteDataParams) {
|
||||
return request.postForm('/getAutocompleteData.jsp', params)
|
||||
}
|
||||
|
||||
interface GetAutocompleteDataExParams {
|
||||
q: string
|
||||
page: number
|
||||
limit: number
|
||||
viewname: string
|
||||
field1: string
|
||||
field2: string
|
||||
searchcondition: string
|
||||
userid?: string
|
||||
}
|
||||
|
||||
export function getAutocompleteDataExApi(params: GetAutocompleteDataExParams) {
|
||||
return request.postForm('/getAutocompleteDataEx.jsp', params)
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
import { toast } from 'sard-uniapp'
|
||||
import dbJson from '@/static/db.json'
|
||||
import { useUserStore } from '@/store'
|
||||
import { joinUrl } from '@/utils/common'
|
||||
import { isH5 } from '@/utils/platform'
|
||||
|
||||
function request<T = any>(option: UniApp.RequestOptions): Promise<IResData<T>> & { abort: () => void } {
|
||||
let task: UniApp.RequestTask | undefined
|
||||
let aborted = false
|
||||
|
||||
const promise = new Promise<IResData<T>>((resolve, reject) => {
|
||||
// 判断是否是登录请求
|
||||
const isLogin = option.url?.includes('login')
|
||||
|
||||
const readDbJson = new Promise<Record<string, string>>((res, rej) => {
|
||||
if (isH5) {
|
||||
// H5:用 request 请求,带时间戳保证最新
|
||||
const timestamp = Date.now()
|
||||
uni.request({
|
||||
url: `/static/db.json?t=${timestamp}`,
|
||||
method: 'GET',
|
||||
success: r => res(r.data as Record<string, string>),
|
||||
fail: err => rej(err),
|
||||
})
|
||||
}
|
||||
else {
|
||||
// 非 H5 直接返回本地导入的 JSON
|
||||
res(dbJson)
|
||||
}
|
||||
})
|
||||
|
||||
readDbJson
|
||||
.then((dbRes) => {
|
||||
if (aborted)
|
||||
return reject(new Error('请求已取消'))
|
||||
|
||||
// 组织请求 URL
|
||||
let reqUrl = ''
|
||||
const userStore = useUserStore()
|
||||
const { token, userid, orgid } = userStore.userInfo || {}
|
||||
|
||||
if (!dbRes || Object.keys(dbRes).length === 0) {
|
||||
toast.fail('机构配置获取失败,请联系管理人员!')
|
||||
return reject(new Error('机构配置获取失败,请联系管理人员!'))
|
||||
}
|
||||
|
||||
if (isLogin) {
|
||||
const orgidFromData = (option.data as any)?.orgid || ''
|
||||
reqUrl = dbRes[orgidFromData]
|
||||
}
|
||||
else {
|
||||
reqUrl = dbRes[orgid || '']
|
||||
}
|
||||
|
||||
if (!reqUrl) {
|
||||
const pages = getCurrentPages()
|
||||
const currentPage = pages[pages.length - 1]
|
||||
const currentRoute = `/${currentPage.route}`
|
||||
|
||||
if (currentRoute !== '/pages-sub/login/index') {
|
||||
uni.showToast({ title: '当前登录已过期,请重新登录', icon: 'error' })
|
||||
|
||||
uni.navigateTo({
|
||||
url: '/pages-sub/login/index',
|
||||
})
|
||||
}
|
||||
else {
|
||||
uni.showToast({ title: '机构码错误', icon: 'error' })
|
||||
}
|
||||
|
||||
return reject(new Error('机构码错误'))
|
||||
}
|
||||
|
||||
// 设置 headers
|
||||
const headers: Record<string, any> = { ...(option.header || {}) }
|
||||
if (!isLogin) {
|
||||
headers.Authorization = `Bearer ${token}`
|
||||
headers.userid = encodeURIComponent(userid)
|
||||
headers.orgid = orgid
|
||||
}
|
||||
|
||||
// 拼接完整 URL
|
||||
option.url = joinUrl(reqUrl, option.url || '')
|
||||
option.header = headers
|
||||
|
||||
// 发请求
|
||||
task = uni.request({
|
||||
...option,
|
||||
url: option.url,
|
||||
success: (res) => {
|
||||
if (aborted)
|
||||
return reject(new Error('请求已取消'))
|
||||
|
||||
let data: IResData<T>
|
||||
try {
|
||||
// 解析最外层响应
|
||||
data = typeof res.data === 'string' ? JSON.parse(res.data) : res.data
|
||||
|
||||
// 如果 data.data 是字符串,则解析成对象
|
||||
if (data.data && typeof data.data === 'string') {
|
||||
try {
|
||||
data.data = JSON.parse(data.data)
|
||||
}
|
||||
catch {
|
||||
// 如果解析失败,保持原始字符串即可
|
||||
console.warn('data.data 不是合法 JSON 字符串', data.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.log(`解析响应JSON失败:${err}`)
|
||||
toast.fail('响应数据解析失败')
|
||||
return reject(new Error('响应数据解析失败'))
|
||||
}
|
||||
|
||||
if (data.code === 200) {
|
||||
resolve(data)
|
||||
}
|
||||
else {
|
||||
toast.fail(data.message || '请求错误')
|
||||
reject(new Error(data.message || '请求错误'))
|
||||
}
|
||||
|
||||
// 登录失效处理
|
||||
if (data.code === 600) {
|
||||
uni.showToast({ title: '请重新登录', icon: 'none' })
|
||||
|
||||
const pages = getCurrentPages()
|
||||
const currentPage = pages[pages.length - 1]
|
||||
const currentRoute = `/${currentPage.route}`
|
||||
if (currentRoute !== '/pages-sub/login/index') {
|
||||
uni.navigateTo({ url: '/pages-sub/login/index' })
|
||||
}
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
if (aborted)
|
||||
return reject(new Error('请求已取消'))
|
||||
console.log(err)
|
||||
toast.fail('网络请求失败,请联系管理人员')
|
||||
|
||||
reject(new Error(err.errMsg || '网络请求失败'))
|
||||
},
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.fail('机构配置获取失败,请联系管理人员!')
|
||||
console.error(err)
|
||||
reject(new Error('机构配置获取失败,请联系管理人员!'))
|
||||
})
|
||||
}) as Promise<IResData<T>> & { abort: () => void }
|
||||
|
||||
promise.abort = () => {
|
||||
aborted = true
|
||||
task?.abort()
|
||||
}
|
||||
|
||||
return promise
|
||||
}
|
||||
|
||||
// GET
|
||||
request.get = <T = any>(url: string, data?: any, option?: UniApp.RequestOptions) => {
|
||||
return request<T>({
|
||||
method: 'GET',
|
||||
url,
|
||||
data,
|
||||
...option,
|
||||
})
|
||||
}
|
||||
|
||||
// PUT
|
||||
request.put = <T = any>(url: string, data?: any, option?: UniApp.RequestOptions) => {
|
||||
return request<T>({
|
||||
method: 'PUT',
|
||||
url,
|
||||
data,
|
||||
...option,
|
||||
})
|
||||
}
|
||||
|
||||
// DELETE
|
||||
request.delete = <T = any>(url: string, data?: any, option?: UniApp.RequestOptions) => {
|
||||
return request<T>({
|
||||
method: 'DELETE',
|
||||
url,
|
||||
data,
|
||||
...option,
|
||||
})
|
||||
}
|
||||
|
||||
// POST JSON
|
||||
request.postJson = <T = any>(url: string, data?: any, option?: UniApp.RequestOptions) => {
|
||||
return request<T>({
|
||||
method: 'POST',
|
||||
url,
|
||||
data,
|
||||
header: {
|
||||
'Content-Type': 'application/json',
|
||||
...(option?.header || {}),
|
||||
},
|
||||
...option,
|
||||
})
|
||||
}
|
||||
|
||||
// POST 表单
|
||||
request.postForm = <T = any>(url: string, data?: any, option?: UniApp.RequestOptions) => {
|
||||
return request<T>({
|
||||
method: 'POST',
|
||||
url,
|
||||
data,
|
||||
header: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
...(option?.header || {}),
|
||||
},
|
||||
...option,
|
||||
})
|
||||
}
|
||||
|
||||
export default request
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"g3cd": "http://127.0.0.1:8081/g3xcjt-server/h5"
|
||||
"g3cd": "http://192.168.1.136:8081/g3xcxlg-server/h5"
|
||||
}
|
||||
@@ -22,24 +22,26 @@ export const useUserStore = defineStore(
|
||||
|
||||
function requireLogin(): boolean {
|
||||
if (!isLogined.value) {
|
||||
uni.showToast({
|
||||
title: '请先登录',
|
||||
icon: 'none',
|
||||
})
|
||||
|
||||
const pages = getCurrentPages()
|
||||
const currentPage = pages[pages.length - 1]
|
||||
const path = currentPage?.route ? `/${currentPage.route}` : ''
|
||||
|
||||
if (!path || path !== '/pages-sub/login/index') {
|
||||
uni.showToast({
|
||||
title: '请先登录',
|
||||
icon: 'none',
|
||||
success: () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages-sub/login/index',
|
||||
})
|
||||
},
|
||||
// 等 toast 显示完再跳转
|
||||
uni.navigateTo({
|
||||
url: '/pages-sub/login/index',
|
||||
})
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,122 +1,773 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"/>
|
||||
<title>登录</title>
|
||||
<style>
|
||||
:root {
|
||||
--primary: #409eff;
|
||||
--text: #222;
|
||||
--subtext: #666;
|
||||
--bg: #f5f7fa;
|
||||
}
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>后台管理系统</title>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" />
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css" />
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
background: var(--bg);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
}
|
||||
:root {
|
||||
--primary: #3498db;
|
||||
--secondary: #2c3e50;
|
||||
--success: #2ecc71;
|
||||
--warning: #f39c12;
|
||||
--danger: #e74c3c;
|
||||
--light: #f8f9fa;
|
||||
--dark: #343a40;
|
||||
--gray: #6c757d;
|
||||
--border: #e9ecef;
|
||||
}
|
||||
|
||||
.login-container {
|
||||
width: 92%;
|
||||
max-width: 400px;
|
||||
background: #fff;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
|
||||
padding: 28px 22px;
|
||||
}
|
||||
body {
|
||||
background-color: #f5f7fa;
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.login-header {
|
||||
text-align: center;
|
||||
font-size: 1.3rem;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
/* 顶部导航 */
|
||||
.header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
background-color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 24px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
.form-group input {
|
||||
width: 100%;
|
||||
padding: 12px 14px;
|
||||
font-size: 0.95rem;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 10px;
|
||||
outline: none;
|
||||
transition: border 0.2s;
|
||||
}
|
||||
.logo i {
|
||||
margin-right: 10px;
|
||||
color: var(--primary);
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.form-group input:focus {
|
||||
border-color: var(--primary);
|
||||
}
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.login-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 0.85rem;
|
||||
margin: 6px 0 18px;
|
||||
color: var(--subtext);
|
||||
}
|
||||
.notification {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.login-actions label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.notification i {
|
||||
font-size: 20px;
|
||||
color: var(--gray);
|
||||
}
|
||||
|
||||
.login-actions a {
|
||||
color: var(--primary);
|
||||
text-decoration: none;
|
||||
}
|
||||
.notification-badge {
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
right: -5px;
|
||||
background-color: var(--danger);
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.login-button {
|
||||
width: 100%;
|
||||
padding: 13px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
background: var(--primary);
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.user-profile {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.login-button:active {
|
||||
background: #318ae6;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-container">
|
||||
<div class="login-header">系统登录</div>
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<input type="text" placeholder="请输入机构码">
|
||||
.avatar {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.username {
|
||||
font-weight: 500;
|
||||
color: var(--dark);
|
||||
}
|
||||
|
||||
/* 侧边导航 */
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
top: 60px;
|
||||
left: 0;
|
||||
width: 240px;
|
||||
height: calc(100vh - 60px);
|
||||
background-color: var(--secondary);
|
||||
color: white;
|
||||
padding: 24px 0;
|
||||
overflow-y: auto;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
padding: 0 24px 10px;
|
||||
font-size: 14px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
padding: 12px 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.nav-item:hover,
|
||||
.nav-item.active {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.nav-item i {
|
||||
font-size: 18px;
|
||||
width: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 主要内容区域 */
|
||||
.main-content {
|
||||
margin-left: 240px;
|
||||
margin-top: 60px;
|
||||
padding: 24px;
|
||||
min-height: calc(100vh - 60px);
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 24px;
|
||||
color: var(--secondary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.welcome-text {
|
||||
font-size: 16px;
|
||||
color: var(--gray);
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
/* 轮播图 */
|
||||
.swiper-container {
|
||||
width: 100%;
|
||||
height: 280px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.swiper-slide {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
padding: 30px;
|
||||
color: white;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.slide-content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
max-width: 70%;
|
||||
}
|
||||
|
||||
.slide-title {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.slide-desc {
|
||||
font-size: 16px;
|
||||
opacity: 0.9;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.swiper-slide::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent 60%);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.swiper-pagination-bullet {
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
opacity: 1;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.swiper-pagination-bullet-active {
|
||||
background: white;
|
||||
}
|
||||
|
||||
/* 功能模块 */
|
||||
.section-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin: 32px 0 20px;
|
||||
color: var(--secondary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.section-title i {
|
||||
margin-right: 10px;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.features-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 24px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
transition:
|
||||
transform 0.3s,
|
||||
box-shadow 0.3s;
|
||||
cursor: pointer;
|
||||
border-left: 4px solid var(--primary);
|
||||
}
|
||||
|
||||
.feature-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 20px;
|
||||
font-size: 28px;
|
||||
color: white;
|
||||
background: linear-gradient(135deg, var(--primary), #2c80c4);
|
||||
}
|
||||
|
||||
.feature-name {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 10px;
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
.feature-desc {
|
||||
font-size: 14px;
|
||||
color: var(--gray);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* 数据统计 */
|
||||
.stats-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||
gap: 24px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.stat-title {
|
||||
font-size: 14px;
|
||||
color: var(--gray);
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
.stat-change {
|
||||
font-size: 14px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.positive {
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
.negative {
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
/* 快捷操作 */
|
||||
.quick-actions {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.actions-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.action-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
background-color: #f8f9fa;
|
||||
transition: all 0.3s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.action-item:hover {
|
||||
background-color: #e9ecef;
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
|
||||
.action-icon {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
color: white;
|
||||
margin-bottom: 12px;
|
||||
background: linear-gradient(135deg, var(--primary), #2c80c4);
|
||||
}
|
||||
|
||||
.action-name {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--dark);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 底部 */
|
||||
.footer {
|
||||
padding: 24px;
|
||||
text-align: center;
|
||||
color: var(--gray);
|
||||
font-size: 14px;
|
||||
border-top: 1px solid var(--border);
|
||||
background: white;
|
||||
margin-left: 240px;
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 992px) {
|
||||
.sidebar {
|
||||
width: 70px;
|
||||
}
|
||||
|
||||
.nav-title,
|
||||
.nav-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
justify-content: center;
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin-left: 70px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-left: 70px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.swiper-container {
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.slide-title {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.slide-desc {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.features-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- 顶部导航 -->
|
||||
<header class="header">
|
||||
<div class="logo">
|
||||
<i class="fas fa-cogs"></i>
|
||||
<span>管理系统</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="text" placeholder="请输入账号">
|
||||
<div class="header-right">
|
||||
<div class="notification">
|
||||
<i class="fas fa-bell"></i>
|
||||
<span class="notification-badge">3</span>
|
||||
</div>
|
||||
<div class="user-profile">
|
||||
<div class="avatar">A</div>
|
||||
<div class="username">管理员</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="password" placeholder="请输入密码">
|
||||
</header>
|
||||
|
||||
<!-- 侧边导航 -->
|
||||
<div class="sidebar">
|
||||
<div class="nav-title">主菜单</div>
|
||||
<div class="nav-item active">
|
||||
<i class="fas fa-home"></i>
|
||||
<span class="nav-text">控制台</span>
|
||||
</div>
|
||||
<div class="login-actions">
|
||||
<label>
|
||||
<input type="checkbox" style="accent-color: var(--primary);"> 记住我
|
||||
</label>
|
||||
<a href="#">忘记密码?</a>
|
||||
<div class="nav-item">
|
||||
<i class="fas fa-users"></i>
|
||||
<span class="nav-text">用户管理</span>
|
||||
</div>
|
||||
<button type="submit" class="login-button">登录</button>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
<div class="nav-item">
|
||||
<i class="fas fa-lock"></i>
|
||||
<span class="nav-text">权限管理</span>
|
||||
</div>
|
||||
<div class="nav-item">
|
||||
<i class="fas fa-database"></i>
|
||||
<span class="nav-text">数据管理</span>
|
||||
</div>
|
||||
<div class="nav-item">
|
||||
<i class="fas fa-chart-line"></i>
|
||||
<span class="nav-text">统计分析</span>
|
||||
</div>
|
||||
|
||||
<div class="nav-title">系统设置</div>
|
||||
<div class="nav-item">
|
||||
<i class="fas fa-cog"></i>
|
||||
<span class="nav-text">系统设置</span>
|
||||
</div>
|
||||
<div class="nav-item">
|
||||
<i class="fas fa-tools"></i>
|
||||
<span class="nav-text">维护工具</span>
|
||||
</div>
|
||||
<div class="nav-item">
|
||||
<i class="fas fa-file-alt"></i>
|
||||
<span class="nav-text">日志管理</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主要内容 -->
|
||||
<div class="main-content">
|
||||
<div class="page-title">
|
||||
<div>控制台 <span class="welcome-text">欢迎回来,管理员</span></div>
|
||||
<div>
|
||||
<button
|
||||
style="
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 16px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
"
|
||||
>
|
||||
<i class="fas fa-sync-alt"></i> 刷新数据
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 轮播图 -->
|
||||
<div class="swiper-container">
|
||||
<div class="swiper-wrapper">
|
||||
<div
|
||||
class="swiper-slide"
|
||||
style="
|
||||
background-image: url('https://images.unsplash.com/photo-1552664730-d307ca884978?ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80');
|
||||
"
|
||||
>
|
||||
<div class="slide-content">
|
||||
<div class="slide-title">系统维护通知</div>
|
||||
<div class="slide-desc">本周六凌晨2:00-4:00将进行系统维护,请提前做好准备</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="swiper-slide"
|
||||
style="
|
||||
background-image: url('https://images.unsplash.com/photo-1517245386807-bb43f82c33c4?ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80');
|
||||
"
|
||||
>
|
||||
<div class="slide-content">
|
||||
<div class="slide-title">新版本发布</div>
|
||||
<div class="slide-desc">V2.5.0版本已上线,包含多项功能优化和安全更新</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="swiper-slide"
|
||||
style="
|
||||
background-image: url('https://images.unsplash.com/photo-1550439062-609e1531270e?ixlib=rb-1.2.1&auto=format&fit=crop&w=1350&q=80');
|
||||
"
|
||||
>
|
||||
<div class="slide-content">
|
||||
<div class="slide-title">安全提示</div>
|
||||
<div class="slide-desc">请定期修改密码并启用双重验证,确保账户安全</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="swiper-pagination"></div>
|
||||
</div>
|
||||
|
||||
<!-- 数据统计 -->
|
||||
<div class="stats-container">
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon" style="background: linear-gradient(135deg, #3498db, #2c80c4)">
|
||||
<i class="fas fa-users"></i>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-title">用户总数</div>
|
||||
<div class="stat-value">8,742</div>
|
||||
<div class="stat-change positive"><i class="fas fa-arrow-up"></i> 12.5% 较上月</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon" style="background: linear-gradient(135deg, #2ecc71, #27ae60)">
|
||||
<i class="fas fa-chart-line"></i>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-title">活跃用户</div>
|
||||
<div class="stat-value">2,356</div>
|
||||
<div class="stat-change positive"><i class="fas fa-arrow-up"></i> 5.3% 较上周</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon" style="background: linear-gradient(135deg, #f39c12, #e67e22)">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-title">待处理问题</div>
|
||||
<div class="stat-value">24</div>
|
||||
<div class="stat-change negative"><i class="fas fa-arrow-down"></i> 3.2% 较上周</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon" style="background: linear-gradient(135deg, #9b59b6, #8e44ad)">
|
||||
<i class="fas fa-tasks"></i>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-title">完成率</div>
|
||||
<div class="stat-value">94.8%</div>
|
||||
<div class="stat-change positive"><i class="fas fa-arrow-up"></i> 2.1% 较上月</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 功能模块 -->
|
||||
<div class="section-title"><i class="fas fa-th-large"></i> 功能模块</div>
|
||||
<div class="features-grid">
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<i class="fas fa-users"></i>
|
||||
</div>
|
||||
<div class="feature-name">用户管理</div>
|
||||
<div class="feature-desc">管理系统用户信息,分配角色和权限</div>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<i class="fas fa-lock"></i>
|
||||
</div>
|
||||
<div class="feature-name">权限控制</div>
|
||||
<div class="feature-desc">设置角色权限,管理访问控制列表</div>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<i class="fas fa-database"></i>
|
||||
</div>
|
||||
<div class="feature-name">数据管理</div>
|
||||
<div class="feature-desc">管理数据库,执行数据备份与恢复</div>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<i class="fas fa-chart-bar"></i>
|
||||
</div>
|
||||
<div class="feature-name">统计分析</div>
|
||||
<div class="feature-desc">查看系统使用数据,生成统计报表</div>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<i class="fas fa-cog"></i>
|
||||
</div>
|
||||
<div class="feature-name">系统设置</div>
|
||||
<div class="feature-desc">配置系统参数,管理全局设置</div>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<i class="fas fa-file-alt"></i>
|
||||
</div>
|
||||
<div class="feature-name">日志审计</div>
|
||||
<div class="feature-desc">查看操作日志,审计系统活动</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 快捷操作 -->
|
||||
<div class="section-title"><i class="fas fa-bolt"></i> 快捷操作</div>
|
||||
<div class="quick-actions">
|
||||
<div class="actions-grid">
|
||||
<div class="action-item">
|
||||
<div class="action-icon">
|
||||
<i class="fas fa-user-plus"></i>
|
||||
</div>
|
||||
<div class="action-name">添加用户</div>
|
||||
</div>
|
||||
<div class="action-item">
|
||||
<div class="action-icon">
|
||||
<i class="fas fa-key"></i>
|
||||
</div>
|
||||
<div class="action-name">重置密码</div>
|
||||
</div>
|
||||
<div class="action-item">
|
||||
<div class="action-icon">
|
||||
<i class="fas fa-download"></i>
|
||||
</div>
|
||||
<div class="action-name">数据导出</div>
|
||||
</div>
|
||||
<div class="action-item">
|
||||
<div class="action-icon">
|
||||
<i class="fas fa-sync"></i>
|
||||
</div>
|
||||
<div class="action-name">刷新缓存</div>
|
||||
</div>
|
||||
<div class="action-item">
|
||||
<div class="action-icon">
|
||||
<i class="fas fa-shield-alt"></i>
|
||||
</div>
|
||||
<div class="action-name">安全检查</div>
|
||||
</div>
|
||||
<div class="action-item">
|
||||
<div class="action-icon">
|
||||
<i class="fas fa-file-export"></i>
|
||||
</div>
|
||||
<div class="action-name">生成报表</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部 -->
|
||||
<footer class="footer">
|
||||
<p>© 2023 后台管理系统 | 版本 2.5.0 | 技术支持: tech@admin.com</p>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js"></script>
|
||||
<script>
|
||||
// 初始化轮播图
|
||||
const swiper = new Swiper('.swiper-container', {
|
||||
direction: 'horizontal',
|
||||
loop: true,
|
||||
autoplay: {
|
||||
delay: 5000,
|
||||
disableOnInteraction: false,
|
||||
},
|
||||
pagination: {
|
||||
el: '.swiper-pagination',
|
||||
clickable: true,
|
||||
},
|
||||
})
|
||||
|
||||
// 模拟功能模块点击
|
||||
document.querySelectorAll('.feature-card').forEach((card) => {
|
||||
card.addEventListener('click', function () {
|
||||
const featureName = this.querySelector('.feature-name').textContent
|
||||
alert(`即将进入: ${featureName} 模块`)
|
||||
})
|
||||
})
|
||||
|
||||
// 模拟快捷操作点击
|
||||
document.querySelectorAll('.action-item').forEach((item) => {
|
||||
item.addEventListener('click', function () {
|
||||
const actionName = this.querySelector('.action-name').textContent
|
||||
alert(`执行操作: ${actionName}`)
|
||||
})
|
||||
})
|
||||
|
||||
// 模拟通知点击
|
||||
document.querySelector('.notification').addEventListener('click', function () {
|
||||
alert('您有3条未读通知')
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user