<template>
  <view class="page">
    <view class="nav">
      <view :style="{ height: statusBarHeight + 'px' }" />
      <view class="nav-inner">
        <view class="nav-btn" @click="goBack">
          <image
            class="nav-back-img"
            :src="backIcon"
            mode="aspectFit"
          />
        </view>
        <text class="nav-title">作品详情</text>
        <view class="nav-btn" @click="onShare">
          <image
            class="nav-share-img"
            :src="shareIcon"
            mode="aspectFit"
          />
        </view>
      </view>
    </view>

    <view
      v-if="loading"
      class="main state-main"
      :style="{ paddingTop: contentTopPx + 'px' }"
    >
      <text class="state-txt">加载中…</text>
    </view>

    <view
      v-else-if="!detail"
      class="main state-main"
      :style="{ paddingTop: contentTopPx + 'px' }"
    >
      <text class="state-txt">{{ loadError || "暂无内容" }}</text>
      <view v-if="apiOn && loadError" class="retry-wrap">
        <view class="retry-btn" @click="retryLoad"><text>重试</text></view>
      </view>
    </view>

    <view
      v-else
      class="main"
      :style="{ paddingTop: contentTopPx + 'px' }"
    >
      <swiper
        v-if="detail.imageUrls.length > 1"
        class="hero-swiper"
        circular
        :current="heroIndex"
        :indicator-dots="true"
        indicator-color="rgba(255,255,255,0.45)"
        indicator-active-color="#ffffff"
        @change="onSwiperChange"
      >
        <swiper-item
          v-for="(url, i) in detail.imageUrls"
          :key="i"
          class="hero-swiper-item"
        >
          <view class="hero-frame" @click="previewAt(i)">
            <image class="hero-img" :src="url" mode="aspectFill" />
          </view>
        </swiper-item>
      </swiper>
      <view v-else class="hero-frame hero-frame--single" @click="previewAt(0)">
        <image
          v-if="detail.imageUrls[0]"
          class="hero-img"
          :src="detail.imageUrls[0]"
          mode="aspectFill"
        />
        <view v-else class="hero-placeholder">
          <text class="hero-placeholder-txt">暂无图片</text>
        </view>
      </view>

      <view v-if="rejectedBanner" class="reject-banner">
        <text class="reject-banner-title">未通过审核</text>
        <text class="reject-banner-reason">{{ rejectedBanner }}</text>
        <view v-if="isRejectedOwner" class="reject-banner-btn" @click="goResubmit">
          <text>修改并重新提交</text>
        </view>
      </view>

      <view class="box">
        <view class="author-row">
          <view class="author-left">
            <view class="avatar">{{ authorInitial }}</view>
            <view class="author-text">
              <text class="author-name">{{ detail.author }}</text>
              <view class="date-row">
                <image
                  class="date-ico-img"
                  :src="dateIcon"
                  mode="aspectFit"
                />
                <view class="date-txt-wrap">
                  <text class="date-txt">{{ detail.date }}</text>
                </view>
              </view>
            </view>
          </view>
          <view
            :class="['like-row', !canLikeWork ? 'like-row-off' : '']"
            @click="toggleAuthorLike"
          >
            <view :class="['follow', authorLiked ? 'follow-on' : '']">
              <image
                class="heart-sm-img"
                :src="authorLiked ? likeOnIcon : likeOffIcon"
                mode="aspectFit"
              />
              <text>{{ authorLiked ? "已点赞" : "点赞" }}</text>
            </view>
            <text class="like-count">{{ likeCount }}</text>
          </view>
        </view>

        <view class="block">
          <text class="h2">{{ detail.title }}</text>
          <view v-if="detailTags.length" class="tags">
            <text v-for="(t, i) in detailTags" :key="i" class="tag">{{ t }}</text>
          </view>
          <text class="desc">{{ detail.description }}</text>
        </view>
      </view>
    </view>
  </view>
</template>

<script setup lang="ts">
import { computed, nextTick, ref } from "vue";
import { onLoad, onShareAppMessage, onShow } from "@dcloudio/uni-app";
import { getPhotoById } from "@/data/gallery";
import { buildPhotoDetail } from "@/data/photo-detail";
import { tagsForPhotoTitle } from "@/utils/photoCardTags";
import { apiEnabled } from "@/config/api";
import {
  apiFailureMessage,
  get,
  isMpSessionExpiredError,
  post,
} from "@/utils/api/http";
import { ensureMpSession } from "@/utils/api/mpSession";
import { labelsFromCategoryKeys } from "@/utils/categoryLabels";
import { resolveMediaUrl } from "@/utils/mediaUrl";
import { setUploadResubmitWorkId } from "@/utils/uploadResubmit";
import { staticAsset } from "@/utils/staticAsset";

