fix: 显式声明接口参数名并优化移动端若干页面
生产环境未保留方法参数名时 Spring 无法绑定 @RequestParam/@PathVariable,补齐注解名称并在 Maven 编译中强制 -parameters;同时优化走访、勤工助学、请假和宿舍点名页交互。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
a15efa8ef8
commit
1d67506451
187 files changed
+1769
-1295
No files matched your search
@@ -8,13 +8,15 @@ NODE_ENV=production
|
||||
VUE_APP_API_BASE_URL=/prod-api
|
||||
# 如果生产环境需要直接访问完整URL,请修改为:
|
||||
# VUE_APP_API_BASE_URL=https://api.example.com
|
||||
VUE_APP_DEV_SERVER_URL=https://m.hzdyjsxy.cn:18080
|
||||
#VUE_APP_DEV_SERVER_URL=https://m.hzdyjsxy.cn:18080
|
||||
VUE_APP_DEV_SERVER_URL=http://m.hzdyjsxy.cn:18080
|
||||
VUE_APP_REQUEST_TIMEOUT=300000
|
||||
VUE_APP_REPEAT_SUBMIT_INTERVAL=500
|
||||
|
||||
# 前端域名配置
|
||||
# 🚨 生产环境部署前必须修改此地址!
|
||||
VUE_APP_FRONTEND_URL=https://m.hzdyjsxy.cn:18080/login?redirect=/index
|
||||
#VUE_APP_FRONTEND_URL=https://m.hzdyjsxy.cn:18080/login?redirect=/index
|
||||
VUE_APP_FRONTEND_URL=http://m.hzdyjsxy.cn:18080/login?redirect=/index
|
||||
|
||||
# 加密配置
|
||||
VUE_APP_RSA_PUBLIC_KEY=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==
|
||||
|
||||
+214
-113
@@ -9,15 +9,29 @@
|
||||
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-bar">
|
||||
<u-search
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索学生姓名、学号或企业"
|
||||
:show-action="false"
|
||||
@search="handleSearch"
|
||||
@custom="handleSearch"
|
||||
bg-color="#f6f6f6"
|
||||
shape="round"
|
||||
/>
|
||||
<view class="search-row">
|
||||
<view class="search-input-wrap">
|
||||
<u-search
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索学生姓名、学号或企业"
|
||||
:show-action="false"
|
||||
@search="handleSearch"
|
||||
@custom="handleSearch"
|
||||
bg-color="#f6f6f6"
|
||||
shape="round"
|
||||
/>
|
||||
</view>
|
||||
<view
|
||||
class="filter-toggle"
|
||||
@click="showFilter = !showFilter"
|
||||
>
|
||||
<u-icon
|
||||
:name="showFilter ? 'arrow-up' : 'list'"
|
||||
size="16"
|
||||
color="#606266"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 筛选条件 -->
|
||||
@@ -26,8 +40,12 @@
|
||||
<u-input
|
||||
v-model="queryParams.visitorName"
|
||||
placeholder="走访人姓名"
|
||||
border="surround"
|
||||
placeholder-style="color: #c0c4cc; font-size: 14px"
|
||||
font-size="14px"
|
||||
shape="circle"
|
||||
border="none"
|
||||
clearable
|
||||
:custom-style="filterInputStyle"
|
||||
/>
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
@@ -35,9 +53,13 @@
|
||||
<u-input
|
||||
v-model="statusText"
|
||||
placeholder="选择走访状态"
|
||||
placeholder-style="color: #c0c4cc; font-size: 14px"
|
||||
font-size="14px"
|
||||
shape="circle"
|
||||
readonly
|
||||
border="surround"
|
||||
border="none"
|
||||
suffix-icon="arrow-down"
|
||||
:custom-style="filterInputStyle"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
@@ -46,9 +68,13 @@
|
||||
<u-input
|
||||
v-model="dateRangeText"
|
||||
placeholder="选择走访日期"
|
||||
placeholder-style="color: #c0c4cc; font-size: 14px"
|
||||
font-size="14px"
|
||||
shape="circle"
|
||||
readonly
|
||||
border="surround"
|
||||
border="none"
|
||||
suffix-icon="calendar"
|
||||
:custom-style="filterInputStyle"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
@@ -74,22 +100,6 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 筛选按钮 -->
|
||||
<view class="action-bar">
|
||||
<u-button
|
||||
type="info"
|
||||
shape="round"
|
||||
size="small"
|
||||
:icon="showFilter ? 'arrow-up' : 'arrow-down'"
|
||||
@click="showFilter = !showFilter"
|
||||
>
|
||||
{{ showFilter ? '收起筛选' : '展开筛选' }}
|
||||
</u-button>
|
||||
<u-button type="primary" shape="round" size="small" icon="plus" @click="handleAdd">
|
||||
新增走访
|
||||
</u-button>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<u-loading-page
|
||||
:loading="loading && visitList.length === 0"
|
||||
@@ -109,46 +119,52 @@
|
||||
:key="item.visitId"
|
||||
@click="handleView(item)"
|
||||
>
|
||||
<!-- 学生信息 -->
|
||||
<!-- 卡片标题:企业名称 + 状态 -->
|
||||
<view class="card-header">
|
||||
<view class="student-info">
|
||||
<text class="student-name">{{ item.studentName }}</text>
|
||||
<text class="student-no">{{ item.studentNo }}</text>
|
||||
<view class="company-title">
|
||||
<view class="company-icon">
|
||||
<u-icon name="home" size="18" color="#3c9cff" />
|
||||
</view>
|
||||
<text class="company-name">{{ item.companyName || '未填写企业' }}</text>
|
||||
</view>
|
||||
<view class="status-badge">
|
||||
<u-tag
|
||||
:text="getStatusLabel(item.status)"
|
||||
:type="getStatusType(item.status)"
|
||||
size="mini"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 企业信息 -->
|
||||
<view class="card-row">
|
||||
<u-icon name="home" size="16" color="#909399" />
|
||||
<text class="info-text">{{ item.companyName || '-' }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 走访信息 -->
|
||||
<view class="card-row">
|
||||
<u-icon name="calendar" size="16" color="#909399" />
|
||||
<text class="info-text">{{ item.visitDate || '-' }}</text>
|
||||
<u-icon
|
||||
name="account"
|
||||
size="16"
|
||||
color="#909399"
|
||||
style="margin-left: 20rpx"
|
||||
<u-tag
|
||||
:text="getStatusLabel(item.status)"
|
||||
:type="getStatusType(item.status)"
|
||||
size="mini"
|
||||
shape="circle"
|
||||
/>
|
||||
<text class="info-text">{{ item.visitorName || '-' }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 班级专业信息 -->
|
||||
<view class="card-row">
|
||||
<u-icon name="bookmark" size="16" color="#909399" />
|
||||
<text class="info-text">
|
||||
{{ item.className || '-' }} / {{ item.majorName || '-' }}
|
||||
</text>
|
||||
<!-- 卡片内容 -->
|
||||
<view class="card-body">
|
||||
<view class="info-row">
|
||||
<u-icon name="account" size="16" color="#909399" />
|
||||
<text class="info-label">学生</text>
|
||||
<text class="info-value">
|
||||
{{ item.studentName || '-' }}
|
||||
<text class="info-sub" v-if="item.studentNo">
|
||||
({{ item.studentNo }})
|
||||
</text>
|
||||
</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<u-icon name="calendar" size="16" color="#909399" />
|
||||
<text class="info-label">日期</text>
|
||||
<text class="info-value">{{ item.visitDate || '-' }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<u-icon name="account-fill" size="16" color="#909399" />
|
||||
<text class="info-label">走访人</text>
|
||||
<text class="info-value">{{ item.visitorName || '-' }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<u-icon name="bookmark" size="16" color="#909399" />
|
||||
<text class="info-label">班级</text>
|
||||
<text class="info-value info-value--ellipsis">
|
||||
{{ item.className || '-' }}
|
||||
<text v-if="item.majorName"> / {{ item.majorName }}</text>
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
@@ -215,9 +231,11 @@
|
||||
<u-picker
|
||||
:show="showStatusPicker"
|
||||
:columns="statusOptions"
|
||||
:default-index="[currentStatusIndex]"
|
||||
keyName="text"
|
||||
:default-index="currentStatusIndex >= 0 ? [currentStatusIndex] : [0]"
|
||||
@confirm="handleStatusConfirm"
|
||||
@cancel="showStatusPicker = false"
|
||||
@close="showStatusPicker = false"
|
||||
/>
|
||||
|
||||
<!-- 日期范围选择器 -->
|
||||
@@ -227,6 +245,11 @@
|
||||
@confirm="handleDateConfirm"
|
||||
@close="showDatePicker = false"
|
||||
/>
|
||||
|
||||
<!-- 悬浮新增按钮 -->
|
||||
<view class="fab-button" @click="handleAdd">
|
||||
<u-icon name="plus" size="24" color="#ffffff" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -269,6 +292,14 @@ export default {
|
||||
showDatePicker: false,
|
||||
dateRangeText: '',
|
||||
selectedDateRange: [],
|
||||
|
||||
// 筛选输入框样式(与搜索框高度一致:u-search 默认 32px)
|
||||
filterInputStyle: {
|
||||
backgroundColor: '#f6f6f6',
|
||||
padding: '0 24rpx',
|
||||
height: '32px',
|
||||
boxSizing: 'border-box',
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
@@ -419,11 +450,14 @@ export default {
|
||||
},
|
||||
|
||||
// 状态选择确认
|
||||
handleStatusConfirm(value) {
|
||||
const selected = value.value[0];
|
||||
this.queryParams.status = selected.value;
|
||||
this.statusText = selected.text;
|
||||
this.currentStatusIndex = value.index[0];
|
||||
handleStatusConfirm(e) {
|
||||
const index = e.indexs[0];
|
||||
const selected = this.statusOptions[0][index];
|
||||
if (selected) {
|
||||
this.queryParams.status = selected.value;
|
||||
this.statusText = selected.text;
|
||||
this.currentStatusIndex = index;
|
||||
}
|
||||
this.showStatusPicker = false;
|
||||
},
|
||||
|
||||
@@ -551,12 +585,37 @@ export default {
|
||||
.search-bar {
|
||||
padding: 20rpx;
|
||||
background: #ffffff;
|
||||
|
||||
.search-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.search-input-wrap {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.filter-toggle {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 50%;
|
||||
background: #f6f6f6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.filter-bar {
|
||||
background: #ffffff;
|
||||
padding: 0 20rpx 20rpx;
|
||||
border-bottom: 1rpx solid #ebeef5;
|
||||
|
||||
.filter-item {
|
||||
margin-bottom: 20rpx;
|
||||
@@ -569,17 +628,30 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx;
|
||||
background: #ffffff;
|
||||
border-bottom: 1rpx solid #ebeef5;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
padding: 20rpx;
|
||||
padding-bottom: 160rpx;
|
||||
}
|
||||
|
||||
.fab-button {
|
||||
position: fixed;
|
||||
right: 20px;
|
||||
bottom: 30px;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #007aff, #0051d9);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4px 16px rgba(0, 122, 255, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
z-index: 999;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
box-shadow: 0 2px 8px rgba(0, 122, 255, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
.empty-container {
|
||||
@@ -590,66 +662,95 @@ export default {
|
||||
.list-content {
|
||||
.visit-card {
|
||||
background: #ffffff;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
transition: all 0.3s ease;
|
||||
border-radius: 16rpx;
|
||||
padding: 0;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(60, 156, 255, 0.08);
|
||||
overflow: hidden;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.99);
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
transform: scale(0.985);
|
||||
box-shadow: 0 2rpx 10rpx rgba(60, 156, 255, 0.06);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 16rpx;
|
||||
align-items: center;
|
||||
padding: 24rpx 24rpx 20rpx;
|
||||
background: linear-gradient(135deg, #f5f9ff 0%, #ffffff 100%);
|
||||
border-bottom: 1rpx solid #f0f3f8;
|
||||
|
||||
.student-info {
|
||||
.company-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
margin-right: 16rpx;
|
||||
|
||||
.student-name {
|
||||
.company-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
|
||||
.company-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-right: 16rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.student-no {
|
||||
font-size: 26rpx;
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.card-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12rpx;
|
||||
.card-body {
|
||||
padding: 20rpx 24rpx 8rpx;
|
||||
|
||||
.info-text {
|
||||
font-size: 28rpx;
|
||||
color: #606266;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
.info-label {
|
||||
width: 80rpx;
|
||||
flex-shrink: 0;
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-size: 28rpx;
|
||||
color: #606266;
|
||||
|
||||
.info-sub {
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
&--ellipsis {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
padding-top: 16rpx;
|
||||
border-top: 1rpx solid #ebeef5;
|
||||
padding: 16rpx 24rpx 20rpx;
|
||||
border-top: 1rpx solid #f0f3f8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,16 +8,38 @@
|
||||
</u-navbar>
|
||||
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-container">
|
||||
<u-search
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索岗位名称或联系老师"
|
||||
:show-action="false"
|
||||
@search="handleSearch"
|
||||
@custom="handleSearch"
|
||||
bg-color="#f5f6fa"
|
||||
shape="round"
|
||||
/>
|
||||
<view class="search-bar">
|
||||
<view class="search-row">
|
||||
<view class="search-input-wrap">
|
||||
<u-search
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索岗位名称或联系老师"
|
||||
:show-action="false"
|
||||
@search="handleSearch"
|
||||
@custom="handleSearch"
|
||||
bg-color="#f6f6f6"
|
||||
shape="round"
|
||||
/>
|
||||
</view>
|
||||
<view
|
||||
v-if="currentTab === 0"
|
||||
class="filter-toggle"
|
||||
@click="showFilter = !showFilter"
|
||||
>
|
||||
<u-icon
|
||||
:name="showFilter ? 'arrow-up' : 'list'"
|
||||
size="16"
|
||||
color="#606266"
|
||||
/>
|
||||
</view>
|
||||
<view
|
||||
v-if="currentTab === 0"
|
||||
class="filter-toggle"
|
||||
@click="refreshData"
|
||||
>
|
||||
<u-icon name="reload" size="16" color="#606266" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 标签页 -->
|
||||
@@ -43,9 +65,13 @@
|
||||
<u-input
|
||||
v-model="scopeText"
|
||||
placeholder="选择发布范围"
|
||||
placeholder-style="color: #c0c4cc; font-size: 14px"
|
||||
font-size="14px"
|
||||
shape="circle"
|
||||
readonly
|
||||
border="surround"
|
||||
border="none"
|
||||
suffix-icon="arrow-down"
|
||||
:custom-style="filterInputStyle"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
@@ -54,18 +80,29 @@
|
||||
<u-input
|
||||
v-model="statusText"
|
||||
placeholder="选择岗位状态"
|
||||
placeholder-style="color: #c0c4cc; font-size: 14px"
|
||||
font-size="14px"
|
||||
shape="circle"
|
||||
readonly
|
||||
border="surround"
|
||||
border="none"
|
||||
suffix-icon="arrow-down"
|
||||
:custom-style="filterInputStyle"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-actions">
|
||||
<u-button type="primary" size="small" @click="handleFilter" :loading="loading">
|
||||
<u-button
|
||||
type="primary"
|
||||
shape="round"
|
||||
size="small"
|
||||
@click="handleFilter"
|
||||
:loading="loading"
|
||||
>
|
||||
筛选
|
||||
</u-button>
|
||||
<u-button
|
||||
type="default"
|
||||
type="info"
|
||||
shape="round"
|
||||
size="small"
|
||||
@click="resetFilter"
|
||||
style="margin-left: 10rpx"
|
||||
@@ -75,27 +112,6 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 筛选按钮 -->
|
||||
<view class="action-bar">
|
||||
<u-button
|
||||
type="default"
|
||||
size="small"
|
||||
:icon="showFilter ? 'arrow-up' : 'arrow-down'"
|
||||
@click="showFilter = !showFilter"
|
||||
>
|
||||
{{ showFilter ? '收起筛选' : '展开筛选' }}
|
||||
</u-button>
|
||||
<u-button
|
||||
type="primary"
|
||||
size="small"
|
||||
icon="reload"
|
||||
@click="refreshData"
|
||||
:loading="refreshing"
|
||||
>
|
||||
刷新
|
||||
</u-button>
|
||||
</view>
|
||||
|
||||
<!-- 岗位卡片列表 -->
|
||||
<view class="list-content" v-if="positionList.length > 0">
|
||||
<view
|
||||
@@ -273,6 +289,7 @@
|
||||
keyName="label"
|
||||
@confirm="onScopeConfirm"
|
||||
@cancel="showScopePicker = false"
|
||||
@close="showScopePicker = false"
|
||||
/>
|
||||
|
||||
<!-- 状态选择器 -->
|
||||
@@ -282,6 +299,7 @@
|
||||
keyName="label"
|
||||
@confirm="onStatusConfirm"
|
||||
@cancel="showStatusPicker = false"
|
||||
@close="showStatusPicker = false"
|
||||
/>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
@@ -316,6 +334,13 @@ export default {
|
||||
showStatusPicker: false,
|
||||
scopeText: '',
|
||||
statusText: '',
|
||||
// 筛选输入框样式(与搜索框高度一致)
|
||||
filterInputStyle: {
|
||||
backgroundColor: '#f6f6f6',
|
||||
padding: '0 24rpx',
|
||||
height: '32px',
|
||||
boxSizing: 'border-box',
|
||||
},
|
||||
|
||||
// 选择器选项(字典)
|
||||
scopeOptions: [[]],
|
||||
@@ -919,24 +944,48 @@ export default {
|
||||
<style scoped>
|
||||
.work-study-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f6fa;
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
.search-bar {
|
||||
padding: 20rpx;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.search-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.search-input-wrap {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.filter-toggle {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 50%;
|
||||
background: #f6f6f6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.filter-toggle:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.tabs-container {
|
||||
background-color: #fff;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
border-bottom: 1rpx solid #ebeef5;
|
||||
}
|
||||
|
||||
.filter-bar {
|
||||
background-color: #fff;
|
||||
padding: 20rpx;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
background: #ffffff;
|
||||
padding: 20rpx 20rpx 20rpx;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
@@ -946,15 +995,7 @@ export default {
|
||||
.filter-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 20rpx;
|
||||
background-color: #fff;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
gap: 10rpx;
|
||||
}
|
||||
|
||||
.list-container {
|
||||
|
||||
@@ -190,36 +190,39 @@
|
||||
@cancel="showCancelTimePicker = false"
|
||||
></u-datetime-picker>
|
||||
|
||||
<u-form-item label="销假地点" prop="cancelLocation" required>
|
||||
<view class="location-row">
|
||||
<u-button
|
||||
shape="round"
|
||||
type="info"
|
||||
size="mini"
|
||||
plain
|
||||
@click="handleGetLocation"
|
||||
>
|
||||
<u-icon name="map-fill" size="20" color="#2979ff" />
|
||||
<text style="margin-left: 8rpx">
|
||||
{{ isWechatBrowser ? '微信定位' : '定位' }}
|
||||
</text>
|
||||
</u-button>
|
||||
</view>
|
||||
</u-form-item>
|
||||
<u-form-item label="坐标">
|
||||
<view v-if="cancelForm.cancelLocation" class="location-info">
|
||||
<view class="location-coords-container">
|
||||
<text class="location-coords">
|
||||
{{ cancelForm.cancelLocation }}
|
||||
</text>
|
||||
<!-- 生产环境公众号 H5(HTTP+自定义端口)定位不可用,临时隐藏;恢复时将 enableLocationFeature 设为 true -->
|
||||
<template v-if="enableLocationFeature">
|
||||
<u-form-item label="销假地点" prop="cancelLocation" required>
|
||||
<view class="location-row">
|
||||
<u-button
|
||||
shape="round"
|
||||
type="info"
|
||||
size="mini"
|
||||
plain
|
||||
@click="handleGetLocation"
|
||||
>
|
||||
<u-icon name="map-fill" size="20" color="#2979ff" />
|
||||
<text style="margin-left: 8rpx">
|
||||
{{ isWechatBrowser ? '微信定位' : '定位' }}
|
||||
</text>
|
||||
</u-button>
|
||||
</view>
|
||||
<view class="location-action-container">
|
||||
<view class="location-icon-btn" @click="openLocationInMap">
|
||||
<u-icon name="map-fill" size="23" color="#2979ff" />
|
||||
</u-form-item>
|
||||
<u-form-item label="坐标">
|
||||
<view v-if="cancelForm.cancelLocation" class="location-info">
|
||||
<view class="location-coords-container">
|
||||
<text class="location-coords">
|
||||
{{ cancelForm.cancelLocation }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="location-action-container">
|
||||
<view class="location-icon-btn" @click="openLocationInMap">
|
||||
<u-icon name="map-fill" size="23" color="#2979ff" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</u-form-item>
|
||||
</u-form-item>
|
||||
</template>
|
||||
|
||||
<u-form-item label="销假备注" prop="cancelRemark">
|
||||
<u-textarea
|
||||
@@ -555,7 +558,9 @@ export default {
|
||||
isWechatBrowser: false,
|
||||
wechatInitializing: false,
|
||||
|
||||
// 定位
|
||||
// 微信定位相关
|
||||
// 临时关闭定位:公众号 H5 在 HTTP+自定义端口下无法可靠定位;恢复定位时改为 true
|
||||
enableLocationFeature: false,
|
||||
geoLatitude: null,
|
||||
geoLongitude: null,
|
||||
|
||||
@@ -626,6 +631,15 @@ export default {
|
||||
leaveNonImageFiles() {
|
||||
return this.leaveAttachmentList.filter((file) => !this.isImageFile(file));
|
||||
},
|
||||
rules() {
|
||||
const base = {
|
||||
cancelTime: [{ required: true, message: '请选择销假时间' }],
|
||||
};
|
||||
if (this.enableLocationFeature) {
|
||||
base.cancelLocation = [{ required: true, message: '请输入销假地点' }];
|
||||
}
|
||||
return base;
|
||||
},
|
||||
},
|
||||
async onLoad(options) {
|
||||
this.routeParams = options || {};
|
||||
@@ -836,10 +850,6 @@ export default {
|
||||
},
|
||||
|
||||
// ============== 校验与时间处理 ==============
|
||||
rules: {
|
||||
cancelTime: [{ required: true, message: '请选择销假时间' }],
|
||||
cancelLocation: [{ required: true, message: '请输入销假地点' }],
|
||||
},
|
||||
handleCancelTimeClick() {
|
||||
this.showCancelTimePicker = true;
|
||||
},
|
||||
@@ -1449,7 +1459,8 @@ export default {
|
||||
uni.showToast({ title: '销假时间不能为空', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
if (!this.cancelForm.cancelLocation) {
|
||||
// 定位功能临时关闭时跳过,允许无坐标保存
|
||||
if (this.enableLocationFeature && !this.cancelForm.cancelLocation) {
|
||||
uni.showToast({ title: '销假地点不能为空', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
@@ -1498,14 +1509,17 @@ export default {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (needLocationOnCancel) {
|
||||
if (!this.cancelForm.cancelLocation) {
|
||||
uni.showToast({
|
||||
title: '该请假类型要求提供销假定位',
|
||||
icon: 'none',
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 定位功能临时关闭时跳过销假定位校验
|
||||
if (
|
||||
this.enableLocationFeature &&
|
||||
needLocationOnCancel &&
|
||||
!this.cancelForm.cancelLocation
|
||||
) {
|
||||
uni.showToast({
|
||||
title: '该请假类型要求提供销假定位',
|
||||
icon: 'none',
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,14 +100,11 @@
|
||||
</view>
|
||||
<!-- 卡片头部 -->
|
||||
<view class="card-header">
|
||||
<view class="leave-info">
|
||||
<view class="header-top">
|
||||
<view class="student-info">
|
||||
<text class="student-name">{{ item.studentName || '-' }}</text>
|
||||
<text class="student-no">{{ item.studentNo || '-' }}</text>
|
||||
</view>
|
||||
<view class="leave-type">{{ item.leaveTypeName || '-' }}</view>
|
||||
</view>
|
||||
<view class="right-section">
|
||||
<view class="status-section">
|
||||
<u-tag
|
||||
:text="
|
||||
@@ -117,6 +114,9 @@
|
||||
size="mini"
|
||||
></u-tag>
|
||||
</view>
|
||||
</view>
|
||||
<view class="header-bottom">
|
||||
<view class="leave-type">{{ item.leaveTypeName || '-' }}</view>
|
||||
<view class="apply-time">
|
||||
申请时间:{{ formatTime(item.createTime) }}
|
||||
</view>
|
||||
@@ -969,23 +969,33 @@ export default {
|
||||
// 卡片头部
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
padding: 24rpx;
|
||||
padding-left: 80rpx; // 为序号预留左边空间
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
|
||||
}
|
||||
|
||||
.leave-info {
|
||||
flex: 1;
|
||||
.header-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-bottom {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.student-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
margin-bottom: 8rpx;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.student-name {
|
||||
@@ -1006,25 +1016,20 @@ export default {
|
||||
font-size: 28rpx;
|
||||
color: #606266;
|
||||
font-weight: 500;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.right-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 8rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.apply-time {
|
||||
font-size: 24rpx;
|
||||
color: #999999;
|
||||
text-align: right;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.status-section {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
// 卡片内容
|
||||
|
||||
@@ -175,45 +175,48 @@
|
||||
/>
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item label="请假地点">
|
||||
<view class="location-row">
|
||||
<!-- 微信定位按钮单独一行 -->
|
||||
<u-button
|
||||
type="info"
|
||||
shape="round"
|
||||
size="mini"
|
||||
plain
|
||||
@click="handleGetLocation"
|
||||
>
|
||||
<u-icon name="map-fill" size="20" color="#2979ff" />
|
||||
<text style="margin-left: 8rpx">
|
||||
{{ isWechatBrowser ? '微信定位' : '定位' }}
|
||||
</text>
|
||||
</u-button>
|
||||
</view>
|
||||
</u-form-item>
|
||||
<u-form-item label="坐标">
|
||||
<view v-if="form.leaveLocation" class="location-info">
|
||||
<!-- 坐标和查看位置按钮在同一行 -->
|
||||
<view class="location-coords-container">
|
||||
<text class="location-coords">
|
||||
经纬度: {{ form.leaveLocation || '未获取' }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="location-action-container">
|
||||
<!-- 生产环境公众号 H5(HTTP+自定义端口)定位不可用,临时隐藏;恢复时将 enableLocationFeature 设为 true -->
|
||||
<template v-if="enableLocationFeature">
|
||||
<u-form-item label="请假地点">
|
||||
<view class="location-row">
|
||||
<!-- 微信定位按钮单独一行 -->
|
||||
<u-button
|
||||
type="info"
|
||||
shape="round"
|
||||
size="mini"
|
||||
type="info"
|
||||
plain
|
||||
@click="openLocationInMap"
|
||||
@click="handleGetLocation"
|
||||
>
|
||||
<!-- <u-icon name="eye" size="16" color="#0051d9" /> -->
|
||||
<text>查看位置</text>
|
||||
<u-icon name="map-fill" size="20" color="#2979ff" />
|
||||
<text style="margin-left: 8rpx">
|
||||
{{ isWechatBrowser ? '微信定位' : '定位' }}
|
||||
</text>
|
||||
</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</u-form-item>
|
||||
</u-form-item>
|
||||
<u-form-item label="坐标">
|
||||
<view v-if="form.leaveLocation" class="location-info">
|
||||
<!-- 坐标和查看位置按钮在同一行 -->
|
||||
<view class="location-coords-container">
|
||||
<text class="location-coords">
|
||||
经纬度: {{ form.leaveLocation || '未获取' }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="location-action-container">
|
||||
<u-button
|
||||
shape="round"
|
||||
size="mini"
|
||||
type="info"
|
||||
plain
|
||||
@click="openLocationInMap"
|
||||
>
|
||||
<!-- <u-icon name="eye" size="16" color="#0051d9" /> -->
|
||||
<text>查看位置</text>
|
||||
</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</u-form-item>
|
||||
</template>
|
||||
</view>
|
||||
|
||||
<!-- 附件上传 -->
|
||||
@@ -624,9 +627,12 @@ export default {
|
||||
taskVariables: {},
|
||||
startTimeValue: new Date().getTime(), // 开始时间选择器的值(默认当前时间)
|
||||
endTimeValue: new Date().getTime(), // 结束时间选择器的值(默认当前时间)
|
||||
minDate: '', // 时间选择器的最小日期
|
||||
maxDate: '', // 时间选择器的最大日期
|
||||
// u-datetime-picker 的 minDate/maxDate 必须为 Number 时间戳,空字符串会导致 Invalid array length
|
||||
minDate: new Date(new Date().getFullYear() - 1, 0, 1).getTime(),
|
||||
maxDate: new Date(new Date().getFullYear() + 1, 11, 31).getTime(),
|
||||
// 微信定位相关
|
||||
// 临时关闭定位:公众号 H5 在 HTTP+自定义端口下无法可靠定位;恢复定位时改为 true
|
||||
enableLocationFeature: false,
|
||||
isWxInited: false,
|
||||
wxInitPromise: null,
|
||||
geoLatitude: null,
|
||||
@@ -3132,12 +3138,14 @@ export default {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 校验是否需要上传地理位置
|
||||
if (leaveType.needLocationOnApply === '1') {
|
||||
if (!this.form.leaveLocation) {
|
||||
uni.showToast({ title: '该请假类型需要上传地理位置', icon: 'none' });
|
||||
return false;
|
||||
}
|
||||
// 2. 校验是否需要上传地理位置(定位功能临时关闭时跳过,允许无坐标保存)
|
||||
if (
|
||||
this.enableLocationFeature &&
|
||||
leaveType.needLocationOnApply === '1' &&
|
||||
!this.form.leaveLocation
|
||||
) {
|
||||
uni.showToast({ title: '该请假类型需要上传地理位置', icon: 'none' });
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 校验和设置销假状态
|
||||
@@ -3180,8 +3188,8 @@ export default {
|
||||
// 启动工作流
|
||||
async startWorkflow(data) {
|
||||
try {
|
||||
// 获取班主任ID和学院审批人
|
||||
const [classTeacherResponse, collegeApproversResponse] = await Promise.all([
|
||||
// 获取班主任ID、学院审批人、二级学院分管领导
|
||||
const [classTeacherResponse, collegeApproversResponse, collegeLeadersResponse] = await Promise.all([
|
||||
request({
|
||||
url: `/studentMsg/studentMsg/getClassTeacherId/${data.studentId}`,
|
||||
method: 'GET',
|
||||
@@ -3190,10 +3198,28 @@ export default {
|
||||
url: `/studentMsg/studentMsg/getCollegeLeaveApprovers/${data.studentId}`,
|
||||
method: 'GET',
|
||||
}),
|
||||
request({
|
||||
url: `/studentMsg/studentMsg/getCollegeLeaders/${data.studentId}`,
|
||||
method: 'GET',
|
||||
}),
|
||||
]);
|
||||
|
||||
const classTeacherId = classTeacherResponse.data;
|
||||
const collegeApprovers = collegeApproversResponse.data || [];
|
||||
const collegeLeaders = collegeLeadersResponse.data || [];
|
||||
|
||||
if (!classTeacherId) {
|
||||
uni.showToast({ title: '获取班主任信息失败', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
if (!collegeApprovers.length) {
|
||||
uni.showToast({ title: '获取学院审批人信息失败', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
if (!collegeLeaders.length) {
|
||||
uni.showToast({ title: '获取二级学院分管领导信息失败', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
this.taskVariables = {
|
||||
qjts: data.leaveDuration,
|
||||
@@ -3202,6 +3228,7 @@ export default {
|
||||
// sfxy: data.needCancel === '1',
|
||||
bzrspr: classTeacherId,
|
||||
ejxyspr: collegeApprovers,
|
||||
ejxyfgld: collegeLeaders,
|
||||
};
|
||||
|
||||
// 启动工作流
|
||||
|
||||
+335
-107
@@ -6,60 +6,113 @@
|
||||
</view>
|
||||
</u-navbar>
|
||||
|
||||
<u-loading-page :loading="pageLoading" loading-text="加载中..."></u-loading-page>
|
||||
|
||||
<view v-if="!pageLoading" class="content-container">
|
||||
<!-- 顶部操作栏 -->
|
||||
<view class="action-bar">
|
||||
<u-button type="primary" icon="plus" @click="handleAdd" style="flex: 1">点名</u-button>
|
||||
</view>
|
||||
|
||||
<!-- 搜索栏 -->
|
||||
<view class="filter-card">
|
||||
<u-form label-width="80">
|
||||
<u-form-item label="批次号">
|
||||
<u-input v-model="queryParams.batchNo" placeholder="请输入批次号" clearable border="none" @change="handleQuery" />
|
||||
</u-form-item>
|
||||
<u-form-item label="宿舍楼">
|
||||
<view @click="showBuildingPicker = true" style="width: 100%">
|
||||
<u-input :value="selectedBuildingName" placeholder="请选择宿舍楼" readonly border="none" suffix-icon="arrow-down" suffix-icon-style="color: #c8c9cc;" style="pointer-events: none" />
|
||||
</view>
|
||||
<u-picker :show="showBuildingPicker" :columns="buildingColumns" keyName="text" @confirm="handleBuildingConfirm" @cancel="showBuildingPicker = false"></u-picker>
|
||||
</u-form-item>
|
||||
</u-form>
|
||||
<view style="display: flex; gap: 12px; margin-top: 12px">
|
||||
<u-button type="primary" size="small" @click="handleQuery" style="flex: 1">搜索</u-button>
|
||||
<u-button size="small" @click="resetQuery" style="flex: 1">重置</u-button>
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-bar">
|
||||
<view class="search-row">
|
||||
<view class="search-input-wrap">
|
||||
<u-search
|
||||
v-model="queryParams.batchNo"
|
||||
placeholder="搜索批次号"
|
||||
:show-action="false"
|
||||
@search="handleQuery"
|
||||
@custom="handleQuery"
|
||||
bg-color="#f6f6f6"
|
||||
shape="round"
|
||||
/>
|
||||
</view>
|
||||
<view class="filter-toggle" @click="showFilter = !showFilter">
|
||||
<u-icon
|
||||
:name="showFilter ? 'arrow-up' : 'list'"
|
||||
size="16"
|
||||
color="#606266"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 筛选条件 -->
|
||||
<view class="filter-bar" v-if="showFilter">
|
||||
<view class="filter-item">
|
||||
<view @click="showBuildingPicker = true">
|
||||
<u-input
|
||||
:value="selectedBuildingName"
|
||||
placeholder="请选择宿舍楼"
|
||||
placeholder-style="color: #c0c4cc; font-size: 14px"
|
||||
font-size="14px"
|
||||
shape="circle"
|
||||
readonly
|
||||
border="none"
|
||||
suffix-icon="arrow-down"
|
||||
:custom-style="filterInputStyle"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-actions">
|
||||
<u-button type="primary" shape="round" size="small" @click="handleQuery" :loading="pageLoading">
|
||||
筛选
|
||||
</u-button>
|
||||
<u-button type="info" shape="round" size="small" @click="resetQuery" style="margin-left: 10rpx">
|
||||
重置
|
||||
</u-button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<u-loading-page :loading="pageLoading && list.length === 0" loading-text="加载中..."></u-loading-page>
|
||||
|
||||
<view v-if="!pageLoading || list.length > 0" class="content-container">
|
||||
<!-- 列表 -->
|
||||
<view v-if="list.length > 0" class="list-section">
|
||||
<view v-for="item in list" :key="item.id" class="check-card" @click="handleView(item)">
|
||||
<!-- 卡片标题:房间名称 -->
|
||||
<view class="card-header">
|
||||
<text class="card-title">{{ item.buildingName || '-' }} - {{ item.roomName || '-' }}</text>
|
||||
<view class="room-title">
|
||||
<view class="room-icon">
|
||||
<u-icon name="home" size="18" color="#0051d9" />
|
||||
</view>
|
||||
<view class="room-title-text">
|
||||
<text class="room-name">{{ item.roomName || '未命名房间' }}</text>
|
||||
<text class="building-name">{{ item.buildingName || '-' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<u-icon name="arrow-right" size="14" color="#c0c4cc" />
|
||||
</view>
|
||||
|
||||
<!-- 点名统计 -->
|
||||
<view class="card-stats">
|
||||
<view class="stat-badge stat-present">
|
||||
<text>在寝 {{ item.presentCount || 0 }}</text>
|
||||
<view class="stat-item stat-present">
|
||||
<text class="stat-num">{{ item.presentCount || 0 }}</text>
|
||||
<text class="stat-label">在寝</text>
|
||||
</view>
|
||||
<view class="stat-badge stat-leave">
|
||||
<text>请假 {{ item.leaveCount || 0 }}</text>
|
||||
<view class="stat-item stat-leave">
|
||||
<text class="stat-num">{{ item.leaveCount || 0 }}</text>
|
||||
<text class="stat-label">请假</text>
|
||||
</view>
|
||||
<view class="stat-badge stat-absent">
|
||||
<text>不在 {{ item.absentCount || 0 }}</text>
|
||||
<view class="stat-item stat-absent">
|
||||
<text class="stat-num">{{ item.absentCount || 0 }}</text>
|
||||
<text class="stat-label">不在</text>
|
||||
</view>
|
||||
<view class="stat-badge stat-empty">
|
||||
<text>空 {{ item.emptyCount || 0 }}</text>
|
||||
<view class="stat-item stat-empty">
|
||||
<text class="stat-num">{{ item.emptyCount || 0 }}</text>
|
||||
<text class="stat-label">空床</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="card-info">
|
||||
<text class="info-text">批次号: {{ item.batchNo }}</text>
|
||||
<text class="info-text">点名时间: {{ item.checkDate }}</text>
|
||||
|
||||
<!-- 卡片内容 -->
|
||||
<view class="card-body">
|
||||
<view class="info-row">
|
||||
<u-icon name="file-text" size="16" color="#909399" />
|
||||
<text class="info-label">批次</text>
|
||||
<text class="info-value info-value--ellipsis">{{ item.batchNo || '-' }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<u-icon name="clock" size="16" color="#909399" />
|
||||
<text class="info-label">时间</text>
|
||||
<text class="info-value">{{ item.checkDate || '-' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else style="padding: 60rpx 0">
|
||||
<view v-else-if="!pageLoading" class="empty-container">
|
||||
<u-empty text="暂无点名记录" mode="data"></u-empty>
|
||||
</view>
|
||||
|
||||
@@ -70,6 +123,21 @@
|
||||
<u-button size="small" :disabled="queryParams.pageNum >= totalPages" @click="handleNextPage">下一页</u-button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 宿舍楼选择器 -->
|
||||
<u-picker
|
||||
:show="showBuildingPicker"
|
||||
:columns="buildingColumns"
|
||||
keyName="text"
|
||||
@confirm="handleBuildingConfirm"
|
||||
@cancel="showBuildingPicker = false"
|
||||
@close="showBuildingPicker = false"
|
||||
/>
|
||||
|
||||
<!-- 悬浮点名按钮 -->
|
||||
<view class="fab-button" @click="handleAdd">
|
||||
<u-icon name="plus" size="24" color="#ffffff" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -82,6 +150,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
pageLoading: false,
|
||||
showFilter: false,
|
||||
list: [],
|
||||
total: 0,
|
||||
queryParams: {
|
||||
@@ -93,6 +162,13 @@ export default {
|
||||
buildingOptions: [],
|
||||
selectedBuildingName: '',
|
||||
showBuildingPicker: false,
|
||||
// 筛选输入框样式(与搜索框高度一致)
|
||||
filterInputStyle: {
|
||||
backgroundColor: '#f6f6f6',
|
||||
padding: '0 24rpx',
|
||||
height: '32px',
|
||||
boxSizing: 'border-box',
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -145,15 +221,13 @@ export default {
|
||||
},
|
||||
|
||||
handleBuildingConfirm(e) {
|
||||
if (e && e.value && e.value.length > 0) {
|
||||
const building = this.buildingOptions.find((item) => item.id === e.value[0].value);
|
||||
if (building) {
|
||||
this.queryParams.buildingId = building.id;
|
||||
this.selectedBuildingName = building.buildingName;
|
||||
}
|
||||
const index = e.indexs && e.indexs[0];
|
||||
const selected = this.buildingColumns[0] && this.buildingColumns[0][index];
|
||||
if (selected) {
|
||||
this.queryParams.buildingId = selected.value;
|
||||
this.selectedBuildingName = selected.text;
|
||||
}
|
||||
this.showBuildingPicker = false;
|
||||
this.handleQuery();
|
||||
},
|
||||
|
||||
handleAdd() {
|
||||
@@ -188,92 +262,246 @@ export default {
|
||||
<style lang="scss" scoped>
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f6f7;
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
padding: 20rpx;
|
||||
background: #ffffff;
|
||||
|
||||
.search-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.search-input-wrap {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.filter-toggle {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 50%;
|
||||
background: #f6f6f6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.filter-bar {
|
||||
background: #ffffff;
|
||||
padding: 0 20rpx 20rpx;
|
||||
|
||||
.filter-item {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.filter-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.content-container {
|
||||
padding: 20rpx;
|
||||
padding-top: 100rpx;
|
||||
padding-bottom: 160rpx;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
margin-bottom: 20rpx;
|
||||
.fab-button {
|
||||
position: fixed;
|
||||
right: 20px;
|
||||
bottom: 30px;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #007aff, #0051d9);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4px 16px rgba(0, 122, 255, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
z-index: 999;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
box-shadow: 0 2px 8px rgba(0, 122, 255, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
.filter-card {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
.empty-container {
|
||||
padding: 80rpx 20rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.list-section {
|
||||
margin-top: 12rpx;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.check-card {
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 16rpx;
|
||||
border: 2rpx solid #e4e7ed;
|
||||
}
|
||||
background: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
padding: 0;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 81, 217, 0.08);
|
||||
overflow: hidden;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
&:active {
|
||||
transform: scale(0.985);
|
||||
box-shadow: 0 2rpx 10rpx rgba(0, 81, 217, 0.06);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24rpx 24rpx 20rpx;
|
||||
background: linear-gradient(135deg, #f3f7ff 0%, #ffffff 100%);
|
||||
border-bottom: 1rpx solid #f0f3f8;
|
||||
|
||||
.card-stats {
|
||||
display: flex;
|
||||
gap: 12rpx;
|
||||
margin-bottom: 16rpx;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.room-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
margin-right: 16rpx;
|
||||
|
||||
.stat-badge {
|
||||
padding: 4rpx 16rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 22rpx;
|
||||
}
|
||||
.room-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
|
||||
.stat-present {
|
||||
background: #e8f5e9;
|
||||
color: #19be6b;
|
||||
}
|
||||
.room-title-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
|
||||
.stat-leave {
|
||||
background: #fff8e1;
|
||||
color: #e6a23c;
|
||||
}
|
||||
.room-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.stat-absent {
|
||||
background: #ffebee;
|
||||
color: #f56c6c;
|
||||
}
|
||||
.building-name {
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stat-empty {
|
||||
background: #f5f5f5;
|
||||
color: #909399;
|
||||
}
|
||||
.card-stats {
|
||||
display: flex;
|
||||
padding: 20rpx 16rpx 8rpx;
|
||||
gap: 12rpx;
|
||||
|
||||
.card-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4rpx;
|
||||
}
|
||||
.stat-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 16rpx 8rpx;
|
||||
border-radius: 12rpx;
|
||||
|
||||
.info-text {
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
.stat-num {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 22rpx;
|
||||
margin-top: 6rpx;
|
||||
}
|
||||
|
||||
&.stat-present {
|
||||
background: #e8f5e9;
|
||||
|
||||
.stat-num,
|
||||
.stat-label {
|
||||
color: #19be6b;
|
||||
}
|
||||
}
|
||||
|
||||
&.stat-leave {
|
||||
background: #fff8e1;
|
||||
|
||||
.stat-num,
|
||||
.stat-label {
|
||||
color: #e6a23c;
|
||||
}
|
||||
}
|
||||
|
||||
&.stat-absent {
|
||||
background: #ffebee;
|
||||
|
||||
.stat-num,
|
||||
.stat-label {
|
||||
color: #f56c6c;
|
||||
}
|
||||
}
|
||||
|
||||
&.stat-empty {
|
||||
background: #f5f5f5;
|
||||
|
||||
.stat-num,
|
||||
.stat-label {
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 12rpx 24rpx 20rpx;
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
width: 64rpx;
|
||||
flex-shrink: 0;
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-size: 26rpx;
|
||||
color: #606266;
|
||||
|
||||
&--ellipsis {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-bar {
|
||||
@@ -288,4 +516,4 @@ export default {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
Reference in new issue
Block a user