petstore-frontend/pages/mine/staff.vue
2026-04-12 22:57:48 +08:00

248 lines
7.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="staff-page">
<!-- 导航栏 -->
<view class="nav-bar">
<text class="nav-back" @click="goBack"></text>
<text class="nav-title">员工管理</text>
<view style="width:40px;"></view>
</view>
<view class="invite-code-row">
<text class="invite-label">员工邀请码</text>
<text class="invite-code">{{ storeInfo.inviteCode }}</text>
<view class="copy-btn" @click="copyCode">复制</view>
</view>
<view class="add-btn-wrap">
<view class="btn-add" @click="showAddStaff = true">+ 新增员工</view>
</view>
<!-- 员工列表 -->
<view class="staff-list">
<view v-for="s in staffList" :key="s.id" class="staff-item">
<view class="staff-avatar" :style="avatarStyle(s)">
<image v-if="s.avatar" :src="s.avatar" class="avatar-img" />
<text v-else class="avatar-initials">{{ s.name ? s.name[0] : '?' }}</text>
</view>
<view class="staff-info">
<text class="staff-name">{{ s.name }}</text>
<text class="staff-phone">{{ s.phone }}</text>
<text class="staff-role">{{ s.role === 'boss' ? '🏠 店长' : '👤 员工' }}</text>
</view>
<text v-if="s.role !== 'boss'" class="delete-btn" @click="deleteStaff(s.id)">删除</text>
</view>
<view v-if="staffList.length === 0" class="empty-wrap">
<text class="empty-icon">👥</text>
<text class="empty-text">暂无员工</text>
</view>
</view>
<!-- 新增员工弹窗 -->
<view v-if="showAddStaff" class="dialog-mask" @click="showAddStaff = false">
<view class="dialog-content" @click.stop>
<view class="dialog-header">
<text class="dialog-title">新增员工</text>
<text class="dialog-close" @click="showAddStaff = false">✕</text>
</view>
<view class="dialog-body">
<view class="form-item">
<text class="form-label">员工姓名</text>
<input class="form-input" v-model="newStaff.name" placeholder="请输入" />
</view>
<view class="form-item">
<text class="form-label">手机号</text>
<input class="form-input" v-model="newStaff.phone" type="tel" placeholder="请输入" maxlength="11" />
</view>
</view>
<view class="dialog-footer">
<view class="btn-cancel" @click="showAddStaff = false">取消</view>
<view class="btn-confirm" @click="onAddStaffConfirm">确定</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { getStaffList, createStaff, deleteStaff as delStaff } from '../../../utils/api.js'
const storeInfo = JSON.parse(uni.getStorageSync('petstore_store') || '{}')
const staffList = ref([])
const showAddStaff = ref(false)
const newStaff = ref({ name: '', phone: '' })
const COLORS = ['#ff7c43', '#07c160', '#8b6914', '#e06040', '#5090d0', '#9b59b6']
const avatarStyle = (s) => {
if (s.avatar) return {}
const idx = (s.name?.charCodeAt(0) || 0) % COLORS.length
return { background: COLORS[idx] }
}
const goBack = () => uni.navigateBack()
const loadStaff = async () => {
const res = await getStaffList(storeInfo.id)
if (res.code === 200) staffList.value = res.data
}
const copyCode = () => {
uni.setClipboardData({ data: storeInfo.inviteCode, success: () => uni.showToast({ title: '邀请码已复制', icon: 'success' }) })
}
const onAddStaffConfirm = async () => {
if (!newStaff.value.name) { uni.showToast({ title: '请输入员工姓名', icon: 'none' }); return }
if (!newStaff.value.phone || newStaff.value.phone.length !== 11) { uni.showToast({ title: '请输入正确的手机号', icon: 'none' }); return }
const res = await createStaff({ storeId: storeInfo.id, name: newStaff.value.name, phone: newStaff.value.phone })
if (res.code === 200) {
uni.showToast({ title: `添加成功,密码:${res.data.password}`, icon: 'none', duration: 3000 })
showAddStaff.value = false
newStaff.value = { name: '', phone: '' }
loadStaff()
} else {
uni.showToast({ title: res.message || '添加失败', icon: 'none' })
}
}
const deleteStaff = async (staffId) => {
uni.showModal({
title: '确认删除',
content: '确定删除该员工?',
success: async (res) => {
if (res.confirm) {
const r = await delStaff(staffId)
if (r.code === 200) {
uni.showToast({ title: '已删除', icon: 'success' })
loadStaff()
} else {
uni.showToast({ title: r.message || '删除失败', icon: 'none' })
}
}
}
})
}
onMounted(() => loadStaff())
</script>
<style scoped>
.staff-page { min-height: 100vh; background: #f5f5f5; padding-bottom: 20px; }
.nav-bar {
background: #fff;
padding: 40px 16px 12px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #f0f0f0;
}
.nav-back { font-size: 28px; color: #333; font-weight: 300; }
.nav-title { font-size: 16px; font-weight: 600; color: #333; }
.invite-code-row {
display: flex;
align-items: center;
background: #fff;
margin: 12px 16px;
padding: 14px 16px;
border-radius: 12px;
gap: 8px;
}
.invite-label { font-size: 14px; color: #666; }
.invite-code { flex: 1; font-size: 15px; color: #07c160; font-weight: 600; font-family: monospace; }
.copy-btn { font-size: 13px; color: #07c160; padding: 4px 12px; border: 1px solid #07c160; border-radius: 4px; }
.add-btn-wrap { padding: 0 16px 12px; }
.btn-add {
width: 100%;
height: 44px;
background: #07c160;
color: #fff;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 15px;
}
.staff-list { padding: 0 16px; }
.staff-item {
display: flex;
align-items: center;
background: #fff;
border-radius: 12px;
padding: 14px 16px;
margin-bottom: 10px;
gap: 12px;
}
.staff-avatar {
width: 44px;
height: 44px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
color: #fff;
overflow: hidden;
flex-shrink: 0;
}
.avatar-img { width: 100%; height: 100%; object-fit: cover; }
.avatar-initials { font-weight: 600; }
.staff-info { flex: 1; display: flex; flex-direction: column; gap: 2px; }
.staff-name { font-size: 15px; font-weight: 500; color: #333; }
.staff-phone { font-size: 12px; color: #999; }
.staff-role { font-size: 11px; color: #999; }
.delete-btn { font-size: 13px; color: #ff4d4f; }
.empty-wrap { display: flex; flex-direction: column; align-items: center; padding: 60px 0; }
.empty-icon { font-size: 48px; }
.empty-text { font-size: 14px; color: #999; margin-top: 12px; }
/* 弹窗 */
.dialog-mask {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.5);
z-index: 999;
display: flex;
align-items: center;
justify-content: center;
}
.dialog-content {
width: 80%;
max-width: 340px;
background: #fff;
border-radius: 12px;
overflow: hidden;
}
.dialog-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
border-bottom: 1px solid #eee;
}
.dialog-title { font-size: 16px; font-weight: 600; color: #333; }
.dialog-close { font-size: 18px; color: #999; }
.dialog-body { padding: 16px 20px; }
.dialog-footer { display: flex; border-top: 1px solid #eee; }
.btn-cancel, .btn-confirm { flex: 1; text-align: center; padding: 14px 0; font-size: 15px; }
.btn-cancel { color: #666; border-right: 1px solid #eee; }
.btn-confirm { color: #07c160; font-weight: 600; }
.form-item { margin-bottom: 16px; }
.form-item:last-child { margin-bottom: 0; }
.form-label { font-size: 13px; color: #999; display: block; margin-bottom: 6px; }
.form-input {
width: 100%;
height: 40px;
border: 1px solid #eee;
border-radius: 8px;
padding: 0 12px;
font-size: 14px;
box-sizing: border-box;
}
</style>