feat: add PopupAd and AdminPopupAd interfaces with CRUD operations

- Introduced PopupAd and AdminPopupAd interfaces in common.ts.
- Implemented encoding, decoding, and JSON conversion methods for both PopupAd and AdminPopupAd.
- Added new RPC methods for managing PopupAds in admin.ts and me.ts, including list, create, update, and delete functionalities.
- Integrated PopupAdsClient in grpcClient.ts for gRPC communication.
- Updated auth store to handle real-time notifications for user-specific topics.
- Modified tsconfig.json to include auto-imports and components type definitions.
This commit is contained in:
2026-03-29 06:42:37 +00:00
parent 43702e8bf7
commit 8515498ade
31 changed files with 3905 additions and 78 deletions

View File

@@ -1,12 +1,11 @@
<script setup lang="ts">
import { cn } from '@/lib/utils';
import { computed } from 'vue';
import { computed, useSlots } from 'vue';
// Vue macro is available at compile time; provide a safe fallback for typecheck.
declare const defineModelModifiers: undefined | (<T>() => T);
type Props = {
as?: 'input' | 'textarea' | 'select';
modelValue?: string | number | null;
type?: string;
placeholder?: string;
@@ -21,14 +20,17 @@ type Props = {
max?: number | string;
step?: number | string;
maxlength?: number;
rows?: number;
};
const props = withDefaults(defineProps<Props>(), {
as: 'input',
modelValue: '',
type: 'text',
placeholder: '',
readonly: false,
disabled: false,
rows: 3,
});
const emit = defineEmits<{
@@ -40,10 +42,13 @@ const modelModifiers = (typeof defineModelModifiers === 'function'
? defineModelModifiers<{ number?: boolean }>()
: ({} as { number?: boolean }));
const isNumberLike = computed(() => props.type === 'number' || !!modelModifiers.number);
const isNumberLike = computed(() => props.as === 'input' && (props.type === 'number' || !!modelModifiers.number));
const hasLeadingSlot = computed(() => props.as === 'input' && !!useSlots().prefix);
const isTextarea = computed(() => props.as === 'textarea');
const isSelect = computed(() => props.as === 'select');
const onInput = (e: Event) => {
const el = e.target as HTMLInputElement;
const el = e.target as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;
const raw = el.value;
if (isNumberLike.value) {
if (raw === '') {
@@ -66,11 +71,40 @@ const baseInputClass = 'w-full px-3 py-2 rounded-md border border-border bg-head
<template>
<div :class="cn('relative', wrapperClass)">
<div v-if="$slots.prefix" class="absolute left-3 top-1/2 -translate-y-1/2 text-foreground/50">
<div v-if="hasLeadingSlot" class="absolute left-3 top-1/2 -translate-y-1/2 text-foreground/50">
<slot name="prefix" />
</div>
<textarea
v-if="isTextarea"
:id="id"
:name="name"
:value="modelValue ?? ''"
:rows="rows"
:placeholder="placeholder"
:readonly="readonly"
:disabled="disabled"
:maxlength="maxlength"
:class="cn(baseInputClass, inputClass)"
@input="onInput"
@keyup="onKeyup"
/>
<select
v-else-if="isSelect"
:id="id"
:name="name"
:value="modelValue ?? ''"
:disabled="disabled"
:class="cn(baseInputClass, inputClass)"
@change="onInput"
@keyup="onKeyup"
>
<slot />
</select>
<input
v-else
:id="id"
:name="name"
:type="type"
@@ -83,7 +117,7 @@ const baseInputClass = 'w-full px-3 py-2 rounded-md border border-border bg-head
:max="max"
:step="step"
:maxlength="maxlength"
:class="cn(baseInputClass, $slots.prefix ? 'pl-10' : '', inputClass)"
:class="cn(baseInputClass, hasLeadingSlot ? 'pl-10' : '', inputClass)"
@input="onInput"
@keyup="onKeyup"
/>