/** 与 GET /api/v1/works/:id 返回的 data 对齐（经 {@link normalizeWorkDetail} 归一化） */
interface WorkDetailData {
  id: number;
  userId: number;
  imageUrl: string;
  images: string[];
  title: string;
  description: string;
  likes: number;
  author: string;
  /** 发布/展示日期 Y-m-d */
  date: string;
  /** 拍摄日期 Y-m-d，有则详情页日期行优先显示 */
  shootDate: string;
  categories: string[];
  liked: boolean;
  status: string;
  rejectReason?: string;
}
const backIcon = staticAsset("back.png");
const shareIcon = staticAsset("fx.png");
const dateIcon = staticAsset("data.png");
const likeOnIcon = staticAsset("cang_on.png");
const likeOffIcon = staticAsset("cang.png");

function pick(
  obj: Record<string, unknown>,
  ...keys: string[]
): unknown {
  for (const k of keys) {
    if (!Object.prototype.hasOwnProperty.call(obj, k)) {
      continue;
    }
    const v = obj[k];
    if (v !== undefined && v !== null) {
      return v;
    }
  }
  return undefined;
}

function pickStr(obj: Record<string, unknown>, ...keys: string[]): string {
  const v = pick(obj, ...keys);
  if (v == null) {
    return "";
  }
  return String(v).trim();
}

function pickNum(obj: Record<string, unknown>, ...keys: string[]): number {
  const v = pick(obj, ...keys);
  if (v == null || v === "") {
    return 0;
  }
  const n = Number(v);
  return Number.isFinite(n) ? n : 0;
}

function pickBool(obj: Record<string, unknown>, ...keys: string[]): boolean {
  const v = pick(obj, ...keys);
  return v === true || v === 1 || v === "1";
}

function pickStrArray(obj: Record<string, unknown>, ...keys: string[]): string[] {
  for (const k of keys) {
    const v = obj[k];
    if (Array.isArray(v)) {
      return v.map((x) => String(x ?? "").trim()).filter(Boolean);
    }
  }
  return [];
}

/** 解析多图：数组 / JSON 字符串 / 类数组对象 */
function pickImageUrls(obj: Record<string, unknown>): string[] {
  const keys = ["images", "image_urls", "imageUrls", "pics", "urls"] as const;
  for (const k of keys) {
    const v = obj[k];
    if (Array.isArray(v)) {
      return v.map((x) => String(x ?? "").trim()).filter(Boolean);
    }
    if (v && typeof v === "object" && !Array.isArray(v)) {
      return Object.values(v as Record<string, unknown>)
        .map((x) => String(x ?? "").trim())
        .filter(Boolean);
    }
    if (typeof v === "string" && v.trim()) {
      try {
        const p = JSON.parse(v) as unknown;
        if (Array.isArray(p)) {
          return p.map((x) => String(x ?? "").trim()).filter(Boolean);
        }
      } catch {
        return [v.trim()];
      }
    }
  }
  return [];
}

/**
 * 接口 data 可能被包一层、或 JSON 再包成字符串、或误返回单元素列表
 */
function unwrapWorkDetailData(raw: unknown, depth = 0): Record<string, unknown> {
  if (depth > 8) {
    return {};
  }
  if (raw == null) {
    return {};
  }
  if (typeof raw === "string") {
    const t = raw.trim();
    if (!t) {
      return {};
    }
    try {
      return unwrapWorkDetailData(JSON.parse(t), depth + 1);
    } catch {
      return {};
    }
  }
  if (Array.isArray(raw)) {
    if (raw.length === 1 && raw[0] && typeof raw[0] === "object" && !Array.isArray(raw[0])) {
      return raw[0] as Record<string, unknown>;
    }
    return {};
  }
  if (typeof raw !== "object") {
    return {};
  }
  const o = raw as Record<string, unknown>;
  // 误传整包 { code, msg, data }（code 偶发为字符串 "0"）
  const nested = o.data;
  const codeOk = o.code === 0 || o.code === "0";
  if (
    codeOk &&
    nested != null &&
    typeof nested === "object" &&
    !Array.isArray(nested)
  ) {
    return unwrapWorkDetailData(nested, depth + 1);
  }
  const keys = Object.keys(o);
  if (keys.length === 1 && keys[0] === "data" && o.data && typeof o.data === "object" && !Array.isArray(o.data)) {
    return unwrapWorkDetailData(o.data, depth + 1);
  }
  // { list: [ 作品 ] } 与列表接口结构混用
  const list = o.list;
  if (Array.isArray(list) && list.length === 1 && list[0] && typeof list[0] === "object" && !Array.isArray(list[0])) {
    return unwrapWorkDetailData(list[0], depth + 1);
  }
  return o;
}

