Selaa lähdekoodia

Merge branch 'test' into develop

develop
chenJinxu 11 kuukautta sitten
vanhempi
commit
f464a43b5b
29 muutettua tiedostoa jossa 693 lisäystä ja 92 poistoa
  1. +0
    -4
      .env.staging
  2. +4
    -0
      .env.test
  3. +4
    -3
      .postcssrc.js
  4. +11
    -4
      README.md
  5. +36
    -0
      package-lock.json
  6. +9
    -7
      package.json
  7. +7
    -2
      src/assets/css/index.scss
  8. +321
    -0
      src/assets/css/reset.scss
  9. +61
    -0
      src/components/NavBar.vue
  10. +39
    -0
      src/config/amap.js
  11. +3
    -3
      src/config/env.development.js
  12. +3
    -3
      src/config/env.production.js
  13. +0
    -8
      src/config/env.staging.js
  14. +8
    -0
      src/config/env.test.js
  15. +1
    -1
      src/config/models.js
  16. +5
    -1
      src/main.js
  17. +2
    -2
      src/plugins/vant.js
  18. +1
    -1
      src/router/index.js
  19. +28
    -10
      src/router/router.config.js
  20. +0
    -5
      src/views/AboutView.vue
  21. +0
    -29
      src/views/HomeView.vue
  22. +18
    -0
      src/views/development/index.vue
  23. +18
    -0
      src/views/insight/index.vue
  24. +27
    -6
      src/views/layouts/index.vue
  25. +18
    -0
      src/views/myself/index.scss
  26. +30
    -0
      src/views/myself/index.vue
  27. +18
    -0
      src/views/optimize/index.vue
  28. +18
    -0
      src/views/today/index.vue
  29. +3
    -3
      vue.config.js

+ 0
- 4
.env.staging Näytä tiedosto

@@ -1,4 +0,0 @@
NODE_ENV='production'
# must start with VUE_APP_
VUE_APP_ENV = 'staging'

+ 4
- 0
.env.test Näytä tiedosto

@@ -0,0 +1,4 @@
NODE_ENV='test'
# must start with VUE_APP_
VUE_APP_ENV = 'test'

+ 4
- 3
.postcssrc.js Näytä tiedosto

@@ -7,12 +7,13 @@ module.exports = ({ file }) => {
'postcss-px-to-viewport': {
unitToConvert: 'px', // 要转化的单位
viewportWidth: file.includes('vant') ? 375 : 750, // 视窗的宽度,对应的是我们设计稿的宽度,一般是750
viewportHeight: 812, // 视窗的高度,根据750设备的宽度来指定,一般指定1334,也可以不配置
unitPrecision: 6, // 指定`px`转换为视窗单位值的小数位数
viewportUnit: 'vw', //指定需要转换成的视窗单位,建议使用vw
selectorBlackList: [], // 指定不转换为视窗单位的类,可以自定义,可以无限添加,建议定义一至两个通用的类名
selectorBlackList: ['van'], // 指定不转换为视窗单位的类,可以自定义,可以无限添加,建议定义一至两个通用的类名
minPixelValue: 1, // 小于或等于`1px`不转换为视窗单位,你也可以设置为你想要的值
mediaQuery: false // 允许在媒体查询中转换`px`
mediaQuery: false, // 允许在媒体查询中转换`px`
landscapeUnit: 'vw', // 横屏时使用的单位
landscapeWidth: 1280 // 横屏时使用的视口宽度
}
}
};


+ 11
- 4
README.md Näytä tiedosto

@@ -1,4 +1,4 @@
# health_students_web
# health_student_web

## Project setup

@@ -26,8 +26,15 @@ npm run lint

### Customize configuration

