Vue3+TypeScript+Django Rest Framework 搭建個人博客(三):博客管理后臺(2)

一個完整的網(wǎng)站都是有前臺和管理后臺組成的,前臺用來給真正的用戶瀏覽和使用焙畔,后臺用來給管理員管理網(wǎng)站內(nèi)容掸读,配置各種功能和數(shù)據(jù)等。博客的管理后臺就是用來承載創(chuàng)建博客宏多,發(fā)布博客儿惫,查看留言,管理博客用戶這些功能的子系統(tǒng)伸但。

大家好肾请,我是落霞孤鶩,上一篇我們已經(jīng)實現(xiàn)了管理后臺的后端接口部分更胖,這一章我們開始搭建博客的管理后臺的前端铛铁,實現(xiàn)對博客網(wǎng)站的管理功能。

一却妨、前端界面開發(fā)

一個管理后臺的功能饵逐,一般都需要從最基礎(chǔ)的業(yè)務(wù)對象的管理開始,在我們的博客網(wǎng)站上彪标,業(yè)務(wù)對象間的依賴依次是用戶倍权、標簽、分類捐下、文章账锹、評論、點贊坷襟、留言奸柬、首頁統(tǒng)計。

基于這個依賴關(guān)系婴程,我們的后臺管理功能也按照這樣的邏輯順序進行構(gòu)建廓奕。然后在構(gòu)建每一個業(yè)務(wù)對象的管理頁面時,按照Type档叔、API桌粉、ComponentView衙四、Route順序進行組織和代碼編寫铃肯。

src/views下創(chuàng)建兩個文件夾adminclient,并把上一個章節(jié)中創(chuàng)建的Login.vue 移動到admin文件夾传蹈,把Home.vue 文件移動到client下押逼。

1.1 菜單管理

1.1.1 Admin.vue

管理后臺的功能需要一個獨立的菜單導(dǎo)航功能步藕,因此在src/views/admin下新增Admin.vue文件,用于完成左側(cè)的菜單導(dǎo)航挑格,代碼如下:

<template>
  <div class="body">
    <div class="menu">
      <el-menu :default-active="state.activePath" :router="true">
        <el-menu-item index="AdminDashboard" route="/admin/dashboard"><i class="el-icon-s-home"></i> Dashboard
        </el-menu-item>
        <el-menu-item index="ArticleManagement" route="/admin/article"><i class="el-icon-s-order"></i> 文章
        </el-menu-item>
        <el-menu-item index="TagManagement" route="/admin/tag"><i class="el-icon-collection-tag"></i> 標簽</el-menu-item>
        <el-menu-item index="CommentManagement" route="/admin/comment"><i class="el-icon-chat-line-round"></i> 評論
        </el-menu-item>
        <el-menu-item index="UserManagement" route="/admin/user"><i class="el-icon-user"></i> 用戶</el-menu-item>
      </el-menu>
    </div>
    <div class="view">
      <router-view/>
    </div>
  </div>
</template>

<script>
import {defineComponent, reactive} from "vue";
import {useRoute} from "vue-router";

export default defineComponent({
  name: "Admin",
  setup() {
    const state = reactive({
      activePath: '',
    });
    const route = useRoute()
    if (route.name === 'Dashboard') {
      state.activePath = 'AdminDashboard'
    } else {
      state.activePath = route.name;
    }
    return {
      state,
    }
  },
});
</script>


<style lang="less" scoped>
.body {
  width: 100%;
  height: 100%;
  box-sizing: border-box;
  display: flex;
}

.user {
  font-size: 20px;
}

.menu {
  width: 200px;
}

.view {
  width: calc(100% - 200px);
  padding: 24px;
}

.el-menu {
  height: 100%;
}

</style>

3.1.2 Dashboard.vue

為了接下來的開發(fā)能很好的開展咙冗,我們先處理管理后臺的默認頁面Dashboard, 在src/views/admin 下創(chuàng)建文件Dashboard.vue漂彤,編寫代碼:

<template>
    <h3>Dashboard</h3>
</template>

<script lang="ts">
import { defineComponent, reactive } from "vue";
export default defineComponent({
   name: 'Dashboard', 
})
</script>

3.1.3 添加路由

src/router/index.ts下調(diào)整代碼如下:

import {createRouter, createWebHistory, RouteRecordRaw} from "vue-router";
import Home from "../views/client/Home.vue";