function normalizeDateYmd(s: string): string {
  const t = s.trim();
  if (!t) {
    return "";
  }
  if (/^\d{4}-\d{2}-\d{2}/.test(t)) {
    return t.slice(0, 10);
  }
  return t;
}

/** 兼容 camelCase / snake_case、数字转字符串等，避免详情显示错字段或点赞数为 0 */
function normalizeWorkDetail(raw: unknown, fallbackId: number): WorkDetailData {
  let o = unwrapWorkDetailData(raw);
  const topList = o.list;
  if (Array.isArray(topList) && topList.length >= 1) {
    const first = topList[0];
    if (
      topList.length === 1 &&
      first &&
      typeof first === "object" &&
      !Array.isArray(first)
    ) {
      o = first as Record<string, unknown>;
    } else if (fallbackId > 0) {
      const hit = topList.find((x) => {
        if (!x || typeof x !== "object" || Array.isArray(x)) {
          return false;
        }
        return pickNum(x as Record<string, unknown>, "id") === fallbackId;
      });
      if (hit && typeof hit === "object" && !Array.isArray(hit)) {
        o = hit as Record<string, unknown>;
      }
    }
  }
  const id = pickNum(o, "id") || fallbackId;
  const imgs = pickImageUrls(o);
  return {
    id,
    userId: pickNum(o, "userId", "user_id"),
    imageUrl: pickStr(o, "imageUrl", "image_url", "cover_image", "coverImage"),
    images: imgs.length ? imgs : pickStrArray(o, "images", "image_urls"),
    title: pickStr(o, "title", "name"),
    description: pickStr(o, "description", "desc", "content"),
    likes: pickNum(o, "likes", "likes_count"),
    author: pickStr(o, "author", "author_name", "nickname"),
    date: normalizeDateYmd(pickStr(o, "date", "publish_date", "publishDate", "create_time", "createTime")),
    shootDate: normalizeDateYmd(pickStr(o, "shootDate", "shoot_date")),
    categories: pickStrArray(o, "categories", "category"),
    liked: pickBool(o, "liked"),
    status: pickStr(o, "status"),
    rejectReason: pickStr(o, "rejectReason", "reject_reason"),
  };
}

interface PhotoDetailVm {
  id: number;
  imageUrls: string[];
  title: string;
  description: string;
  author: string;
  date: string;
  likes: number;
  categoryLabels: string[];
  /** approved | pending | rejected */
  workStatus: string;
  /** 接口仅作者且已拒绝时返回 */
  rejectReason: string;
}

const statusBarHeight = ref(20);
const contentTopPx = ref(64);
const photoId = ref(0);
const authorLiked = ref(false);
const likeCount = ref(0);
const loading = ref(true);
const loadError = ref("");
const detail = ref<PhotoDetailVm | null>(null);
const heroIndex = ref(0);

const apiOn = computed(() => apiEnabled());

const rejectedBanner = computed(() => {
  const d = detail.value;
  if (!d || d.workStatus !== "rejected") {
    return "";
  }
  return d.rejectReason.trim() || "请根据审核意见修改后重新提交";
});

/** 已拒绝作品仅作者能加载到详情（他人 403），能进入本页即视为可重新提交 */
const isRejectedOwner = computed(() => detail.value?.workStatus === "rejected");

const canLikeWork = computed(() => detail.value?.workStatus === "approved");

const authorInitial = computed(() => {
  const a = detail.value?.author?.trim() || "用";
  return a.charAt(0) || "用";
});

const detailTags = computed(() => {
  const d = detail.value;
  if (!d) return [];
  if (d.categoryLabels.length) {
    return d.categoryLabels;
  }
  return tagsForPhotoTitle(d.title);
});