See [Configuration Reference](https://cli.vuejs.org/config/).

# HealthStudentWeb

健康同学微信公众号 h5 项目
健康同学微信公众号 vue2 h5 项目

## v1.0.1F

`2023年12月13日` FETURE

- 构建 基本框架
- 增加 navbar 组件
- 增加 样式初始化 css
- 增加 day.js,echarts 和高德地图

+ 36
- 0
package-lock.json Näytä tiedosto

@@ -3598,6 +3598,11 @@
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
},
"dayjs": {
"version": "1.11.10",
"resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.10.tgz",
"integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ=="
},
"debounce": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/debounce/-/debounce-1.2.1.tgz",
@@ -3888,6 +3893,22 @@
"integrity": "sha512-wK2sCs4feiiJeFXn3zvY0p41mdU5VUgbgs1rNsc/y5ngFUijdWd+iIN8eoyuZHKB8xN6BL4PdWmzqFmxNg6V2w==",
"dev": true
},
"echarts": {
"version": "5.4.3",
"resolved": "https://registry.npmmirror.com/echarts/-/echarts-5.4.3.tgz",
"integrity": "sha512-mYKxLxhzy6zyTi/FaEbJMOZU1ULGEQHaeIeuMR5L+JnJTpz+YR03mnnpBhbR4+UYJAgiXgpyTVLffPAjOTLkZA==",
"requires": {
"tslib": "2.3.0",
"zrender": "5.4.4"
},
"dependencies": {
"tslib": {
"version": "2.3.0",
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
}
}
},
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz",
@@ -8637,6 +8658,21 @@
"dev": true
}
}
},
"zrender": {
"version": "5.4.4",
"resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.4.4.tgz",
"integrity": "sha512-0VxCNJ7AGOMCWeHVyTrGzUgrK4asT4ml9PEkeGirAkKNYXYzoPJCLvmyfdoOXcjTHPs10OZVMfD1Rwg16AZyYw==",
"requires": {
"tslib": "2.3.0"
},
"dependencies": {
"tslib": {
"version": "2.3.0",
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
}
}
}
}
}

+ 9
- 7
package.json Näytä tiedosto