const routes: Array<RouteRecordRaw> = [
    {
        path: "/",
        name: "Home",
        component: Home,
        meta: {}
    },
    {
        path: "/login/",
        name: "Login",
        component: () =>
            import("../views/admin/Login.vue")
    },
    {
        path: '/admin',
        name: 'Admin',
        component: () => import("../views/admin/Admin.vue"),
        children: [
            {
                path: '/admin/',
                name: 'Dashboard',
                component: () => import("../views/admin/Dashboard.vue"),
            },
            {
                path: '/admin/dashboard',
                name: 'AdminDashboard',
                component: () => import("../views/admin/Dashboard.vue"),
            },
        ]
    },
]

const router = createRouter({
    history: createWebHistory(import.meta.env.BASE_URL),
    routes,
});


export default router;

1.2 用戶管理

1.2.1 Type

在我們處理登錄和注冊的時候雾消,已經(jīng)完成了用戶的類型定義,也即User 的interface定義挫望,這里增加所有返回結(jié)果的定義立润,用于管理接口返回的數(shù)據(jù)結(jié)構(gòu)。在src/types/index.ts文件中代碼如下:

export interface User {
    id: number,
    username: string,
    email: string,
    avatar: string | any,
    nickname: string | any,
    is_active?: any,
    is_superuser?: boolean,
    created_at?: string,
}


export interface ResponseData {
    count: number;
    results?: any;
    detail?: string;
}

1.2.2 API

這里要編寫用戶管理相關(guān)的接口媳板,列表查詢范删、啟用、禁用拷肌、詳情查看。在src/api/service.ts編寫如下代碼:

import { User, ResponseData } from "../types"


export function getUserDetail(userId: number) {
    return request({
        url: '/user/' + userId + '/',
        method: 'get',
    }) as unknown as User
}

export function saveUser(method: string, data: User) {
    // @ts-ignore
    return request({
        url: '/user/' + data.id + '/',
        method,
        data,
    }) as unknown as ResponseData
}

1.2.3 Component

在查看用戶詳情時旨巷,我們需要一個抽屜巨缘,展示用戶的詳細信息,因此在src/components下創(chuàng)建文件UserDetail.vue采呐,編寫代碼如下:

<template>
  <el-drawer
      v-model="state.visible"
      :before-close="handleClose"
      direction="rtl"
      size="500px"
      title="用戶詳情"
      @opened="handleSearch"
  >
    <el-descriptions :column="1" border class="detail" >
      <el-descriptions-item label="用戶名">{{ state.user.username }}</el-descriptions-item>
      <el-descriptions-item label="角色">{{ state.user.role }}</el-descriptions-item>
      <el-descriptions-item label="狀態(tài)">{{ state.user.is_active }}</el-descriptions-item>
      <el-descriptions-item label="郵箱">{{ state.user.email }}</el-descriptions-item>
      <el-descriptions-item label="創(chuàng)建時間">{{ state.user.created_at }}</el-descriptions-item>
      <el-descriptions-item label="最后登錄時間">{{ state.user.last_login }}</el-descriptions-item>
    </el-descriptions>
  </el-drawer>
</template>

<script lang="ts">
import {defineComponent, reactive} from "vue";
import {User} from "../types";
import {getUserDetail} from "../api/service";

export default defineComponent({
  name: "UserDetail",
  props: {
    visible: {
      type: Boolean,
      require: true,
    },
    userId: {
      type: Number,
      require: true,
    },
    loading: {
      type: Boolean,
      require: true,
    }
  },
  emits: ["close",],
  watch: {
    '$props.visible': {
      async handler(val: Boolean, oldVal: Boolean) {
        if (val !== oldVal) {
          this.state.visible = val
        }
      }
    }
  },
  setup(props) {
    const state = reactive({
      visible: props.visible as Boolean,
      user: {} as User,
    });

    return {
      state,
    }
  },
  methods: {
    handleClose(isOk: Boolean) {
      this.$emit("close", {
        user: this.state.user,
        isOk,
      })
    },
    async handleSearch() {
      this.state.user = await getUserDetail(this.$props.userId)
    }
  }
})
</script>

<style scoped>
.detail {
  padding: 24px;
  margin-top: -12px;
  border-top: #eeeeee 1px solid;
}
</style>

1.2.4 View

在用戶管理中若锁,我們通過一個表格,分頁展示所有的用戶信息斧吐,并通過表格的操作列又固,提供查看詳情、啟用煤率、禁用功能仰冠。

src/utils/index.ts 下增加方法timestampToTime

