2026-05-30 21:18:13 +09:00
|
|
|
<script setup>
|
2026-05-31 19:02:05 +09:00
|
|
|
import { computed, onMounted, ref } from 'vue'
|
|
|
|
|
import { adminApi } from '@/api/adminApi'
|
|
|
|
|
import { useAuthStore } from '@/stores/auth'
|
2026-06-24 21:57:50 +09:00
|
|
|
import { useDialog } from '@/composables/dialog'
|
2026-05-31 15:42:52 +09:00
|
|
|
import IconBtn from '@/components/ui/IconBtn.vue'
|
2026-05-30 21:18:13 +09:00
|
|
|
|
2026-05-31 19:02:05 +09:00
|
|
|
const auth = useAuthStore()
|
2026-06-24 21:57:50 +09:00
|
|
|
const dialog = useDialog()
|
2026-05-31 19:02:05 +09:00
|
|
|
const myId = computed(() => auth.user?.id)
|
2026-05-30 21:18:13 +09:00
|
|
|
|
2026-05-31 19:02:05 +09:00
|
|
|
const members = ref([])
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
const error = ref(null)
|
|
|
|
|
const keyword = ref('')
|
2026-05-30 21:18:13 +09:00
|
|
|
|
2026-05-31 19:02:05 +09:00
|
|
|
const filtered = computed(() => {
|
|
|
|
|
const k = keyword.value.trim().toLowerCase()
|
|
|
|
|
if (!k) return members.value
|
|
|
|
|
return members.value.filter(
|
|
|
|
|
(m) =>
|
|
|
|
|
(m.loginId || '').toLowerCase().includes(k) ||
|
|
|
|
|
(m.name || '').toLowerCase().includes(k) ||
|
|
|
|
|
(m.email || '').toLowerCase().includes(k),
|
|
|
|
|
)
|
2026-05-30 21:18:13 +09:00
|
|
|
})
|
|
|
|
|
|
2026-05-31 19:02:05 +09:00
|
|
|
function fmtDate(v) {
|
|
|
|
|
if (!v) return '-'
|
|
|
|
|
const d = new Date(v)
|
|
|
|
|
return Number.isNaN(d.getTime()) ? v : `${d.getFullYear()}.${String(d.getMonth() + 1).padStart(2, '0')}.${String(d.getDate()).padStart(2, '0')}`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function load() {
|
|
|
|
|
loading.value = true
|
|
|
|
|
error.value = null
|
2026-05-30 21:18:13 +09:00
|
|
|
try {
|
2026-05-31 19:02:05 +09:00
|
|
|
members.value = await adminApi.members()
|
2026-05-30 21:18:13 +09:00
|
|
|
} catch (e) {
|
2026-05-31 19:02:05 +09:00
|
|
|
error.value = e.response?.data?.message || '불러오지 못했습니다.'
|
2026-05-30 21:18:13 +09:00
|
|
|
} finally {
|
2026-05-31 19:02:05 +09:00
|
|
|
loading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-06 00:16:50 +09:00
|
|
|
// 회원가입 허용 설정
|
|
|
|
|
const signupEnabled = ref(true)
|
|
|
|
|
const signupSaving = ref(false)
|
|
|
|
|
async function loadSignupSetting() {
|
|
|
|
|
try {
|
|
|
|
|
const res = await adminApi.getSignupSetting()
|
|
|
|
|
signupEnabled.value = !!res.enabled
|
|
|
|
|
} catch {
|
|
|
|
|
/* 무시 — 기본 허용 표시 */
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
async function toggleSignup() {
|
|
|
|
|
if (signupSaving.value) return
|
|
|
|
|
const next = !signupEnabled.value
|
|
|
|
|
signupSaving.value = true
|
|
|
|
|
try {
|
|
|
|
|
const res = await adminApi.setSignupSetting(next)
|
|
|
|
|
signupEnabled.value = !!res.enabled
|
|
|
|
|
} catch (e) {
|
|
|
|
|
alert(e.response?.data?.message || '설정 변경에 실패했습니다.')
|
|
|
|
|
} finally {
|
|
|
|
|
signupSaving.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-31 19:02:05 +09:00
|
|
|
async function changeRole(m, role) {
|
|
|
|
|
if (role === m.role) return
|
|
|
|
|
try {
|
|
|
|
|
const updated = await adminApi.updateMemberRole(m.id, role)
|
|
|
|
|
Object.assign(m, updated)
|
|
|
|
|
} catch (e) {
|
|
|
|
|
alert(e.response?.data?.message || '역할 변경 실패')
|
|
|
|
|
await load()
|
2026-05-30 21:18:13 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-28 11:02:44 +09:00
|
|
|
async function changePlan(m, plan) {
|
|
|
|
|
if (plan === m.plan) return
|
|
|
|
|
try {
|
|
|
|
|
const updated = await adminApi.updateMemberPlan(m.id, plan)
|
|
|
|
|
Object.assign(m, updated)
|
|
|
|
|
} catch (e) {
|
|
|
|
|
alert(e.response?.data?.message || '멤버십 변경 실패')
|
|
|
|
|
await load()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-31 19:02:05 +09:00
|
|
|
async function changeStatus(m, status) {
|
|
|
|
|
if (status === m.status) return
|
2026-05-30 21:18:13 +09:00
|
|
|
try {
|
2026-05-31 19:02:05 +09:00
|
|
|
const updated = await adminApi.updateMemberStatus(m.id, status)
|
|
|
|
|
Object.assign(m, updated)
|
2026-05-30 21:18:13 +09:00
|
|
|
} catch (e) {
|
2026-05-31 19:02:05 +09:00
|
|
|
alert(e.response?.data?.message || '상태 변경 실패')
|
|
|
|
|
await load()
|
2026-05-30 21:18:13 +09:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-31 19:02:05 +09:00
|
|
|
async function remove(m) {
|
2026-06-24 21:57:50 +09:00
|
|
|
if (!(await dialog.confirm(`'${m.name}(${m.loginId})' 회원을 삭제할까요?\n되돌릴 수 없습니다.`, { title: '회원 삭제', danger: true }))) return
|
2026-05-31 19:02:05 +09:00
|
|
|
try {
|
|
|
|
|
await adminApi.removeMember(m.id)
|
|
|
|
|
members.value = members.value.filter((x) => x.id !== m.id)
|
|
|
|
|
} catch (e) {
|
|
|
|
|
alert(e.response?.data?.message || '삭제 실패')
|
|
|
|
|
}
|
2026-05-30 21:18:13 +09:00
|
|
|
}
|
2026-05-31 19:02:05 +09:00
|
|
|
|
2026-06-06 00:16:50 +09:00
|
|
|
onMounted(() => {
|
|
|
|
|
load()
|
|
|
|
|
loadSignupSetting()
|
|
|
|
|
})
|
2026-05-30 21:18:13 +09:00
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
2026-05-31 19:02:05 +09:00
|
|
|
<main class="members">
|
2026-06-27 22:59:20 +09:00
|
|
|
<header class="head" style="justify-content: flex-end">
|
2026-05-31 19:02:05 +09:00
|
|
|
<div class="head-actions">
|
|
|
|
|
<input v-model="keyword" type="search" class="search" placeholder="아이디·이름·이메일 검색" />
|
|
|
|
|
<IconBtn icon="refresh" title="새로고침" size="sm" :disabled="loading" @click="load" />
|
|
|
|
|
</div>
|
|
|
|
|
</header>
|
2026-06-28 11:02:44 +09:00
|
|
|
<p class="hint">가입한 회원의 역할·멤버십·상태를 관리합니다. (관리자 전용)</p>
|
2026-05-30 21:18:13 +09:00
|
|
|
|
2026-06-06 00:16:50 +09:00
|
|
|
<!-- 회원가입 허용 토글 -->
|
|
|
|
|
<div class="signup-toggle" :class="{ off: !signupEnabled }">
|
|
|
|
|
<div class="st-text">
|
|
|
|
|
<strong>회원가입 {{ signupEnabled ? '허용' : '제한' }}</strong>
|
|
|
|
|
<span class="st-desc">{{ signupEnabled ? '누구나 회원가입할 수 있습니다.' : '신규 회원가입이 차단됩니다.' }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<button
|
|
|
|
|
type="button" class="switch" :class="{ on: signupEnabled }"
|
|
|
|
|
:disabled="signupSaving" role="switch" :aria-checked="signupEnabled"
|
|
|
|
|
@click="toggleSignup"
|
|
|
|
|
><span class="knob"></span></button>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-05-30 21:18:13 +09:00
|
|
|
<p v-if="error" class="msg error">{{ error }}</p>
|
2026-05-31 19:02:05 +09:00
|
|
|
<p v-if="loading" class="msg">불러오는 중...</p>
|
2026-05-30 21:18:13 +09:00
|
|
|
|
2026-05-31 19:02:05 +09:00
|
|
|
<table v-else-if="filtered.length" class="member-table">
|
2026-05-30 21:18:13 +09:00
|
|
|
<thead>
|
|
|
|
|
<tr>
|
2026-05-31 19:02:05 +09:00
|
|
|
<th class="c-id">ID</th>
|
|
|
|
|
<th>아이디 / 이름</th>
|
|
|
|
|
<th class="c-email">이메일</th>
|
|
|
|
|
<th class="c-role">역할</th>
|
2026-06-28 11:02:44 +09:00
|
|
|
<th class="c-plan">멤버십</th>
|
2026-05-31 19:02:05 +09:00
|
|
|
<th class="c-status">상태</th>
|
|
|
|
|
<th class="c-date">가입일</th>
|
|
|
|
|
<th class="c-act"></th>
|
2026-05-30 21:18:13 +09:00
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody>
|
2026-05-31 19:02:05 +09:00
|
|
|
<tr v-for="m in filtered" :key="m.id" :class="{ me: m.id === myId, suspended: m.status !== 'ACTIVE' }">
|
|
|
|
|
<td class="c-id">{{ m.id }}</td>
|
|
|
|
|
<td class="c-user">
|
|
|
|
|
<div class="login">{{ m.loginId || '(소셜)' }}<span v-if="m.id === myId" class="badge-me">나</span></div>
|
|
|
|
|
<div class="name">{{ m.name }}<span v-if="m.provider && m.provider !== 'LOCAL'" class="prov">{{ m.provider }}</span></div>
|
|
|
|
|
</td>
|
|
|
|
|
<td class="c-email">{{ m.email || '-' }}</td>
|
|
|
|
|
<td class="c-role">
|
|
|
|
|
<select :value="m.role" :disabled="m.id === myId" @change="changeRole(m, $event.target.value)">
|
|
|
|
|
<option value="USER">USER</option>
|
|
|
|
|
<option value="ADMIN">ADMIN</option>
|
|
|
|
|
</select>
|
|
|
|
|
</td>
|
2026-06-28 11:02:44 +09:00
|
|
|
<td class="c-plan">
|
|
|
|
|
<select
|
|
|
|
|
:value="m.plan || 'FREE'"
|
|
|
|
|
class="plan-sel" :class="(m.plan || 'FREE').toLowerCase()"
|
|
|
|
|
@change="changePlan(m, $event.target.value)"
|
|
|
|
|
>
|
|
|
|
|
<option value="FREE">무료</option>
|
|
|
|
|
<option value="PREMIUM">유료</option>
|
|
|
|
|
</select>
|
|
|
|
|
</td>
|
2026-05-31 19:02:05 +09:00
|
|
|
<td class="c-status">
|
|
|
|
|
<select
|
|
|
|
|
:value="m.status" :disabled="m.id === myId"
|
|
|
|
|
class="status-sel" :class="m.status.toLowerCase()"
|
|
|
|
|
@change="changeStatus(m, $event.target.value)"
|
|
|
|
|
>
|
|
|
|
|
<option value="ACTIVE">활성</option>
|
|
|
|
|
<option value="SUSPENDED">정지</option>
|
|
|
|
|
<option value="WITHDRAWN">탈퇴</option>
|
|
|
|
|
</select>
|
|
|
|
|
</td>
|
|
|
|
|
<td class="c-date">{{ fmtDate(m.createdAt) }}</td>
|
|
|
|
|
<td class="c-act">
|
|
|
|
|
<IconBtn icon="trash" title="삭제" variant="danger" size="sm" :disabled="m.id === myId" @click="remove(m)" />
|
2026-05-30 21:18:13 +09:00
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
2026-05-31 19:02:05 +09:00
|
|
|
<p v-else-if="!loading" class="msg">{{ keyword ? '검색 결과가 없습니다.' : '회원이 없습니다.' }}</p>
|
2026-05-30 21:18:13 +09:00
|
|
|
</main>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
2026-05-31 19:02:05 +09:00
|
|
|
.members {
|
|
|
|
|
max-width: 900px;
|
2026-05-30 21:18:13 +09:00
|
|
|
margin: 0 auto;
|
|
|
|
|
}
|
2026-05-31 19:02:05 +09:00
|
|
|
.head {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
gap: 0.5rem;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
}
|
2026-05-30 21:18:13 +09:00
|
|
|
h1 {
|
|
|
|
|
font-size: 1.5rem;
|
|
|
|
|
}
|
2026-05-31 19:02:05 +09:00
|
|
|
.head-actions {
|
2026-05-30 21:18:13 +09:00
|
|
|
display: flex;
|
2026-05-31 19:02:05 +09:00
|
|
|
align-items: center;
|
2026-05-30 21:18:13 +09:00
|
|
|
gap: 0.5rem;
|
|
|
|
|
}
|
2026-05-31 19:02:05 +09:00
|
|
|
.search {
|
|
|
|
|
padding: 0.45rem 0.7rem;
|
2026-05-30 21:18:13 +09:00
|
|
|
border: 1px solid var(--color-border);
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
background: var(--color-background-soft);
|
|
|
|
|
color: var(--color-text);
|
2026-05-31 19:02:05 +09:00
|
|
|
min-width: 12rem;
|
2026-05-30 21:18:13 +09:00
|
|
|
}
|
2026-05-31 19:02:05 +09:00
|
|
|
.hint {
|
|
|
|
|
font-size: 0.85rem;
|
|
|
|
|
opacity: 0.7;
|
|
|
|
|
margin: 0.5rem 0 1rem;
|
|
|
|
|
}
|
2026-06-06 00:16:50 +09:00
|
|
|
.signup-toggle {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
gap: 1rem;
|
|
|
|
|
padding: 0.8rem 1rem;
|
|
|
|
|
margin-bottom: 1rem;
|
|
|
|
|
border: 1px solid var(--color-border);
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
background: var(--color-background-soft);
|
|
|
|
|
}
|
|
|
|
|
.signup-toggle.off {
|
|
|
|
|
border-color: #c0392b;
|
|
|
|
|
background: rgba(192, 57, 43, 0.06);
|
|
|
|
|
}
|
|
|
|
|
.st-text {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 0.15rem;
|
|
|
|
|
min-width: 0;
|
|
|
|
|
}
|
|
|
|
|
.st-text strong {
|
|
|
|
|
font-size: 0.95rem;
|
|
|
|
|
}
|
|
|
|
|
.st-desc {
|
|
|
|
|
font-size: 0.8rem;
|
|
|
|
|
opacity: 0.7;
|
|
|
|
|
}
|
|
|
|
|
.switch {
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
width: 48px;
|
|
|
|
|
height: 28px;
|
|
|
|
|
border: 0;
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
background: #b0b0b0;
|
|
|
|
|
position: relative;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: background 0.18s;
|
|
|
|
|
padding: 0;
|
|
|
|
|
}
|
|
|
|
|
.switch.on {
|
|
|
|
|
background: hsla(160, 100%, 37%, 1);
|
|
|
|
|
}
|
|
|
|
|
.switch:disabled {
|
|
|
|
|
opacity: 0.6;
|
|
|
|
|
cursor: default;
|
|
|
|
|
}
|
|
|
|
|
.switch .knob {
|
|
|
|
|
position: absolute;
|
|
|
|
|
top: 3px;
|
|
|
|
|
left: 3px;
|
|
|
|
|
width: 22px;
|
|
|
|
|
height: 22px;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
background: #fff;
|
|
|
|
|
transition: transform 0.18s;
|
|
|
|
|
}
|
|
|
|
|
.switch.on .knob {
|
|
|
|
|
transform: translateX(20px);
|
|
|
|
|
}
|
2026-05-31 19:02:05 +09:00
|
|
|
.member-table {
|
|
|
|
|
width: 100%;
|
|
|
|
|
border-collapse: collapse;
|
|
|
|
|
font-size: 0.9rem;
|
|
|
|
|
}
|
|
|
|
|
.member-table th,
|
|
|
|
|
.member-table td {
|
|
|
|
|
padding: 0.5rem 0.5rem;
|
|
|
|
|
border-bottom: 1px solid var(--color-border);
|
|
|
|
|
text-align: left;
|
|
|
|
|
vertical-align: middle;
|
|
|
|
|
}
|
|
|
|
|
.c-id {
|
|
|
|
|
width: 48px;
|
|
|
|
|
}
|
|
|
|
|
.c-role,
|
2026-06-28 11:02:44 +09:00
|
|
|
.c-plan,
|
2026-05-31 19:02:05 +09:00
|
|
|
.c-status {
|
|
|
|
|
width: 90px;
|
|
|
|
|
}
|
|
|
|
|
.c-date {
|
|
|
|
|
width: 100px;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
}
|
|
|
|
|
.c-act {
|
|
|
|
|
width: 48px;
|
|
|
|
|
}
|
|
|
|
|
.member-table tr.me {
|
|
|
|
|
background: hsla(160, 100%, 37%, 0.06);
|
|
|
|
|
}
|
|
|
|
|
.member-table tr.suspended .name,
|
|
|
|
|
.member-table tr.suspended .login {
|
|
|
|
|
opacity: 0.6;
|
|
|
|
|
}
|
|
|
|
|
.login {
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
}
|
|
|
|
|
.badge-me {
|
|
|
|
|
margin-left: 0.35rem;
|
|
|
|
|
font-size: 0.68rem;
|
|
|
|
|
border: 1px solid hsla(160, 100%, 37%, 1);
|
|
|
|
|
color: hsla(160, 100%, 37%, 1);
|
|
|
|
|
border-radius: 3px;
|
|
|
|
|
padding: 0.02rem 0.3rem;
|
|
|
|
|
}
|
|
|
|
|
.name {
|
|
|
|
|
font-size: 0.82rem;
|
|
|
|
|
opacity: 0.75;
|
|
|
|
|
}
|
|
|
|
|
.prov {
|
|
|
|
|
margin-left: 0.3rem;
|
|
|
|
|
font-size: 0.7rem;
|
|
|
|
|
opacity: 0.6;
|
|
|
|
|
}
|
|
|
|
|
.member-table select {
|
|
|
|
|
padding: 0.3rem 0.4rem;
|
2026-05-30 21:18:13 +09:00
|
|
|
border: 1px solid var(--color-border);
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
background: var(--color-background-soft);
|
|
|
|
|
color: var(--color-text);
|
2026-05-31 19:02:05 +09:00
|
|
|
font-size: 0.82rem;
|
2026-05-30 21:18:13 +09:00
|
|
|
}
|
2026-05-31 19:02:05 +09:00
|
|
|
.member-table select:disabled {
|
|
|
|
|
opacity: 0.5;
|
2026-05-30 21:18:13 +09:00
|
|
|
cursor: not-allowed;
|
|
|
|
|
}
|
2026-05-31 19:02:05 +09:00
|
|
|
.status-sel.active {
|
|
|
|
|
color: #2e7d32;
|
2026-05-30 21:18:13 +09:00
|
|
|
}
|
2026-05-31 19:02:05 +09:00
|
|
|
.status-sel.suspended {
|
|
|
|
|
color: #e67e22;
|
2026-05-30 21:18:13 +09:00
|
|
|
}
|
2026-05-31 19:02:05 +09:00
|
|
|
.status-sel.withdrawn {
|
|
|
|
|
color: #c0392b;
|
2026-05-30 21:18:13 +09:00
|
|
|
}
|
2026-06-28 11:02:44 +09:00
|
|
|
.plan-sel.premium {
|
|
|
|
|
color: #b8860b;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
}
|
|
|
|
|
.plan-sel.free {
|
|
|
|
|
color: var(--color-text);
|
|
|
|
|
}
|
2026-05-30 21:18:13 +09:00
|
|
|
.msg {
|
2026-05-31 19:02:05 +09:00
|
|
|
margin: 1rem 0;
|
2026-05-30 21:18:13 +09:00
|
|
|
}
|
|
|
|
|
.msg.error {
|
|
|
|
|
color: #c0392b;
|
|
|
|
|
}
|
2026-05-31 15:42:52 +09:00
|
|
|
|
2026-05-31 19:02:05 +09:00
|
|
|
/* 모바일: 2줄 카드 */
|
2026-05-31 15:42:52 +09:00
|
|
|
@media (max-width: 768px) {
|
2026-05-31 19:02:05 +09:00
|
|
|
.members {
|
2026-05-31 15:42:52 +09:00
|
|
|
max-width: 100%;
|
|
|
|
|
}
|
2026-05-31 19:02:05 +09:00
|
|
|
.member-table thead {
|
2026-05-31 15:42:52 +09:00
|
|
|
display: none;
|
|
|
|
|
}
|
2026-05-31 19:02:05 +09:00
|
|
|
.member-table,
|
|
|
|
|
.member-table tbody,
|
|
|
|
|
.member-table tr,
|
|
|
|
|
.member-table td {
|
2026-05-31 15:42:52 +09:00
|
|
|
display: block;
|
|
|
|
|
}
|
2026-05-31 19:02:05 +09:00
|
|
|
.member-table tr {
|
2026-05-31 15:42:52 +09:00
|
|
|
display: flex;
|
|
|
|
|
flex-wrap: wrap;
|
2026-05-31 19:02:05 +09:00
|
|
|
align-items: center;
|
|
|
|
|
column-gap: 0.5rem;
|
|
|
|
|
row-gap: 0.3rem;
|
|
|
|
|
padding: 0.6rem 0.2rem;
|
2026-05-31 15:42:52 +09:00
|
|
|
border-bottom: 1px solid var(--color-border);
|
|
|
|
|
}
|
2026-05-31 19:02:05 +09:00
|
|
|
.member-table td {
|
2026-05-31 15:42:52 +09:00
|
|
|
border: 0;
|
|
|
|
|
padding: 0;
|
2026-05-31 19:02:05 +09:00
|
|
|
width: auto;
|
2026-05-31 15:42:52 +09:00
|
|
|
}
|
2026-05-31 19:02:05 +09:00
|
|
|
.c-user {
|
|
|
|
|
flex: 1 1 100%;
|
|
|
|
|
}
|
|
|
|
|
.c-id::before {
|
2026-05-31 15:42:52 +09:00
|
|
|
content: '#';
|
|
|
|
|
}
|
2026-05-31 19:02:05 +09:00
|
|
|
.c-id {
|
|
|
|
|
opacity: 0.6;
|
|
|
|
|
font-size: 0.8rem;
|
2026-05-31 15:42:52 +09:00
|
|
|
}
|
2026-05-31 19:02:05 +09:00
|
|
|
.c-email {
|
|
|
|
|
flex: 1 1 100%;
|
2026-05-31 15:42:52 +09:00
|
|
|
font-size: 0.8rem;
|
2026-05-31 19:02:05 +09:00
|
|
|
opacity: 0.75;
|
2026-05-31 15:42:52 +09:00
|
|
|
}
|
2026-05-31 19:02:05 +09:00
|
|
|
.c-date {
|
|
|
|
|
font-size: 0.78rem;
|
|
|
|
|
opacity: 0.65;
|
2026-05-31 15:42:52 +09:00
|
|
|
}
|
2026-05-31 19:02:05 +09:00
|
|
|
.c-act {
|
2026-05-31 15:42:52 +09:00
|
|
|
margin-left: auto;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-30 21:18:13 +09:00
|
|
|
</style>
|