@@ -1,15 +1,15 @@
{
"name": "vue-h5-template",
"version": "2.1.0",
"description": "A vue h5 template with Vant UI",
"author": "Sunnie <sunniejs@163.com>",
"name": "health-student",
"version": "1.0.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"stage": "vue-cli-service build --mode staging",
"lint": "vue-cli-service lint",
"deps": "yarn upgrade-interactive --latest"
"deps": "yarn upgrade-interactive --latest",
"dev": "vue-cli-service serve --mode development",
"test": "vue-cli-service serve --mode test",
"pro": "vue-cli-service serve --mode production"
},
"dependencies": {
"amfe-flexible": "^2.2.1",
@@ -19,7 +19,9 @@
"vant": "^2.12.48",
"vue": "^2.7.8",
"vue-router": "^3.5.4",
"vuex": "^3.6.2"
"vuex": "^3.6.2",
"dayjs": "^1.11.7",
"echarts": "^5.4.1"
},
"devDependencies": {
"@babel/core": "^7.18.10",


+ 7
- 2
src/assets/css/index.scss Näytä tiedosto

@@ -1,6 +1,6 @@
@import './variables.scss';
@import './mixin.scss';
@import './reset.scss';
html,
body .app {
color: #333333;
@@ -9,5 +9,10 @@ body .app {
}

.app-container {
padding-bottom: 100px;
position: relative;
height: calc(100vh - 100px);
.layout-content {
position: relative;
height: 100%;
}
}

+ 321
- 0
src/assets/css/reset.scss Näytä tiedosto

@@ -0,0 +1,321 @@
* {
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
-webkit-tap-highlight-color: transparent;
}

html {
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}

body,
html {
-webkit-user-select: none;
user-select: none;
}

address,
applet,
article,
aside,
audio,
blockquote,
body,
canvas,
caption,
dd,
details,
div,
dl,
dt,
embed,
figcaption,
figure,
footer,
h1,
h2,
h3,
h4,
h5,
h6,
header,
html,
iframe,
li,
mark,
menu,
nav,
object,
ol,
output,
p,
pre,
progress,
ruby,
section,
summary,
table,
tbody,
td,
tfoot,
th,
thead,
time,
tr,
ul,
video {
margin: 0;
padding: 0;
border: 0;
vertical-align: baseline;
}

a {
color: #444444;
background-color: transparent;
text-decoration: none;
-webkit-touch-callout: none;
outline: none;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}

a.active,
a.link,
a.hover {
color: #444444;
}

li {
list-style: none;
}

article,
aside,
details,
figcaption,
figure,
footer,
header,
main,
menu,
nav,
section,
summary {
display: block;
}

audio,
canvas,
progress,
video {
display: inline-block;
}

audio:not([controls]) {
display: none;
height: 0;
}

[hidden],
template {
display: none;
}

a:active,
a:hover {
outline: 0;
}

b,
strong {
font-weight: 700;
}

small {
font-size: 80%;
}

sub,
sup {
position: relative;
vertical-align: baseline;
font-size: 75%;
line-height: 0;
}

sup {
top: -0.5em;
}

sub {
bottom: -0.25em;
}

img {
border: 0;
-webkit-touch-callout: none;
}

svg:not(:root) {
overflow: hidden;
}

hr {
box-sizing: content-box;
height: 0;
}

pre {
overflow: auto;
}

code,
kbd,
pre,
samp {
font-size: 1em;
font-family: monospace;
}

a,
button,
input,
optgroup,
select,
textarea {
-webkit-tap-highlight-color: transparent;
}

button,
input,
optgroup,
select,
textarea {
margin: 0;
outline: 0;
color: inherit;
font: inherit;
line-height: normal;
-webkit-appearance: none;
}

button {
overflow: visible;
}

button,
select {
text-transform: none;
}

button,
html input[type='button'],
input[type='reset'],
input[type='submit'] {
cursor: pointer;
-webkit-appearance: button;
}

button[disabled],
html input[disabled] {
cursor: default;
}

button::-moz-focus-inner,
input::-moz-focus-inner {
padding: 0;
border: 0;
}

input {
line-height: normal;
}

input[type='checkbox'],
input[type='radio'] {
box-sizing: border-box;
padding: 0;
}

input[type='number']::-webkit-inner-spin-button,
input[type='number']::-webkit-outer-spin-button {
height: auto;
}

input[type='search'] {
box-sizing: content-box;
-webkit-appearance: textfield;
}

input[type='search']::-webkit-search-cancel-button,
input[type='search']::-webkit-search-decoration {
-webkit-appearance: none;
}

fieldset {
margin: 0 2px;
padding: 0.35em 0.625em 0.75em;
border: 1px solid silver;
}

legend {
padding: 0;
border: 0;
}

textarea {
overflow: auto;
}

optgroup {
font-weight: 700;
}

table {
border-collapse: collapse;
border-spacing: 0;
}

td,
th {
padding: 0;
}

.fl,
.leftArea {
float: left;
}

.fr,
.rightArea {
float: right;
}

.clearFix:after {
clear: both;
display: block;
visibility: hidden;
height: 0;
content: '.';
}

input {
border: none;
}

button,
input,
select,
textarea {
outline: 0;
}

textarea {
overflow: hidden;
border: none;
resize: none;
}

img[src=''], img:not([src]) {
opacity: 0;
}

+ 61
- 0
src/components/NavBar.vue Näytä tiedosto

@@ -0,0 +1,61 @@
<!-- -->
<template>
<div class="nav-bar">
<van-nav-bar
:title="title"
:left-text="leftText"
:right-text="rightText"
:left-arrow="leftArrow"
@click-left="onClickLeft"
@click-right="onClickRight"
></van-nav-bar>
</div>
</template>

<script>
export default {
name: 'NavBar',
props: {
title: {
type: String,
default: ''
},
leftText: {
type: String,
default: '' || '返回'
},
rightText: {
type: String,
default: ''
},
leftArrow: {
type: Boolean,
default: true
}
},
data() {
return {};
},
created() {},
mounted() {},
methods: {
onClickLeft(value) {
this.$emit('on-click-left', value);
},
onClickRight(value) {
this.$emit('on-click-right', value);
}
}
};
</script>
<style scoped lang="scss">
.nav-bar {
position: absolute !important;
left: 0;
top: 0;
height: 100px;
width: 100%;
}

/* @import url(); 引入css类 */
</style>

+ 39
- 0
src/config/amap.js Näytä tiedosto

@@ -0,0 +1,39 @@
export default function MapLoader(isSyncLoad = false, pluginsArr = []) {
// plugin: 字符串数组[ 'AMap.Geocoder', ... ]
return new Promise((resolve, reject) => {
try {
if (window.AMap && (pluginsArr === null || pluginsArr === undefined || pluginsArr.length === 0)) {
resolve(window.AMap);
} else {
var script = document.createElement('script');
script.type = 'text/javascript';
script.async = !isSyncLoad;
script.src =
'https://webapi.amap.com/maps?v=1.4.15&callback=initAMap&key=' +
'6e4a6c39ea6d18b8dd3151baa3a7c0d5' +
'&plugin=AMap.BezierCurveEditor' +
(pluginsArr ? ',' + pluginsArr.join(',') : '');
script.onerror = reject;
document.head.appendChild(script);

var script1 = document.createElement('script');
script1.type = 'text/javascript';
script1.async = false;
script1.src = 'https://webapi.amap.com/ui/1.0/main.js?v=1.0.11';
script1.onerror = reject;
if (isSyncLoad) {
document.head.appendChild(script1);
}
}
window.initAMap = () => {
resolve(window.AMap);
};
// JSAPI key搭配静态安全密钥以明文设置, 详情见: https://lbs.amap.com/api/jsapi-v2/guide/abc/load
window._AMapSecurityConfig = {
securityJsCode: '6a421e1233cd12dd4899e373e11bb641'
};
} catch (e) {
console.log(e);
}
});
}

+ 3
- 3
src/config/env.development.js Näytä tiedosto

@@ -1,8 +1,8 @@
// 本地环境配置
module.exports = {
title: 'vue-h5-template',
baseUrl: 'http://localhost:9018', // 项目地址
baseApi: 'https://test.xxx.com/api', // 本地api请求地址,注意:如果你使用了代理,请设置成'/'
title: '健康同学',
baseUrl: 'https://id.ssjlai.com/', // 项目地址
baseApi: 'https://id.ssjlai.com/', // 本地api请求地址,注意:如果你使用了代理,请设置成'/'
APPID: 'xxx',
APPSECRET: 'xxx',
$cdn: 'https://www.sunniejs.cn/static'


+ 3
- 3
src/config/env.production.js Näytä tiedosto

@@ -1,8 +1,8 @@
// 正式
module.exports = {
title: 'vue-h5-template',
baseUrl: 'https://www.xxx.com/', // 正式项目地址
baseApi: 'https://www.xxx.com/api', // 正式api请求地址
title: '健康同学',
baseUrl: 'https://ai.ssjlai.com/', // 正式项目地址
baseApi: 'https://ai.ssjlai.com/', // 正式api请求地址
APPID: 'xxx',
APPSECRET: 'xxx',
$cdn: 'https://www.sunniejs.cn/static'


+ 0
- 8
src/config/env.staging.js Näytä tiedosto

@@ -1,8 +0,0 @@
module.exports = {
title: 'vue-h5-template',
baseUrl: 'https://test.xxx.com', // 测试项目地址
baseApi: 'https://test.xxx.com/api', // 测试api请求地址
APPID: 'xxx',
APPSECRET: 'xxx',
$cdn: 'https://www.sunniejs.cn/static'
};

+ 8
- 0
src/config/env.test.js Näytä tiedosto

@@ -0,0 +1,8 @@
module.exports = {
title: '健康同学',
baseUrl: 'https://id.ssjlai.com/', // 测试项目地址
baseApi: 'https://id.ssjlai.com/', // 测试api请求地址
APPID: 'xxx',
APPSECRET: 'xxx',
$cdn: 'https://www.sunniejs.cn/static'
};

+ 1
- 1
src/config/models.js Näytä tiedosto

@@ -1,2 +1,2 @@
// 项目版本号
export const VersionModel = '1.0.0';
export const VersionModel = '1.0.1';

+ 5
- 1
src/main.js Näytä tiedosto

@@ -10,13 +10,17 @@ import store from './store';
// 设置 js中可以访问 $cdn
import { $cdn } from '@/config';
Vue.prototype.$cdn = $cdn;

import dayjs from 'dayjs';
// 引入echarts
import * as echarts from 'echarts';
// 全局引入按需引入UI库 vant
import '@/plugins/vant';
// 引入全局样式
import '@/assets/css/index.scss';
// 移动端适配
import 'amfe-flexible';
Vue.prototype.$dayjs = dayjs;
Vue.prototype.$echarts = echarts;

// filters
import './filters';


+ 2
- 2
src/plugins/vant.js Näytä tiedosto

@@ -1,4 +1,4 @@
// 按需全局引入 vant组件
import Vue from 'vue';
import { Button, Tabbar, TabbarItem, Toast } from 'vant';
Vue.use(Button).use(Tabbar).use(TabbarItem).use(Toast);
import { Button, Tabbar, TabbarItem, Toast, NavBar } from 'vant';
Vue.use(Button).use(Tabbar).use(TabbarItem).use(Toast).use(NavBar);

+ 1
- 1
src/router/index.js Näytä tiedosto

@@ -26,5 +26,5 @@ export function resetRouter() {
const newRouter = createRouter();
router.matcher = newRouter.matcher; // reset router
}
// TODO 增加路由拦截
export default router;

+ 28
- 10
src/router/router.config.js Näytä tiedosto

@@ -6,23 +6,41 @@ export const constantRouterMap = [
{
path: '/',
component: () => import('@/views/layouts/index'),
redirect: '/home',
redirect: '/index',
meta: {
title: '首页',
title: '成长',
keepAlive: false
},
children: [
{
path: '/home',
name: 'Home',
component: () => import('@/views/HomeView'),
meta: { title: '首页', keepAlive: false }
path: '/index',
name: 'Development',
component: () => import('@/views/development/index'),
meta: { title: '成长', keepAlive: false }
},
{
path: '/about',
name: 'About',
component: () => import('@/views/AboutView'),
meta: { title: '关于我', keepAlive: false }
path: '/today',
name: 'Today',
component: () => import('@/views/today/index'),
meta: { title: '今日', keepAlive: false }
},
{
path: '/insight',
name: 'Insight',
component: () => import('@/views/insight/index'),
meta: { title: '洞悉', keepAlive: false }
},
{
path: '/optimize',
name: 'Optimize',
component: () => import('@/views/optimize/index'),
meta: { title: '优化', keepAlive: false }
},
{
path: '/myself',
name: 'Myself',
component: () => import('@/views/myself/index'),
meta: { title: '我的', keepAlive: false }
}
]
}


+ 0
- 5
src/views/AboutView.vue Näytä tiedosto

@@ -1,5 +0,0 @@
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>

+ 0
- 29
src/views/HomeView.vue Näytä tiedosto

@@ -1,29 +0,0 @@
<template>
<div>
<van-button @click="onSave" type="warning">测试</van-button>
</div>
</template>

<script>
export default {
data() {
return {};
},
created() {
this.code = 'daa';
},
mounted() {},
methods: {
onSave() {
this.$store.dispatch('setTestName', '测试撒法发啊啊');
console.log(this.$store.getters.testName);
this.$toast.success({
message: `${this.$store.getters.testName}`
});
}
}
};
</script>
<style scoped>
/* @import url(); 引入css类 */
</style>

+ 18
- 0
src/views/development/index.vue Näytä tiedosto

@@ -0,0 +1,18 @@
<!-- -->
<template>
<div></div>
</template>

<script>
export default {
data() {
return {};
},
created() {},
mounted() {},
methods: {}
};
</script>
<style scoped>
/* @import url(); 引入css类 */
</style>

+ 18
- 0
src/views/insight/index.vue Näytä tiedosto

@@ -0,0 +1,18 @@
<!-- -->
<template>
<div></div>
</template>

<script>
export default {
data() {
return {};
},
created() {},
mounted() {},
methods: {}
};
</script>
<style scoped>
/* @import url(); 引入css类 */
</style>

+ 27
- 6
src/views/layouts/index.vue Näytä tiedosto

@@ -20,18 +20,39 @@ export default {
return {
tabbars: [
{
title: '首页',
title: '成长',
to: {
name: 'Home'
name: 'Development'
},
icon: 'home-o'
icon: 'guide-o'
},
{
title: '关于我',
title: '今日',
to: {
name: 'About'
name: 'Today'
},
icon: 'user-o'
icon: 'notes-o'
},
{
title: '洞悉',
to: {
name: 'Insight'
},
icon: 'apps-o'
},
{
title: '优化',
to: {
name: 'Optimize'
},
icon: 'diamond-o'
},
{
title: '我的',
to: {
name: 'Myself'
},
icon: 'user-circle-o'
}
]
};


+ 18
- 0
src/views/myself/index.scss Näytä tiedosto

@@ -0,0 +1,18 @@
.myself {
position: relative;
height: 100vh;
width: 100%;
overflow: hidden;
.container {
height: calc(100vh - 200px);
padding-top: 100px;
overflow: scroll;
.box-scor {
position: relative;
.item {
font-size: 32px;
height: 200px;
}
}
}
}

+ 30
- 0
src/views/myself/index.vue Näytä tiedosto

@@ -0,0 +1,30 @@
<!-- -->
<template>
<div class="myself">
<NavBar></NavBar>
<div class="container">
<div class="box-scor">
<div v-for="(item, index) in 20" :key="index" class="item">
{{ index }}
</div>
</div>
</div>
</div>
</template>

<script>
import NavBar from '@/components/NavBar.vue';
export default {
components: { NavBar },
data() {
return {};
},
created() {},
mounted() {},
methods: {}
};
</script>
<style scoped lang="scss">
@import './index.scss';
/* @import url(); 引入css类 */
</style>

+ 18
- 0
src/views/optimize/index.vue Näytä tiedosto

@@ -0,0 +1,18 @@
<!-- -->
<template>
<div></div>
</template>

<script>
export default {
data() {
return {};
},
created() {},
mounted() {},
methods: {}
};
</script>
<style scoped>
/* @import url(); 引入css类 */
</style>

+ 18
- 0
src/views/today/index.vue Näytä tiedosto

@@ -0,0 +1,18 @@
<!-- -->
<template>
<div></div>
</template>

<script>
export default {
data() {
return {};
},
created() {},
mounted() {},
methods: {}
};
</script>
<style scoped>
/* @import url(); 引入css类 */
</style>

+ 3
- 3
vue.config.js Näytä tiedosto

@@ -4,7 +4,7 @@ const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPl

const resolve = dir => path.join(__dirname, dir);
// page title
const name = defaultSettings.title || 'vue mobile template';
const name = defaultSettings.title || '';
// 生产环境,测试和正式
const IS_PROD = ['production', 'prod'].includes(process.env.NODE_ENV);

@@ -92,8 +92,8 @@ module.exports = defineConfig({
},

chainWebpack: config => {
config.plugins.delete('preload'); // TODO: need test
config.plugins.delete('prefetch'); // TODO: need test
/* config.plugins.delete('preload');
config.plugins.delete('prefetch'); */

// 别名 alias
config.resolve.alias


Loading…
Peruuta
Tallenna