export function timestampToTime(timestamp: Date | any, dayMinSecFlag: boolean) {    const date = new Date(timestamp);    const Y = date.getFullYear() + "-";    const M =        (date.getMonth() + 1 < 10            ? "0" + (date.getMonth() + 1)            : date.getMonth() + 1) + "-";    const D =        date.getDate() < 10 ? "0" + date.getDate() + " " : date.getDate() + " ";    const h =        date.getHours() < 10 ? "0" + date.getHours() + ":" : date.getHours() + ":";    const m =        date.getMinutes() < 10            ? "0" + date.getMinutes() + ":"            : date.getMinutes() + ":";    const s =        date.getSeconds() < 10 ? "0" + date.getSeconds() : date.getSeconds();    if (!dayMinSecFlag) {        return Y + M + D;    }    return Y + M + D + h + m + s;}

src/views/admin下新增文件User.vue,引用UserDetail組件蝶糯,具體代碼如下:

<template>
  <div>
    <div>
      <el-form :inline="true" :model="state.params" class="demo-form-inline">
        <el-form-item label="名稱">
          <el-input v-model="state.params.name" placeholder="賬號"/>
        </el-form-item>
        <el-form-item label="狀態(tài)">
          <el-select v-model="state.params.is_active" placeholder="請選擇">
            <el-option :value="1" label="生效"/>
            <el-option :value="0" label="禁用"/>
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button :loading="state.isLoading" type="primary" @click="handleSearch">查詢</el-button>
        </el-form-item>
      </el-form>
    </div>
    <div>
      <el-table ref="userTable" :data="state.userList" :header-cell-style="{background:'#eef1f6',color:'#606266'}"
                stripe>
        <el-table-column type="selection" width="55"/>
        <el-table-column label="ID" prop="id" width="80"/>
        <el-table-column label="賬號" prop="username" width="200"/>
        <el-table-column label="昵稱" prop="nickname" width="200"/>
        <el-table-column label="狀態(tài)" prop="is_active"/>
        <el-table-column :formatter="datetimeFormatter" label="注冊時間" prop="created_at"/>
        <el-table-column label="操作">
          <template #default="scope">
            <el-popconfirm v-if="scope.row.is_active" cancelButtonText='取消' confirmButtonText='禁用' icon="el-icon-info"
                           iconColor="red" title="確定禁用該用戶嗎洋只?" @confirm="disableUser(scope.$index,scope.row)">
              <template #reference>
                <el-button size="small" type="text">
                  禁用
                </el-button>
              </template>
            </el-popconfirm>
            <el-button v-if="!scope.row.is_active" size="small" type="text"
                       @click.native.prevent="enableUser(scope.$index, scope.row)">
              啟用
            </el-button>
            <el-button size="small" type="text"
                       @click.native.prevent="showUserDetail(scope.row)">
              詳情
            </el-button>
          </template>
        </el-table-column>
      </el-table>

    </div>
    <div class="pagination">
      <el-pagination :page-size="10" :total="state.total" background
                     layout="prev, pager, next"></el-pagination>
    </div>
  </div>
  <UserDetail
      :user-id="state.userId"
      :visible="state.showDialog"
      @close="state.showDialog = false"
  />
</template>

<script lang="ts">
import {defineComponent, reactive} from "vue";
import {ResponseData, User} from "../../types";
import {ElMessage} from "element-plus";
import {timestampToTime} from "../../utils";
import {getUserList, saveUser} from "../../api/service";
import UserDetail from "../../components/UserDetail.vue";

export default defineComponent({
  name: "User",
  components: {UserDetail},
  setup: function () {
    const state = reactive({
      userList: [] as Array<User>,
      params: {
        name: '',
        role: 'Reader',
        is_active: undefined,
        page: 1,
        page_size: 10,
      },
      isLoading: false,
      total: 0,
      showDialog: false,
      userId: 0,
      saveLoading: false,
    });

    const handleSearch = async (): Promise<void> => {
      state.isLoading = true;
      try {
        const data: ResponseData = await getUserList(state.params);
        state.isLoading = false;
        state.userList = data.results;
        state.total = data.count
      } catch (e) {
        console.error(e)
        state.isLoading = false;
      }
    };

    const disableUser = async (index: number, row: User) => {
      await saveUser('patch', {id: row.id, is_active: false} as User);
      ElMessage({
        message: "禁用成功!",
        type: "success",
      });
      await handleSearch()
    }

    const enableUser = async (index: number, row: User) => {
      await saveUser('patch', {id: row.id, is_active: true} as User);
      ElMessage({
        message: "啟用成功昼捍!",
        type: "success",
      });
      await handleSearch()
    }

    const datetimeFormatter = (row: User, column: number, cellValue: string, index: number) => {
      return timestampToTime(cellValue, true);
    }

    handleSearch()
    return {
      state,
      handleSearch,
      datetimeFormatter,
      disableUser,
      enableUser,
    }
  },
  methods: {
    showUserDetail(row: User) {
      this.state.userId = row.id
      this.state.showDialog = true;
    },
  }
})
</script>