function mapApiToVm(id: number, r: WorkDetailData): PhotoDetailVm {
  const displayDate = r.shootDate || r.date || "";
  const raw: string[] = [];
  const imgs = Array.isArray(r.images) ? r.images : [];
  if (imgs.length) {
    for (const u of imgs) {
      const t = String(u || "").trim();
      if (t) {
        raw.push(t);
      }
    }
  }
  if (!raw.length && r.imageUrl) {
    const t = String(r.imageUrl).trim();
    if (t) {
      raw.push(t);
    }
  }
  const urls: string[] = [];
  const seen = new Set<string>();
  for (const u of raw) {
    const s = resolveMediaUrl(u);
    if (s && !seen.has(s)) {
      seen.add(s);
      urls.push(s);
    }
  }
  const st = (r.status || "").toLowerCase().trim();
  return {
    id,
    imageUrls: urls.length ? urls : [""],
    title: r.title || "",
    description: r.description || "",
    author: r.author || "用户",
    date: displayDate,
    likes: r.likes,
    categoryLabels: labelsFromCategoryKeys(r.categories ?? []),
    workStatus: st || "approved",
    rejectReason: (r.rejectReason || "").trim(),
  };
}

function mapLocalToVm(id: number): PhotoDetailVm | null {
  const p = getPhotoById(id);
  if (!p) {
    return null;
  }
  const d = buildPhotoDetail(p);
  return {
    id: d.id,
    imageUrls: [d.imageUrl],
    title: d.title,
    description: d.description,
    author: d.author,
    date: d.date,
    likes: d.likes,
    categoryLabels: tagsForPhotoTitle(d.title),
    workStatus: "approved",
    rejectReason: "",
  };
}

async function loadPage() {
  const id = photoId.value;
  heroIndex.value = 0;
  loading.value = true;
  loadError.value = "";
  detail.value = null;

  if (id <= 0) {
    loading.value = false;
    loadError.value = "缺少作品信息";
    return;
  }

  try {
    if (apiEnabled()) {
      // 已通过作品对游客开放，不必等换票；待审核仅作者可看时依赖本地已有 Bearer
      const res = await get<unknown>(`/api/v1/works/${id}`);
      const r = normalizeWorkDetail(res.data, id);
      detail.value = mapApiToVm(id, r);
      likeCount.value = r.likes;
      authorLiked.value = r.liked;
      return;
    }

    const local = mapLocalToVm(id) ?? mapLocalToVm(2);
    if (!local) {
      loadError.value = "作品不存在";
      return;
    }
    detail.value = local;
    likeCount.value = local.likes;
    authorLiked.value = false;
  } catch (e) {
    if (isMpSessionExpiredError(e)) {
      return;
    }
    let msg = "加载失败";
    if (e && typeof e === "object" && "msg" in e && typeof (e as { msg: string }).msg === "string") {
      msg = (e as { msg: string }).msg;
    } else if (e instanceof Error && e.message) {
      msg = e.message;
    }
    loadError.value = msg;
    uni.showToast({ title: msg.length > 40 ? msg.slice(0, 40) + "…" : msg, icon: "none" });
  } finally {
    loading.value = false;
  }
}

function retryLoad() {
  void loadPage();
}

onShareAppMessage(() => {
  const cover = detail.value?.imageUrls?.[0];
  const msg: {
    title: string;
    path: string;
    imageUrl?: string;
  } = {
    title: detail.value?.title ?? "稻城亚丁作品墙",
    path: `/pages/photo-detail/photo-detail?id=${photoId.value}`,
  };
  if (cover && !cover.startsWith("/static/")) {
    msg.imageUrl = cover;
  }
  return msg;
});

function parseQueryId(opts?: Record<string, unknown>): number {
  const raw = opts?.id;
  const n =
    raw != null && String(raw).trim() !== "" ? Number(raw) : Number.NaN;
  return Number.isInteger(n) && n > 0 ? n : 0;
}

function applyRouteIdAndLoad(opts?: Record<string, unknown>) {
  const info = uni.getSystemInfoSync();
  statusBarHeight.value = info.statusBarHeight || 20;
  contentTopPx.value = statusBarHeight.value + uni.upx2px(88);

  const n = parseQueryId(opts);
  if (apiEnabled() && n <= 0) {
    photoId.value = 0;
    loading.value = false;
    loadError.value = "缺少作品信息";
    return;
  }
  photoId.value = n > 0 ? n : 2;
  void loadPage();
}

onLoad((q) => {
  applyRouteIdAndLoad(q as Record<string, unknown> | undefined);
});

