feat: complete huihui square avatar workflows
This commit is contained in:
492
digital-avatar-app/src/views/AvatarCreate.vue
Normal file
492
digital-avatar-app/src/views/AvatarCreate.vue
Normal file
@@ -0,0 +1,492 @@
|
||||
<template>
|
||||
<div class="create-page">
|
||||
<!-- 步骤 0:引导落地页 -->
|
||||
<div v-if="step === 0" class="landing">
|
||||
<header class="hero">
|
||||
<div class="hero-icon">🤖</div>
|
||||
<h1 class="hero-title">创建你的数字分身</h1>
|
||||
<p class="hero-sub">AI 助手,为你工作、社交、创造价值</p>
|
||||
</header>
|
||||
|
||||
<section class="features">
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">💬</div>
|
||||
<h3>智能对话</h3>
|
||||
<p>24/7 在线,自动回复消息、处理咨询</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">📅</div>
|
||||
<h3>日程管理</h3>
|
||||
<p>智能安排会议、提醒重要事项</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">🤝</div>
|
||||
<h3>社交助手</h3>
|
||||
<p>管理人脉、扩展社交圈</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="cta">
|
||||
<button class="create-btn" @click="step = 1">
|
||||
<span class="btn-icon">✨</span> 开始创建
|
||||
</button>
|
||||
<p class="hint">只需 4 步,快速拥有你的 AI 分身</p>
|
||||
</section>
|
||||
|
||||
<section class="existing" v-if="avatarStore.avatars.length > 0">
|
||||
<p>已有数字分身?</p>
|
||||
<button class="link-btn" @click="goManage">前往管理 →</button>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- 步骤 1-4:创建向导 -->
|
||||
<div v-else class="wizard">
|
||||
<header class="wizard-header">
|
||||
<button class="back" @click="prev">‹</button>
|
||||
<h2 class="w-title">{{ stepTitles[step - 1] }}</h2>
|
||||
<span class="step-count">{{ step }}/4</span>
|
||||
</header>
|
||||
<div class="progress">
|
||||
<div class="progress-bar" :style="{ width: (step / 4 * 100) + '%' }"></div>
|
||||
</div>
|
||||
|
||||
<div class="wizard-body">
|
||||
<!-- Step 1:基础信息 -->
|
||||
<div v-if="step === 1" class="form">
|
||||
<div class="field">
|
||||
<label class="field-label">分身头像</label>
|
||||
<div class="avatar-pick">
|
||||
<div class="avatar-pick-preview">
|
||||
<img v-if="form.photoUrl" :src="form.photoUrl" alt="" referrerpolicy="no-referrer" />
|
||||
<span v-else>{{ form.emoji }}</span>
|
||||
</div>
|
||||
<p class="avatar-pick-hint">已沿用你的会会头像,也可在下一步选择表情形象</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field-label">分身名称 <span class="req">*</span></label>
|
||||
<input v-model="form.name" class="input" placeholder="如:我的数字分身" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field-label">显示名称</label>
|
||||
<input v-model="form.displayName" class="input" placeholder="如:会会助手" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field-label">分身描述</label>
|
||||
<textarea v-model="form.description" class="textarea" rows="3" placeholder="描述它的功能与特点"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Step 2:形象风格 -->
|
||||
<div v-else-if="step === 2" class="form">
|
||||
<p class="field-label">选择形象</p>
|
||||
|
||||
<!-- 默认:沿用当前用户的会会头像 -->
|
||||
<div
|
||||
v-if="userAvatarUrl"
|
||||
class="avatar-photo-option"
|
||||
:class="{ active: usePhoto }"
|
||||
@click="selectMyAvatar"
|
||||
>
|
||||
<div class="apo-thumb">
|
||||
<img :src="userAvatarUrl" alt="" referrerpolicy="no-referrer" />
|
||||
</div>
|
||||
<div class="apo-meta">
|
||||
<span class="apo-title">使用我的头像</span>
|
||||
<span class="apo-sub">沿用你的会会账号头像</span>
|
||||
</div>
|
||||
<span class="apo-check">✓</span>
|
||||
</div>
|
||||
|
||||
<div class="emoji-grid">
|
||||
<button
|
||||
v-for="e in emojis"
|
||||
:key="e"
|
||||
class="emoji-opt"
|
||||
:class="{ active: !usePhoto && form.emoji === e }"
|
||||
@click="selectEmoji(e)"
|
||||
>{{ e }}</button>
|
||||
</div>
|
||||
<p class="field-label">回复风格</p>
|
||||
<div class="seg">
|
||||
<button
|
||||
v-for="s in styles"
|
||||
:key="s.value"
|
||||
class="seg-btn"
|
||||
:class="{ active: form.replyStyle === s.value }"
|
||||
@click="form.replyStyle = s.value"
|
||||
>{{ s.label }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Step 3:灵魂配置 -->
|
||||
<div v-else-if="step === 3" class="form">
|
||||
<div class="slider-item">
|
||||
<div class="slider-head"><span>创造力</span><b>{{ form.creativity }}</b></div>
|
||||
<input type="range" min="0" max="100" v-model.number="form.creativity" class="range" />
|
||||
</div>
|
||||
<div class="slider-item">
|
||||
<div class="slider-head"><span>严谨度</span><b>{{ form.rigor }}</b></div>
|
||||
<input type="range" min="0" max="100" v-model.number="form.rigor" class="range" />
|
||||
</div>
|
||||
<div class="slider-item">
|
||||
<div class="slider-head"><span>幽默感</span><b>{{ form.humor }}</b></div>
|
||||
<input type="range" min="0" max="100" v-model.number="form.humor" class="range" />
|
||||
</div>
|
||||
<p class="field-label">回复长度</p>
|
||||
<div class="seg">
|
||||
<button
|
||||
v-for="l in lengths"
|
||||
:key="l.value"
|
||||
class="seg-btn"
|
||||
:class="{ active: form.responseLength === l.value }"
|
||||
@click="form.responseLength = l.value"
|
||||
>{{ l.label }}</button>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="field-label">系统提示词(选填)</label>
|
||||
<textarea v-model="form.systemPrompt" class="textarea" rows="3" placeholder="给分身的额外指令,如:语气要专业、回答要简洁"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Step 4:确认创建 -->
|
||||
<div v-else class="confirm">
|
||||
<div class="confirm-avatar">
|
||||
<img v-if="usePhoto && form.photoUrl" :src="form.photoUrl" alt="" referrerpolicy="no-referrer" />
|
||||
<span v-else>{{ form.emoji }}</span>
|
||||
</div>
|
||||
<h3 class="confirm-name">{{ form.displayName || form.name || '未命名分身' }}</h3>
|
||||
<p class="confirm-desc">{{ form.description || '暂无描述' }}</p>
|
||||
<ul class="confirm-list">
|
||||
<li><span>回复风格</span><b>{{ styleLabel }}</b></li>
|
||||
<li><span>创造力 / 严谨度 / 幽默感</span><b>{{ form.creativity }} / {{ form.rigor }} / {{ form.humor }}</b></li>
|
||||
<li><span>回复长度</span><b>{{ lengthLabel }}</b></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="wizard-footer">
|
||||
<button v-if="step > 1" class="btn-secondary" @click="prev">上一步</button>
|
||||
<button v-if="step < 4" class="btn-primary" :disabled="!canNext" @click="next">下一步</button>
|
||||
<button v-else class="btn-primary" :disabled="submitting" @click="submit">
|
||||
{{ submitting ? '创建中...' : '创建分身' }}
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, computed, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useAvatarStore } from '@/store/avatar'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import { getCurrentUser } from '@/api'
|
||||
|
||||
const router = useRouter()
|
||||
const avatarStore = useAvatarStore()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const step = ref(0)
|
||||
const stepTitles = ['基础信息', '形象风格', '灵魂配置', '确认创建']
|
||||
const submitting = ref(false)
|
||||
|
||||
const emojis = ['🤖', '😊', '🦊', '🐱', '🦁', '🐼', '👩💻', '🧑🚀', '💡', '🌟']
|
||||
const styles = [
|
||||
{ value: 'professional', label: '专业' },
|
||||
{ value: 'casual', label: '轻松' },
|
||||
{ value: 'friendly', label: '亲切' }
|
||||
]
|
||||
const lengths = [
|
||||
{ value: 'short', label: '简短' },
|
||||
{ value: 'medium', label: '适中' },
|
||||
{ value: 'long', label: '详细' }
|
||||
]
|
||||
|
||||
const form = reactive({
|
||||
name: '',
|
||||
displayName: '',
|
||||
photoUrl: '',
|
||||
description: '',
|
||||
emoji: '🤖',
|
||||
replyStyle: 'professional',
|
||||
creativity: 50,
|
||||
rigor: 50,
|
||||
humor: 30,
|
||||
responseLength: 'medium',
|
||||
systemPrompt: ''
|
||||
})
|
||||
|
||||
const styleLabel = computed(() => styles.find(s => s.value === form.replyStyle)?.label || '')
|
||||
const lengthLabel = computed(() => lengths.find(l => l.value === form.responseLength)?.label || '')
|
||||
|
||||
// 当前登录用户的会会头像(用于「形象选择」默认项)
|
||||
// 优先采用真实登录态 userStore.user,避免被空的 userProfile 覆盖
|
||||
const userAvatarUrl = computed(() => {
|
||||
const u = userStore.user
|
||||
const p = avatarStore.userProfile
|
||||
return ((u?.avatarUrl as string) || (p?.avatarUrl as string) || '')
|
||||
})
|
||||
|
||||
// 形象选择:默认沿用当前用户的会会头像;选中表情后切换为 emoji 模式
|
||||
const usePhoto = ref(false)
|
||||
const selectMyAvatar = () => {
|
||||
usePhoto.value = true
|
||||
form.photoUrl = userAvatarUrl.value
|
||||
}
|
||||
const selectEmoji = (e: string) => {
|
||||
form.emoji = e
|
||||
usePhoto.value = false
|
||||
form.photoUrl = ''
|
||||
}
|
||||
|
||||
const canNext = computed(() => {
|
||||
if (step.value === 1) return form.name.trim().length > 0
|
||||
return true
|
||||
})
|
||||
|
||||
const next = () => { if (step.value < 4) step.value++ }
|
||||
const prev = () => { if (step.value === 1) step.value = 0; else step.value-- }
|
||||
|
||||
const submit = async () => {
|
||||
if (submitting.value) return
|
||||
submitting.value = true
|
||||
try {
|
||||
await avatarStore.addAvatar({
|
||||
name: form.name,
|
||||
displayName: form.displayName || form.name,
|
||||
photoUrl: usePhoto.value ? form.photoUrl : '',
|
||||
description: form.description,
|
||||
emoji: form.emoji,
|
||||
config: {
|
||||
replyStyle: form.replyStyle,
|
||||
creativity: form.creativity,
|
||||
rigor: form.rigor,
|
||||
humor: form.humor,
|
||||
responseLength: form.responseLength,
|
||||
systemPrompt: form.systemPrompt
|
||||
}
|
||||
})
|
||||
router.push('/avatar/manage')
|
||||
} catch (e) {
|
||||
alert('创建失败,请稍后重试')
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 预填会会登录用户的名称与头像(沿用当前账号资料)
|
||||
// 优先采用真实登录态 userStore.user(持久化会话),避免被空资料 / mock 覆盖
|
||||
onMounted(async () => {
|
||||
userStore.loadFromStorage()
|
||||
const real = userStore.user
|
||||
const native = avatarStore.userProfile
|
||||
let nick = real?.nickname || native?.nickname || ''
|
||||
let avatar = real?.avatarUrl || native?.avatarUrl || ''
|
||||
// 兜底:本地资料为空时,向服务端 /huihui/me 拉取真实登录态(Bearer app_token)
|
||||
if ((!nick || !avatar) && userStore.isLogin) {
|
||||
try {
|
||||
const me2: any = await getCurrentUser()
|
||||
nick = nick || me2?.nickname || ''
|
||||
avatar = avatar || me2?.avatarUrl || ''
|
||||
} catch { /* 忽略,保持本地值 */ }
|
||||
}
|
||||
if (nick) {
|
||||
if (!form.displayName) form.displayName = nick
|
||||
if (!form.name) form.name = nick
|
||||
}
|
||||
if (avatar && !form.photoUrl) form.photoUrl = avatar
|
||||
// 「形象选择」默认使用当前用户的会会头像
|
||||
if (userAvatarUrl.value) usePhoto.value = true
|
||||
})
|
||||
|
||||
const goManage = () => router.push('/avatar/manage')
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.create-page {
|
||||
min-height: 100vh;
|
||||
background: #F8F9FA;
|
||||
}
|
||||
|
||||
/* ===== 落地页 ===== */
|
||||
.landing { padding-bottom: 40px; }
|
||||
|
||||
.hero {
|
||||
background: linear-gradient(135deg, #F97316 0%, #FB923C 50%, #FDBA74 100%);
|
||||
padding: 60px 20px 80px;
|
||||
text-align: center;
|
||||
color: white;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.hero-icon {
|
||||
font-size: 80px;
|
||||
margin-bottom: 20px;
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
@keyframes pulse { 0%,100% { transform: scale(1);} 50% { transform: scale(1.1);} }
|
||||
.hero-title { font-size: 28px; font-weight: 700; margin: 0 0 12px; letter-spacing: 1px; }
|
||||
.hero-sub { font-size: 16px; opacity: 0.9; margin: 0; }
|
||||
|
||||
.features {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 12px;
|
||||
padding: 20px;
|
||||
margin-top: -40px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
.feature-card {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
padding: 20px 16px;
|
||||
text-align: center;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
|
||||
}
|
||||
.feature-icon { font-size: 36px; margin-bottom: 12px; }
|
||||
.feature-card h3 { font-size: 15px; font-weight: 600; color: #18191C; margin: 0 0 8px; }
|
||||
.feature-card p { font-size: 12px; color: #9398AE; margin: 0; line-height: 1.4; }
|
||||
|
||||
.cta { padding: 30px 20px; text-align: center; }
|
||||
.create-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 16px 48px;
|
||||
background: linear-gradient(135deg, #F97316 0%, #FB923C 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 16px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 8px 20px rgba(249,115,22,0.4);
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
.create-btn:active { transform: scale(0.97); }
|
||||
.hint { font-size: 13px; color: #9398AE; margin-top: 16px; }
|
||||
|
||||
.existing { text-align: center; padding: 10px; }
|
||||
.existing p { font-size: 14px; color: #9398AE; margin: 0 0 12px; }
|
||||
.link-btn {
|
||||
background: none; border: 2px solid #F97316; color: #F97316;
|
||||
padding: 10px 24px; border-radius: 10px; font-size: 14px; font-weight: 600; cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.link-btn:hover { background: #F97316; color: white; }
|
||||
|
||||
/* ===== 向导 ===== */
|
||||
.wizard { min-height: 100vh; display: flex; flex-direction: column; }
|
||||
.wizard-header {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: 16px 20px; background: white; border-bottom: 1px solid #EDEEF1;
|
||||
}
|
||||
.back { background: none; border: none; font-size: 26px; cursor: pointer; color: #18191C; padding: 0 8px; }
|
||||
.w-title { font-size: 17px; font-weight: 600; margin: 0; color: #18191C; }
|
||||
.step-count { font-size: 13px; color: #9398AE; }
|
||||
|
||||
.progress { height: 4px; background: #EDEEF1; }
|
||||
.progress-bar { height: 100%; background: linear-gradient(90deg, #F97316, #FB923C); transition: width 0.35s ease; }
|
||||
|
||||
.wizard-body { flex: 1; padding: 24px 20px; }
|
||||
|
||||
.form { display: flex; flex-direction: column; gap: 20px; }
|
||||
.field { display: flex; flex-direction: column; gap: 8px; }
|
||||
.field-label { font-size: 14px; font-weight: 500; color: #18191C; }
|
||||
.req { color: #EF4444; }
|
||||
|
||||
.avatar-pick { display: flex; flex-direction: column; gap: 10px; }
|
||||
.avatar-pick-preview {
|
||||
width: 72px; height: 72px; border-radius: 50%; overflow: hidden;
|
||||
background: linear-gradient(135deg, #F97316, #FB923C);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 36px; box-shadow: 0 4px 12px rgba(249,115,22,0.3);
|
||||
border: 2px solid #fff;
|
||||
}
|
||||
.avatar-pick-preview img { width: 100%; height: 100%; object-fit: cover; }
|
||||
.avatar-pick-hint { font-size: 12px; color: #9398AE; margin: 0; }
|
||||
.input, .textarea {
|
||||
width: 100%; padding: 12px 16px; border: 1px solid #EDEEF1; border-radius: 10px;
|
||||
font-size: 15px; color: #18191C; background: white; font-family: inherit;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
.input:focus, .textarea:focus { outline: none; border-color: #F97316; }
|
||||
.textarea { resize: vertical; }
|
||||
|
||||
.emoji-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 10px; }
|
||||
.emoji-opt {
|
||||
aspect-ratio: 1; font-size: 28px; border: 2px solid #EDEEF1; border-radius: 12px;
|
||||
background: white; cursor: pointer; transition: all 0.2s;
|
||||
}
|
||||
.emoji-opt.active { border-color: #F97316; background: #FFF0E6; }
|
||||
|
||||
/* 形象选择:默认沿用会会头像 */
|
||||
.avatar-photo-option {
|
||||
display: flex; align-items: center; gap: 14px;
|
||||
padding: 14px 16px; border: 2px solid #EDEEF1; border-radius: 14px;
|
||||
background: white; cursor: pointer; transition: all 0.2s; margin-bottom: 18px;
|
||||
}
|
||||
.avatar-photo-option.active { border-color: #F97316; background: #FFF0E6; }
|
||||
.apo-thumb {
|
||||
width: 52px; height: 52px; border-radius: 50%; overflow: hidden; flex: 0 0 auto;
|
||||
background: linear-gradient(135deg, #F97316, #FB923C);
|
||||
border: 2px solid #fff; box-shadow: 0 2px 8px rgba(249,115,22,0.3);
|
||||
}
|
||||
.apo-thumb img { width: 100%; height: 100%; object-fit: cover; }
|
||||
.apo-meta { flex: 1; display: flex; flex-direction: column; gap: 2px; }
|
||||
.apo-title { font-size: 15px; font-weight: 600; color: #18191C; }
|
||||
.apo-sub { font-size: 12px; color: #9398AE; }
|
||||
.apo-check { font-size: 18px; color: #F97316; opacity: 0; transition: opacity 0.2s; flex: 0 0 auto; }
|
||||
.avatar-photo-option.active .apo-check { opacity: 1; }
|
||||
|
||||
.seg { display: flex; gap: 12px; }
|
||||
.seg-btn {
|
||||
flex: 1; padding: 12px; border: 2px solid #EDEEF1; border-radius: 10px; background: white;
|
||||
font-size: 14px; color: #9398AE; cursor: pointer; transition: all 0.2s;
|
||||
}
|
||||
.seg-btn.active { border-color: #F97316; color: #F97316; background: #FFF0E6; }
|
||||
|
||||
.slider-item { display: flex; flex-direction: column; gap: 10px; }
|
||||
.slider-head { display: flex; justify-content: space-between; font-size: 14px; color: #18191C; }
|
||||
.slider-head b { color: #F97316; }
|
||||
.range { -webkit-appearance: none; width: 100%; height: 6px; border-radius: 3px; background: #EDEEF1; outline: none; }
|
||||
.range::-webkit-slider-thumb {
|
||||
-webkit-appearance: none; width: 22px; height: 22px; border-radius: 50%;
|
||||
background: #F97316; cursor: pointer; box-shadow: 0 2px 6px rgba(249,115,22,0.4);
|
||||
}
|
||||
|
||||
.confirm { display: flex; flex-direction: column; align-items: center; gap: 12px; padding-top: 10px; }
|
||||
.confirm-avatar {
|
||||
width: 88px; height: 88px; border-radius: 50%; overflow: hidden;
|
||||
background: linear-gradient(135deg, #F97316, #FB923C);
|
||||
display: flex; align-items: center; justify-content: center; font-size: 44px;
|
||||
box-shadow: 0 4px 12px rgba(249,115,22,0.3);
|
||||
border: 2px solid #fff;
|
||||
}
|
||||
.confirm-avatar img { width: 100%; height: 100%; object-fit: cover; }
|
||||
.confirm-name { font-size: 20px; font-weight: 700; color: #18191C; margin: 0; }
|
||||
.confirm-desc { font-size: 14px; color: #9398AE; margin: 0; text-align: center; }
|
||||
.confirm-list { width: 100%; list-style: none; padding: 0; margin: 12px 0 0; display: flex; flex-direction: column; gap: 10px; }
|
||||
.confirm-list li {
|
||||
display: flex; justify-content: space-between; padding: 14px 16px; background: white;
|
||||
border-radius: 10px; font-size: 13px; color: #9398AE; box-shadow: 0 2px 8px rgba(0,0,0,0.04);
|
||||
}
|
||||
.confirm-list li b { color: #18191C; font-weight: 600; }
|
||||
|
||||
.wizard-footer {
|
||||
display: flex; gap: 12px; padding: 16px 20px;
|
||||
padding-bottom: calc(16px + env(safe-area-inset-bottom));
|
||||
background: white; border-top: 1px solid #EDEEF1;
|
||||
}
|
||||
.btn-secondary {
|
||||
flex: 0 0 auto; padding: 14px 24px; background: white; color: #18191C;
|
||||
border: 2px solid #EDEEF1; border-radius: 12px; font-size: 15px; font-weight: 600; cursor: pointer;
|
||||
}
|
||||
.btn-primary {
|
||||
flex: 1; padding: 14px; background: linear-gradient(135deg, #F97316, #FB923C); color: white;
|
||||
border: none; border-radius: 12px; font-size: 16px; font-weight: 600; cursor: pointer;
|
||||
box-shadow: 0 4px 12px rgba(249,115,22,0.3); transition: opacity 0.2s;
|
||||
}
|
||||
.btn-primary:disabled { background: #D1D5DB; box-shadow: none; cursor: not-allowed; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user