<style scoped>
.pagination {
  text-align: right;
  margin-top: 12px;
}
</style>

1.2.5 Router

有了一個新的頁面识虚,我們需要定義route來完成路由跳轉(zhuǎn)。在src/route/index.ts 文件中編寫如下代碼:

import {createRouter, createWebHistory, RouteRecordRaw} from "vue-router";
import Home from "../views/client/Home.vue";

const routes: Array<RouteRecordRaw> = [
    {
        path: "/",
        name: "Home",
        component: Home,
        meta: {}
    },
    {
        path: "/login/",
        name: "Login",
        component: () =>
            import("../views/admin/Login.vue")
    },
    {
        path: '/admin',
        name: 'Admin',
        component: () => import("../views/admin/Admin.vue"),
        children: [
            {
                path: '/admin/',
                name: 'Dashboard',
                component: () => import("../views/admin/Dashboard.vue"),
            },
            {
                path: '/admin/dashboard',
                name: 'AdminDashboard',
                component: () => import("../views/admin/Dashboard.vue"),
            },
            {
                path: '/admin/user',
                name: 'UserManagement',
                component: () => import("../views/admin/User.vue"),
            },
        ]
    },
]

const router = createRouter({
    history: createWebHistory(import.meta.env.BASE_URL),
    routes,
});


export default router;

1.3 標簽管理

主要是為了方便靈活的給文章標記類型妒茬,所以才有標簽管理担锤,標簽的屬性很簡單,就是一個名稱乍钻。

1.3.1 Type

src/types/index.ts文件中增加代碼如下:

export interface Tag {
    id: number,
    name: string,
    created_at: string,
    modified_at: string,
}

export interface TagList {
    count: number,
    results: Array<Tag> | any
}

1.3.2 API

這里要編寫標簽管理相關(guān)的接口肛循,列表查詢铭腕、新增、修改育拨、刪除谨履。在src/api/service.ts編寫如下代碼:

export function getTagList(params: any) {
    return request({
        url: '/tag/',
        method: 'get',
        params,
    }) as unknown as TagList
}

export function saveTag(method: string, data: Tag) {
    let url = '/tag/'
    if (['put', 'patch'].includes(method)) {
        url += data.id + '/'
    }
    // @ts-ignore
    return request({
        url,
        method,
        data,
    }) as unknown as ResponseData
}

export function addTag(data: Tag) {
    return request({
        url: '/tag/',
        method: 'post',
        data,
    }) as unknown as ResponseData
}

export function deleteTag(id: number) {
    return request({
        url: '/tag/' + id + '/',
        method: 'delete',
    }) as unknown as ResponseData
}

1.3.3 Component

提供一個新增和修改標簽的彈框組件,因此在src/components下創(chuàng)建文件TagEditDialog.vue熬丧,編寫代碼如下:

<template>
  <el-dialog v-model="state.visible" :title="state.title" @close="handleClose(false)" width="440px" >
    <el-form size="medium" label-suffix=":" class="form">
      <el-form-item label="名稱" label-width="80px">
        <el-input v-model="state.name" autocomplete="off" size=""></el-input>
      </el-form-item>
    </el-form>
    <template #footer>
    <span class="dialog-footer">
      <el-button @click="handleClose(false)">取 消</el-button>
      <el-button :loading="loading" type="primary" @click="handleClose(true)">確 定</el-button>
    </span>
    </template>
  </el-dialog>
</template>

<script lang="ts">
import {defineComponent, PropType, reactive} from "vue";
import {Tag} from "../types";

