feat(销售页面): 添加模块显示控制功能及周期分析组件

refactor(StatisticData): 简化指标名称显示
style(UserDropdown): 添加显示设置弹窗样式
This commit is contained in:
2025-09-02 11:18:05 +08:00
parent 328ae8cd55
commit e94ea6b592
4 changed files with 795 additions and 43 deletions

View File

@@ -25,6 +25,13 @@
</svg>
修改密码
</div>
<div class="dropdown-item" @click="handleDisplaySettings">
<svg width="16" height="16" viewBox="0 0 16 16" style="margin-right: 8px;">
<path d="M8 4.754a3.246 3.246 0 1 0 0 6.492 3.246 3.246 0 0 0 0-6.492zM5.754 8a2.246 2.246 0 1 1 4.492 0 2.246 2.246 0 0 1-4.492 0z" fill="currentColor"/>
<path d="M9.796 1.343c-.527-1.79-3.065-1.79-3.592 0l-.094.319a.873.873 0 0 1-1.255.52l-.292-.16c-1.64-.892-3.433.902-2.54 2.541l.159.292a.873.873 0 0 1-.52 1.255l-.319.094c-1.79.527-1.79 3.065 0 3.592l.319.094a.873.873 0 0 1 .52 1.255l-.16.292c-.892 1.64.901 3.434 2.541 2.54l.292-.159a.873.873 0 0 1 1.255.52l.094.319c.527 1.79 3.065 1.79 3.592 0l.094-.319a.873.873 0 0 1 1.255-.52l.292.16c1.64.893 3.434-.902 2.54-2.541l-.159-.292a.873.873 0 0 1 .52-1.255l.319-.094c1.79-.527 1.79-3.065 0-3.592l-.319-.094a.873.873 0 0 1-.52-1.255l.16-.292c.893-1.64-.902-3.433-2.541-2.54l-.292.159a.873.873 0 0 1-1.255-.52l-.094-.319zm-2.633.283c.246-.835 1.428-.835 1.674 0l.094.319a1.873 1.873 0 0 0 2.693 1.115l.291-.16c.764-.415 1.6.42 1.184 1.185l-.159.292a1.873 1.873 0 0 0 1.116 2.692l.318.094c.835.246.835 1.428 0 1.674l-.319.094a1.873 1.873 0 0 0-1.115 2.693l.16.291c.415.764-.42 1.6-1.185 1.184l-.291-.159a1.873 1.873 0 0 0-2.693 1.116l-.094.318c-.246.835-1.428.835-1.674 0l-.094-.319a1.873 1.873 0 0 0-2.692-1.115l-.292.16c-.764.415-1.6-.42-1.184-1.185l.159-.291A1.873 1.873 0 0 0 1.945 8.93l-.319-.094c-.835-.246-.835-1.428 0-1.674l.319-.094A1.873 1.873 0 0 0 3.06 4.377l-.16-.292c-.415-.764.42-1.6 1.185-1.184l.292.159a1.873 1.873 0 0 0 2.692-1.115l.094-.319z" fill="currentColor"/>
</svg>
显示设置
</div>
<div class="dropdown-item logout-item" @click="handleLogout">
<svg width="16" height="16" viewBox="0 0 16 16" style="margin-right: 8px;">
<path d="M6 2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H6zM5 3a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V3z" fill="currentColor"/>
@@ -148,6 +155,49 @@
</div>
</div>
<!-- 显示设置弹窗 -->
<div v-if="showDisplayModal" class="display-modal-overlay" @click="cancelDisplaySettings">
<div class="display-modal" @click.stop>
<div class="display-modal-header">
<h2>显示设置</h2>
<p>选择要显示的模块</p>
</div>
<div class="display-modal-body">
<div class="checkbox-group">
<label v-for="(visible, key) in localCardVisibility" :key="key" class="checkbox-item">
<input
type="checkbox"
v-model="localCardVisibility[key]"
:disabled="displayLoading"
/>
<span class="checkbox-label">{{ getCardDisplayName(key) }}</span>
</label>
</div>
</div>
<div class="display-modal-footer">
<button
type="button"
class="btn-cancel"
@click="cancelDisplaySettings"
:disabled="displayLoading"
>
取消
</button>
<button
type="button"
class="btn-confirm"
@click="handleDisplaySubmit"
:disabled="displayLoading"
>
<span v-if="displayLoading" class="loading-spinner"></span>
{{ displayLoading ? '应用中...' : '确认应用' }}
</button>
</div>
</div>
</div>
<!-- 退出登录确认弹窗 -->
<div v-if="showLogoutModal" class="logout-modal-overlay" @click="cancelLogout">
<div class="logout-modal" @click.stop>
@@ -166,11 +216,28 @@
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { ref, reactive, onMounted, onUnmounted, watch } from 'vue'
import { useRouter } from 'vue-router'
import { useUserStore } from '@/stores/user'
import http from '@/utils/https'
// Props
const props = defineProps({
cardVisibility: {
type: Object,
default: () => ({
timeline: true,
rawData: true,
customerDetail: true,
analytics: true,
weekAnalysis: true
})
}
})
// Emits
const emit = defineEmits(['update-card-visibility'])
// 路由实例
const router = useRouter()
@@ -192,6 +259,26 @@ const passwordForm = ref({
newPassword: ''
}) // 修改密码表单数据
const passwordLoading = ref(false) // 修改密码加载状态
const showDisplayModal = ref(false) // 显示设置弹窗显示状态
const displayLoading = ref(false) // 显示设置加载状态
const localCardVisibility = reactive({}) // 本地卡片显示状态
// 监听props变化同步到本地状态
watch(() => props.cardVisibility, (newVal) => {
Object.assign(localCardVisibility, newVal)
}, { immediate: true, deep: true })
// 获取卡片显示名称
const getCardDisplayName = (key) => {
const nameMap = {
timeline: '销售时间线',
rawData: '原始数据',
customerDetail: '客户详情',
analytics: '数据分析',
weekAnalysis: '周期分析'
}
return nameMap[key] || key
}
// 切换下拉菜单显示状态
const toggleDropdown = () => {
@@ -309,6 +396,39 @@ const cancelPasswordChange = () => {
passwordForm.value.newPassword = ''
}
// 显示设置
const handleDisplaySettings = () => {
console.log('显示设置')
showDropdown.value = false
showDisplayModal.value = true
}
// 显示设置处理函数
const handleDisplaySubmit = async () => {
displayLoading.value = true
try {
// 发送事件给父组件更新卡片显示状态
emit('update-card-visibility', { ...localCardVisibility })
// 模拟异步操作
await new Promise(resolve => setTimeout(resolve, 300))
showDisplayModal.value = false
} catch (error) {
console.error('显示设置失败:', error)
} finally {
displayLoading.value = false
}
}
// 取消显示设置
const cancelDisplaySettings = () => {
showDisplayModal.value = false
// 恢复到原始状态
Object.assign(localCardVisibility, props.cardVisibility)
}
// 退出登录
const handleLogout = () => {
showDropdown.value = false
@@ -655,6 +775,142 @@ const cancelLogout = () => {
}
}
/* 显示设置弹窗样式 */
.display-modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
backdrop-filter: blur(4px);
}
.display-modal {
background: white;
border-radius: 12px;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
min-width: 400px;
max-width: 500px;
animation: modalSlideIn 0.3s ease-out;
}
.display-modal-header {
padding: 24px 24px 16px;
border-bottom: 1px solid #f1f5f9;
}
.display-modal-header h2 {
font-size: 20px;
font-weight: 600;
color: #1e293b;
margin: 0 0 8px 0;
}
.display-modal-header p {
font-size: 14px;
color: #64748b;
margin: 0;
}
.display-modal-body {
padding: 24px;
}
.checkbox-group {
display: flex;
flex-direction: column;
gap: 16px;
}
.checkbox-item {
display: flex;
align-items: center;
cursor: pointer;
padding: 8px;
border-radius: 6px;
transition: background-color 0.2s;
}
.checkbox-item:hover {
background-color: #f8fafc;
}
.checkbox-item input[type="checkbox"] {
width: 18px;
height: 18px;
margin-right: 12px;
cursor: pointer;
accent-color: #667eea;
}
.checkbox-item input[type="checkbox"]:disabled {
cursor: not-allowed;
opacity: 0.6;
}
.checkbox-label {
font-size: 14px;
color: #374151;
font-weight: 500;
cursor: pointer;
}
.display-modal-footer {
padding: 16px 24px 24px 24px;
display: flex;
gap: 12px;
justify-content: flex-end;
}
.display-modal-footer .btn-cancel,
.display-modal-footer .btn-confirm {
padding: 12px 24px;
border-radius: 8px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
border: none;
min-width: 80px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.display-modal-footer .btn-cancel {
background: #f8fafc;
color: #64748b;
border: 1px solid #e2e8f0;
}
.display-modal-footer .btn-cancel:hover:not(:disabled) {
background: #f1f5f9;
transform: translateY(-1px);
}
.display-modal-footer .btn-confirm {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.display-modal-footer .btn-confirm:hover:not(:disabled) {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
}
.display-modal-footer .btn-cancel:disabled,
.display-modal-footer .btn-confirm:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
/* 修改密码弹窗样式 */
.password-modal-overlay {
position: fixed;