function syncDetailIdFromRoute(): void {
  if (!apiEnabled()) {
    return;
  }
  try {
    const pages = getCurrentPages();
    const cur = pages[pages.length - 1] as {
      options?: Record<string, string | undefined>;
    };
    const nid = parseQueryId(cur?.options as Record<string, unknown>);
    if (nid > 0 && nid !== photoId.value) {
      photoId.value = nid;
      void loadPage();
    }
  } catch {
    /* ignore */
  }
}

/** H5 等：可能只触发 onShow；options 偶发首帧为空，nextTick 再读一次 */
onShow(() => {
  syncDetailIdFromRoute();
  void nextTick(() => {
    syncDetailIdFromRoute();
  });
});

function goBack() {
  uni.navigateBack({ delta: 1 });
}

async function toggleAuthorLike() {
  if (!canLikeWork.value) {
    uni.showToast({ title: "仅已通过审核的作品可点赞", icon: "none" });
    return;
  }
  if (apiEnabled()) {
    try {
      await ensureMpSession();
      const res = await post<{ liked: boolean; likes: number }>(
        `/api/v1/works/${photoId.value}/like`,
        {}
      );
      const d = res.data as { liked?: unknown; likes?: unknown };
      authorLiked.value = !!d.liked;
      const n = Number(d.likes);
      const nextLikes = Number.isFinite(n) ? n : 0;
      likeCount.value = nextLikes;
      if (detail.value) {
        detail.value = { ...detail.value, likes: nextLikes };
      }
    } catch (e) {
      if (isMpSessionExpiredError(e)) {
        return;
      }
      uni.showToast({
        title: apiFailureMessage(e, "操作失败"),
        icon: "none",
      });
    }
    return;
  }
  if (authorLiked.value) {
    likeCount.value -= 1;
  } else {
    likeCount.value += 1;
  }
  authorLiked.value = !authorLiked.value;
  if (detail.value) {
    detail.value = { ...detail.value, likes: likeCount.value };
  }
}

function onShare() {
  uni.showToast({ title: "请点击右上角分享", icon: "none" });
}

function onSwiperChange(e: { detail?: { current?: number } }) {
  const c = e.detail?.current;
  if (typeof c === "number" && c >= 0) {
    heroIndex.value = c;
  }
}

function previewAt(index: number) {
  const urls = (detail.value?.imageUrls ?? []).filter(Boolean);
  if (!urls.length) {
    return;
  }
  const cur = urls[Math.min(Math.max(0, index), urls.length - 1)];
  uni.previewImage({
    current: cur,
    urls,
  });
}

function goResubmit() {
  const id = photoId.value > 0 ? photoId.value : (detail.value?.id ?? 0);
  if (id <= 0) {
    uni.showToast({ title: "作品信息异常，请返回重试", icon: "none" });
    return;
  }
  setUploadResubmitWorkId(id);
  uni.switchTab({
    url: "/pages/upload/upload",
    fail: () => {
      uni.showToast({ title: "跳转失败，请稍后重试", icon: "none" });
    },
  });
}
</script>

<style scoped>
.page {
  min-height: 100vh;
  padding-bottom: env(safe-area-inset-bottom);
  background: #fff;
}

.main {
  box-sizing: border-box;
  width: 100%;
}

.nav {
  position: fixed;
  top: 0;
  right: 0;
  left: 0;
  z-index: 50;
  background: rgba(255, 255, 255, 0.96);
  border-bottom: 1rpx solid #e5e7eb;
}

.nav-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 88rpx;
}

.nav-btn {
  display: flex;
  width: 72rpx;
  height: 72rpx;
  align-items: center;
  justify-content: center;
}

.nav-back-img {
  width: 44rpx;
  height: 44rpx;
}

.nav-share-img {
  width: 44rpx;
  height: 44rpx;
}

.nav-title {
  font-size: 30rpx;
  color: #111827;
}

.state-main {
  box-sizing: border-box;
  min-height: 50vh;
  padding: 48rpx 32rpx;
}

.state-txt {
  display: block;
  font-size: 28rpx;
  line-height: 1.6;
  color: #6b7280;
  text-align: center;
}

.retry-wrap {
  display: flex;
  justify-content: center;
  margin-top: 40rpx;
}

.retry-btn {
  padding: 20rpx 56rpx;
  font-size: 28rpx;
  color: #fff;
  border-radius: 999rpx;
  background: #4a90e2;
}

.hero-swiper {
  display: block;
  width: 100%;
  height: 560rpx;
}