export default defineComponent({
  name: "TagEditDialog",
  props: {
    visible: {
      type: Boolean,
      require: true,
    },
    tag: {
      type: Object as PropType<Tag>,
      require: true,
    },
    loading: {
      type: Boolean,
      require: true,
    }
  },
  emits: ["close",],
  watch: {
    '$props.visible': {
      handler(val: Boolean, oldVal: Boolean) {
        if (val !== oldVal) {
          this.state.visible = val
        }
        if (val) {
          this.state.name = this.$props.tag.name
          this.state.title = this.$props.tag.id ? '修改標簽' : '新增標簽'
        }
      }
    }
  },
  setup(props) {
    const state = reactive({
      visible: props.visible as Boolean,
      //@ts-ignore
      name: '',
      //@ts-ignore
      title: ''
    });

    return {
      state,
    }
  },
  methods: {
    handleClose(isOk: Boolean) {
      this.$emit("close", {
        obj: {
          //@ts-ignore
          id: this.$props.tag.id,
          name: this.state.name
        },
        isOk,
      })
    }
  }
})
</script>

<style scoped>
.form{
  padding-right: 24px;
}

</style>

1.3.4 View

通過表格管理標簽笋粟,實現(xiàn)對標簽的新增,修改析蝴,刪除和列表查看害捕,在src/views/admin下新增文件Tag.vue文件,編寫如下代碼:

<template>
  <div>
    <div>
      <el-form :inline="true" :model="state.params" class="demo-form-inline">
        <el-form-item label="名稱">
          <el-input v-model="state.params.name" placeholder="名稱" />
        </el-form-item>
        <el-form-item>
          <el-button
            :loading="state.isLoading"
            type="primary"
            @click="handleSearch"
            >查詢</el-button
          >
        </el-form-item>
      </el-form>
    </div>
    <div class="button-container">
      <el-button
        :loading="state.isLoading"
        type="primary"
        @click="showAddDialog"
        ><i class="el-icon-plus" /> 新 增
      </el-button>
    </div>
    <div>
      <el-table
        ref="tagTable"
        :data="state.tagList"
        :header-cell-style="{ background: '#eef1f6', color: '#606266' }"
        stripe
      >
        <el-table-column type="selection" width="55" />
        <el-table-column label="ID" prop="id" width="80" />
        <el-table-column label="名稱" prop="name" width="200" />
        <el-table-column
          :formatter="datetimeFormatter"
          label="修改時間"
          prop="modified_at"
        />
        <el-table-column fixed="right" label="操作" width="120">
          <template #default="scope">
            <el-popconfirm
              cancelButtonText="取消"
              confirmButtonText="刪除"
              icon="el-icon-info"
              iconColor="red"
              title="確定刪除系列嗎闷畸?"
              @confirm="deleteObject(scope.$index, scope.row)"
            >
              <template #reference>
                <el-button size="small" type="text"> 刪除 </el-button>
              </template>
            </el-popconfirm>
            <el-button
              size="small"
              type="text"
              @click.prevent="showEditDialog(scope.$index, scope.row)"
            >
              編輯
            </el-button>
          </template>
        </el-table-column>
      </el-table>
    </div>
    <div class="pagination">
      <el-pagination
        :page-size="10"
        :total="state.total"
        background
        layout="prev, pager, next"
      ></el-pagination>
    </div>
  </div>
  <TagEditDialog
    :loading="state.saveLoading"
    :tag="state.tag"
    :visible="state.showDialog"
    @close="handleCloseDialog"
  />
</template>

<script lang="ts">
import { defineComponent, reactive } from "vue";
import { ResponseData, Tag } from "../../types";
import { addTag, deleteTag, getTagList, saveTag } from "../../api/service";
import { timestampToTime } from "../../utils";
import { ElMessage } from "element-plus";
import TagEditDialog from "../../components/TagEditDialog.vue";
import { useRoute } from "vue-router";

