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:
@@ -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"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user