.hero-swiper-item {
  box-sizing: border-box;
  width: 100%;
  height: 560rpx;
  overflow: hidden;
}

/* 小程序内 swiper 子项需固定高度容器，image 再铺满 */
.hero-frame {
  width: 100%;
  height: 560rpx;
  overflow: hidden;
  background: #f3f4f6;
}

.hero-frame--single {
  display: block;
}

.reject-banner {
  margin: 0 32rpx 24rpx;
  padding: 24rpx 28rpx;
  border-radius: 20rpx;
  background: #fef2f2;
  border: 1rpx solid #fecaca;
}

.reject-banner-title {
  display: block;
  margin-bottom: 12rpx;
  font-size: 28rpx;
  font-weight: 600;
  color: #b91c1c;
}

.reject-banner-reason {
  display: block;
  font-size: 26rpx;
  line-height: 1.55;
  color: #7f1d1d;
  white-space: pre-wrap;
  word-break: break-word;
}

.reject-banner-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: 24rpx;
  padding: 20rpx;
  font-size: 28rpx;
  color: #fff;
  border-radius: 16rpx;
  background: #4a90e2;
}

.like-row-off {
  opacity: 0.45;
}

.hero-img {
  display: block;
  width: 100%;
  height: 560rpx;
}

.hero-placeholder {
  display: flex;
  width: 100%;
  height: 560rpx;
  align-items: center;
  justify-content: center;
  background: #f3f4f6;
}

.hero-placeholder-txt {
  font-size: 26rpx;
  color: #9ca3af;
}

.box {
  max-width: 750rpx;
  padding: 0 32rpx;
  margin: 0 auto;
}

.author-row {
  display: flex;
  align-items: center;
  padding: 32rpx 0;
  border-bottom: 1rpx solid #f3f4f6;
}

.author-left {
  display: flex;
  min-width: 0;
  flex: 1;
  align-items: center;
  margin-right: 16rpx;
}

.author-left .avatar {
  margin-right: 24rpx;
  flex-shrink: 0;
}

.author-text {
  min-width: 0;
  flex: 1;
}

.avatar {
  display: flex;
  width: 80rpx;
  height: 80rpx;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: #4a90e2;
  color: #fff;
  font-size: 32rpx;
}

.author-name {
  display: block;
  overflow: hidden;
  font-size: 28rpx;
  color: #111827;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.date-row {
  display: flex;
  align-items: center;
  gap: 8rpx;
  margin-top: 8rpx;
  min-height: 26rpx;
}

.date-ico-img {
  display: block;
  width: 26rpx;
  height: 26rpx;
  flex-shrink: 0;
}

.date-txt-wrap {
  display: flex;
  align-items: center;
  min-height: 26rpx;
}

.date-txt {
  font-size: 22rpx;
  line-height: 1.2;
  color: #6b7280;
}

.like-row {
  display: flex;
  flex-shrink: 0;
  align-items: center;
  margin-left: auto;
}

.like-row .follow {
  margin-right: 16rpx;
}

.like-count {
  font-size: 24rpx;
  color: #6b7280;
}

.follow {
  display: flex;
  align-items: center;
  gap: 8rpx;
  padding: 12rpx 28rpx;
  font-size: 24rpx;
  color: #4a90e2;
  border: 1rpx solid #4a90e2;
  border-radius: 999rpx;
}

.follow-on {
  color: #ef4444;
  border-color: #ef4444;
  background: #fef2f2;
}

.heart-sm-img {
  width: 26rpx;
  height: 26rpx;
  flex-shrink: 0;
}

.block {
  padding: 32rpx 0;
  border-bottom: 1rpx solid #f3f4f6;
}

.h2 {
  display: block;
  font-size: 40rpx;
  line-height: 1.35;
  color: #111827;
  word-break: break-word;
}

.tags {
  display: flex;
  flex-wrap: wrap;
  gap: 12rpx;
  margin-top: 16rpx;
}

.tag {
  padding: 8rpx 20rpx;
  font-size: 22rpx;
  color: #4a90e2;
  border: 1rpx solid rgba(74, 144, 226, 0.35);
  border-radius: 999rpx;
  background: rgba(74, 144, 226, 0.12);
}

.desc {
  display: block;
  margin-top: 24rpx;
  font-size: 28rpx;
  line-height: 1.65;
  color: #374151;
  word-break: break-word;
  white-space: pre-wrap;
}
</style>