export default defineComponent({
  name: "Tag",
  components: { TagEditDialog },
  watch: {
    "$route.path": {
      handler(val, oldVal) {
        if (val !== oldVal && ["/admin/tag"].includes(val)) this.handleSearch();
      },
      deep: true,
    },
  },
  setup: function () {
    const route = useRoute();
    const state = reactive({
      tagList: [] as Array<Tag>,
      params: {
        name: undefined,
        page: 1,
        page_size: 10,
      },
      isLoading: false,
      total: 0,
      showDialog: false,
      tag: {
        id: 0,
        name: "",
      } as Tag,
      saveLoading: false,
    });

    const handleSearch = async (): Promise<void> => {
      state.isLoading = true;
      try {
        const data: ResponseData = await getTagList(state.params);
        state.isLoading = false;
        state.tagList = data.results;
        state.total = data.count;
      } catch (e) {
        console.error(e);
        state.isLoading = false;
      }
    };

    const deleteObject = async (index: number, row: Tag) => {
      await deleteTag(row.id);
      ElMessage({
        message: "刪除成功尝盼!",
        type: "success",
      });
      await handleSearch();
    };

    const datetimeFormatter = (
      row: Tag,
      column: number,
      cellValue: string,
      index: number
    ) => {
      return timestampToTime(cellValue, true);
    };

    handleSearch();
    return {
      state,
      handleSearch,
      datetimeFormatter,
      deleteObject,
    };
  },
  methods: {
    showEditDialog(index: number, row: Tag) {
      this.state.tag = row;
      this.state.showDialog = true;
    },

    showAddDialog() {
      this.state.tag = {} as Tag;
      this.state.showDialog = true;
    },

    async handleCloseDialog(params: any) {
      if (!params.isOk) {
        this.state.showDialog = false;
        return;
      }
      this.state.saveLoading = true;
      const method = this.state.tag.id ? "put" : "post";
      try {
        await saveTag(method, params.obj);
        this.state.showDialog = false;
        this.state.saveLoading = false;
        await this.handleSearch();
      } catch (e) {
        console.error(e);
        this.state.saveLoading = false;
      }
    },
  },
});
</script>

<style scoped>
.pagination {
  text-align: right;
  margin-top: 12px;
}
</style>

1.3.5Router

定義route來完成路由跳轉(zhuǎn)。在src/route/index.ts 文件中新增代碼:

import {createRouter, createWebHistory, RouteRecordRaw} from "vue-router";
import Home from "../views/client/Home.vue";

const routes: Array<RouteRecordRaw> = [
    {
        path: "/",
        name: "Home",
        component: Home,
        meta: {}
    },
    {
        path: "/login/",
        name: "Login",
        component: () =>
            import"../views/admin/Login.vue")
    },
    {
        path: '/admin',
        name: 'Admin',
        component: () => import("../views/admin/Admin.vue"),
        children: [
            {
                path: '/admin/',
                name: 'Dashboard',
                component: () => import("../views/admin/Dashboard.vue"),
            },
            {
                path: '/admin/dashboard',
                name: 'AdminDashboard',
                component: () => import("../views/admin/Dashboard.vue"),
            },
            {
                path: '/admin/user',
                name: 'UserManagement',
                component: () => import("../views/admin/User.vue"),
            },
            {
                path: '/admin/tag',
                name: 'Tag',
                component: () => import("../views/admin/Tag.vue"),
            },
        ]
    },
]

const router = createRouter({
    history: createWebHistory(import.meta.env.BASE_URL),
    routes,
});


export default router;
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末佑菩,一起剝皮案震驚了整個濱河市盾沫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌殿漠,老刑警劉巖赴精,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異绞幌,居然都是意外死亡蕾哟,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門莲蜘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谭确,“玉大人,你說我怎么就攤上這事票渠≈鸸” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵庄新,是天一觀的道長鞠眉。 經(jīng)常有香客問我,道長择诈,這世上最難降的妖魔是什么械蹋? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮羞芍,結(jié)果婚禮上哗戈,老公的妹妹穿的比我還像新娘。我一直安慰自己荷科,他們只是感情好唯咬,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布纱注。 她就那樣靜靜地躺著,像睡著了一般胆胰。 火紅的嫁衣襯著肌膚如雪狞贱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天蜀涨,我揣著相機與錄音瞎嬉,去河邊找鬼。 笑死厚柳,一個胖子當著我的面吹牛氧枣,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播别垮,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼便监,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了碳想?” 一聲冷哼從身側(cè)響起烧董,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎胧奔,沒想到半個月后解藻,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡葡盗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了啡浊。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片觅够。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖巷嚣,靈堂內(nèi)的尸體忽然破棺而出喘先,到底是詐尸還是另有隱情,我是刑警寧澤廷粒,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布窘拯,位于F島的核電站,受9級特大地震影響坝茎,放射性物質(zhì)發(fā)生泄漏涤姊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一嗤放、第九天 我趴在偏房一處隱蔽的房頂上張望思喊。 院中可真熱鬧,春花似錦次酌、人聲如沸恨课。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽剂公。三九已至希俩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間纲辽,已是汗流浹背颜武。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留文兑,地道東北人盒刚。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像绿贞,于是被迫代替她去往敵國和親因块。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容